diff --git a/unison-cli-integration/integration-tests/IntegrationTests/transcript.md b/unison-cli-integration/integration-tests/IntegrationTests/transcript.md index 0b26c2d432..bcab5daec6 100644 --- a/unison-cli-integration/integration-tests/IntegrationTests/transcript.md +++ b/unison-cli-integration/integration-tests/IntegrationTests/transcript.md @@ -2,7 +2,7 @@ ``` ucm :hide scratch/main> builtins.mergeio lib.builtins -scratch/main> load ./unison-src/transcripts-using-base/base.u +scratch/main> load "./unison-src/transcripts-using-base/base.u" scratch/main> add ``` diff --git a/unison-cli/src/Unison/Codebase/Editor/HandleInput.hs b/unison-cli/src/Unison/Codebase/Editor/HandleInput.hs index 3300abdf1a..56036072bd 100644 --- a/unison-cli/src/Unison/Codebase/Editor/HandleInput.hs +++ b/unison-cli/src/Unison/Codebase/Editor/HandleInput.hs @@ -712,18 +712,31 @@ loop e = do DebugFuzzyOptionsI command args -> do Cli.Env {codebase} <- ask currentBranch <- Branch.withoutTransitiveLibs <$> Cli.getCurrentBranch0 - case Map.lookup command InputPatterns.patternMap of - Just (IP.InputPattern {args = argTypes}) -> do - zip argTypes args & Monoid.foldMapM \case - ((argName, _, IP.ArgumentType {fzfResolver = Just IP.FZFResolver {getOptions}}), "_") -> do - pp <- Cli.getCurrentProjectPath - results <- liftIO $ getOptions codebase pp currentBranch - Cli.respond (DebugDisplayFuzzyOptions argName (Text.unpack <$> results)) - ((_, _, IP.ArgumentType {fzfResolver = Nothing}), "_") -> do - Cli.respond DebugFuzzyOptionsNoResolver - _ -> pure () - Nothing -> do - Cli.respond DebugFuzzyOptionsNoResolver + maybe + (Cli.respond $ DebugFuzzyOptionsNoCommand command) + ( \IP.InputPattern {params} -> + either (Cli.respond . DebugFuzzyOptionsIncorrectArgs) snd $ + IP.foldArgs + ( \(paramName, IP.ParameterType {fzfResolver}) arg -> + ( *> + if arg == "_" + then + maybe + (Cli.respond DebugFuzzyOptionsNoResolver) + ( \IP.FZFResolver {getOptions} -> do + pp <- Cli.getCurrentProjectPath + results <- liftIO $ getOptions codebase pp currentBranch + Cli.respond (DebugDisplayFuzzyOptions paramName (Text.unpack <$> results)) + ) + fzfResolver + else pure () + ) + ) + (pure ()) + params + args + ) + $ Map.lookup command InputPatterns.patternMap DebugFormatI -> do env <- ask void $ runMaybeT do diff --git a/unison-cli/src/Unison/Codebase/Editor/Output.hs b/unison-cli/src/Unison/Codebase/Editor/Output.hs index b811fde7f9..efd004bdf3 100644 --- a/unison-cli/src/Unison/Codebase/Editor/Output.hs +++ b/unison-cli/src/Unison/Codebase/Editor/Output.hs @@ -355,6 +355,8 @@ data Output | DisplayDebugCompletions [Completion.Completion] | DisplayDebugLSPNameCompletions [(Text, Name, LabeledDependency)] | DebugDisplayFuzzyOptions Text [String {- arg description, options -}] + | DebugFuzzyOptionsIncorrectArgs (NonEmpty String) + | DebugFuzzyOptionsNoCommand String | DebugFuzzyOptionsNoResolver | DebugTerm (Bool {- verbose mode -}) (Either (Text {- A builtin hash -}) (Term Symbol Ann)) | DebugDecl (Either (Text {- A builtin hash -}) (DD.Decl Symbol Ann)) (Maybe ConstructorId {- If 'Just' we're debugging a constructor of the given decl -}) @@ -624,6 +626,8 @@ isFailure o = case o of DisplayDebugCompletions {} -> False DisplayDebugLSPNameCompletions {} -> False DebugDisplayFuzzyOptions {} -> False + DebugFuzzyOptionsIncorrectArgs {} -> True + DebugFuzzyOptionsNoCommand {} -> True DebugFuzzyOptionsNoResolver {} -> True DebugTerm {} -> False DebugDecl {} -> False diff --git a/unison-cli/src/Unison/Codebase/Transcript/Runner.hs b/unison-cli/src/Unison/Codebase/Transcript/Runner.hs index 9c06e31da8..77dc1fa88f 100644 --- a/unison-cli/src/Unison/Codebase/Transcript/Runner.hs +++ b/unison-cli/src/Unison/Codebase/Transcript/Runner.hs @@ -297,27 +297,24 @@ run isTest verbosity dir codebase runtime sbRuntime nRuntime ucmVersion baseURL atomically . Q.undequeue cmdQueue $ Just p pure $ Right switchCommand Nothing -> do - case words . Text.unpack $ lineTxt of - [] -> Cli.returnEarlyWithoutOutput - args -> do - liftIO $ outputUcmLine p - numberedArgs <- use #numberedArgs - PP.ProjectAndBranch projId branchId <- - PP.toProjectAndBranch . NonEmpty.head <$> use #projectPathStack - let getProjectRoot = liftIO $ Codebase.expectProjectBranchRoot codebase projId branchId - liftIO (parseInput codebase curPath getProjectRoot numberedArgs patternMap args) - >>= either - -- invalid command is treated as a failure - ( \msg -> do - liftIO $ writeIORef hasErrors True - liftIO (readIORef allowErrors) >>= \case - True -> do - liftIO $ outputUcmResult msg - Cli.returnEarlyWithoutOutput - False -> liftIO . dieWithMsg $ Pretty.toPlain terminalWidth msg - ) - -- No input received from this line, try again. - (maybe Cli.returnEarlyWithoutOutput $ pure . Right . snd) + liftIO $ outputUcmLine p + numberedArgs <- use #numberedArgs + PP.ProjectAndBranch projId branchId <- + PP.toProjectAndBranch . NonEmpty.head <$> use #projectPathStack + let getProjectRoot = liftIO $ Codebase.expectProjectBranchRoot codebase projId branchId + liftIO (parseInput codebase curPath getProjectRoot numberedArgs patternMap $ Text.unpack lineTxt) + >>= either + -- invalid command is treated as a failure + ( \msg -> do + liftIO $ writeIORef hasErrors True + liftIO (readIORef allowErrors) >>= \case + True -> do + liftIO $ outputUcmResult msg + Cli.returnEarlyWithoutOutput + False -> liftIO . dieWithMsg $ Pretty.toPlain terminalWidth msg + ) + -- No input received from this line, try again. + (maybe Cli.returnEarlyWithoutOutput $ pure . Right . (\(_, _, input) -> input)) startProcessedBlock block = case block of Unison infoTags txt -> do diff --git a/unison-cli/src/Unison/CommandLine.hs b/unison-cli/src/Unison/CommandLine.hs index 99ac5799d9..d8b9907cae 100644 --- a/unison-cli/src/Unison/CommandLine.hs +++ b/unison-cli/src/Unison/CommandLine.hs @@ -15,13 +15,16 @@ import Control.Lens hiding (aside) import Control.Monad.Except import Control.Monad.Trans.Except import Data.List (isPrefixOf, isSuffixOf) +import Data.List.Extra (breakOn, trimStart) +import Data.List.NonEmpty (NonEmpty ((:|)), nonEmpty) import Data.Map qualified as Map -import Data.Semialign qualified as Align import Data.Text qualified as Text import Data.Text.IO qualified as Text -import Data.These (These (..)) import Data.Vector qualified as Vector import System.FilePath (takeFileName) +import Text.Megaparsec.Error qualified as EP +import Text.Numeral (defaultInflection) +import Text.Numeral.Language.ENG qualified as Numeral import Text.Regex.TDFA ((=~)) import Unison.Codebase (Codebase) import Unison.Codebase.Branch (Branch0) @@ -39,6 +42,8 @@ import Unison.CommandLine.InputPatterns qualified as IPs import Unison.Parser.Ann (Ann) import Unison.Prelude import Unison.Symbol (Symbol) +import Unison.Syntax.Lexer.Token qualified as Token +import Unison.Syntax.Lexer.Unison qualified as Lexer import Unison.Util.ColorText qualified as CT import Unison.Util.Monoid (foldMapM) import Unison.Util.Pretty qualified as P @@ -60,6 +65,81 @@ watchFileSystem q dir = do atomically . Q.enqueue q $ UnisonFileChanged (Text.pack filePath) text pure (cancel >> killThread t) +-- | Expanding numbers is a bit complicated. Each `Parameter` expects either structured or “unstructured” arguments. So +-- we iterate over the parameters, if it doesn’t want structured, we just preserve the string. If it does want +-- structured, we have to expand the argument, which may result in /multiple/ structured arguments, we take the first +-- one for the param and pass the rest along. Now, if the next param wants unstructured, but we’ve already structured +-- it, then we’ve got an error. +expandArguments :: + NumberedArgs -> + InputPattern.Parameters -> + [Lexer.Lexeme] -> + Either (NonEmpty InputPattern.Argument) (InputPattern.Parameters, InputPattern.Arguments) +expandArguments numberedArgs params = + fmap (fmap $ reverse) + . InputPattern.foldArgs' + ( \acc (_, param) arg -> + if InputPattern.isStructured param + then + either + ( maybe + (arg : acc, []) + ( maybe + (arg : acc, []) -- FIXME: shouldn’t be empty – error here, or enforce nonempty earlier? + (\(h :| t) -> (h : acc, t)) + . nonEmpty + . fmap pure + ) + . expandNumber numberedArgs + ) + ((,[]) . (: acc) . pure) + arg + else + ( either + Left + pure -- FIXME: Should error, because we have a structured arg when we didn’t expect one + arg + : acc, + [] + ) + ) + [] + params + . fmap Left + +reportTooManyArgs :: + InputPattern.Parameters -> NonEmpty InputPattern.Argument -> ExceptT (P.Pretty CT.ColorText) IO a +reportTooManyArgs params extraArgs = do + let showNum n = fromMaybe (tShow n) $ Numeral.us_cardinal defaultInflection n + maxCount <- + maybe + ( throwError . P.text $ + "Internal error: fuzzy finder complained that there are " + <> showNum (length extraArgs) + <> " too many arguments provided, but the command apparently allows an unbounded number of arguments." + ) + pure + $ InputPattern.maxArgs params + let foundCount = showNum $ maxCount + length extraArgs + throwError . P.text $ + "I expected no more than " <> showNum maxCount <> " arguments, but received " <> foundCount <> ":" <> Text.pack (show extraArgs) <> "." + +reportFailure :: String -> InputPattern -> P.Pretty CT.ColorText -> P.Pretty CT.ColorText +reportFailure command pat msg = + P.warnCallout $ + P.wrap "Sorry, I wasn’t sure how to process your request:" + <> P.newline + <> P.newline + <> P.indentN 2 msg + <> P.newline + <> P.newline + <> P.wrap + ( "You can run" + <> IPs.makeExample IPs.help [fromString command] + <> "for more information on using" + <> IPs.makeExampleEOS pat [] + ) + parseInput :: Codebase IO Symbol Ann -> -- | Current location @@ -69,58 +149,72 @@ parseInput :: NumberedArgs -> -- | Input Pattern Map Map String InputPattern -> - -- | command:arguments - [String] -> - -- Returns either an error message or the fully expanded arguments list and parsed input. + -- | command & arguments + String -> + -- | Returns either an error message or the fully expanded arguments list and parsed input. + -- If the output is `Nothing`, the user cancelled the input (e.g. ctrl-c) + IO + (Either (P.Pretty CT.ColorText) (Maybe (String, InputPattern.Arguments, Input))) +parseInput codebase projPath currentProjectRoot numberedArgs patterns inputStr = + let (command, args) = breakOn " " $ trimStart inputStr + in if command == "" + then pure $ pure Nothing + else + either + (pure . Left . P.string . EP.errorBundlePretty) + ( fmap (fmap $ fmap \(args, input) -> (command, args, input)) + . parseInput' codebase projPath currentProjectRoot numberedArgs patterns command + . fmap Token.payload + . init + ) + $ Lexer.lexer' "UCM" args + +parseInput' :: + Codebase IO Symbol Ann -> + -- | Current location + PP.ProjectPath -> + IO (Branch.Branch IO) -> + -- | Numbered arguments + NumberedArgs -> + -- | Input Pattern Map + Map String InputPattern -> + -- | command + String -> + -- | arguments + [Lexer.Lexeme] -> + -- | Returns either an error message or the fully expanded arguments list and parsed input. -- If the output is `Nothing`, the user cancelled the input (e.g. ctrl-c) IO (Either (P.Pretty CT.ColorText) (Maybe (InputPattern.Arguments, Input))) -parseInput codebase projPath currentProjectRoot numberedArgs patterns segments = runExceptT do +parseInput' codebase projPath currentProjectRoot numberedArgs patterns command args = runExceptT do let getCurrentBranch0 :: IO (Branch0 IO) getCurrentBranch0 = do projRoot <- currentProjectRoot pure . Branch.head $ Branch.getAt' (projPath ^. PP.path_) projRoot - case segments of - [] -> throwE "" - command : args -> case Map.lookup command patterns of - Just pat@(InputPattern {parse, help}) -> do - let expandedNumbers :: InputPattern.Arguments - expandedNumbers = - foldMap (\arg -> maybe [Left arg] (fmap pure) $ expandNumber numberedArgs arg) args - lift (fzfResolve codebase projPath getCurrentBranch0 pat expandedNumbers) >>= \case - Left (NoFZFResolverForArgumentType _argDesc) -> throwError help - Left (NoFZFOptions argDesc) -> throwError (noCompletionsMessage argDesc) - Left FZFCancelled -> pure Nothing - Right resolvedArgs -> do - parsedInput <- - except - . first - ( \msg -> - P.warnCallout $ - P.wrap "Sorry, I wasn’t sure how to process your request:" - <> P.newline - <> P.newline - <> P.indentN 2 msg - <> P.newline - <> P.newline - <> P.wrap - ( "You can run" - <> IPs.makeExample IPs.help [fromString command] - <> "for more information on using" - <> IPs.makeExampleEOS pat [] - ) - ) - $ parse resolvedArgs - pure $ Just (Left command : resolvedArgs, parsedInput) - Nothing -> - throwE - . warn - . P.wrap - $ "I don't know how to" - <> P.group (fromString command <> ".") - <> "Type" - <> IPs.makeExample' IPs.help - <> "or `?` to get help." + maybe + ( throwE . warn . P.wrap $ + "I don't know how to" + <> P.group (fromString command <> ".") + <> "Type" + <> IPs.makeExample' IPs.help + <> "or `?` to get help." + ) + ( \pat@(InputPattern {params, help, parse}) -> do + (remainingParams, expandedNumbers) <- either (reportTooManyArgs params) pure $ expandArguments numberedArgs params args + lift (fzfResolve codebase projPath getCurrentBranch0 remainingParams expandedNumbers) + >>= either + ( \case + NoFZFResolverForArgumentType _argDesc -> throwError help + NoFZFOptions argDesc -> throwError (noCompletionsMessage argDesc) + FZFCancelled -> pure Nothing + FZFOversaturated extraArgs -> reportTooManyArgs params extraArgs + ) + ( \resolvedArgs -> do + parsedInput <- except . first (reportFailure command pat) $ parse resolvedArgs + pure $ Just (resolvedArgs, parsedInput) + ) + ) + $ Map.lookup command patterns where noCompletionsMessage argDesc = P.callout "⚠️" $ @@ -132,8 +226,13 @@ parseInput codebase projPath currentProjectRoot numberedArgs patterns segments = ] -- Expand a numeric argument like `1` or a range like `3-9` -expandNumber :: NumberedArgs -> String -> Maybe NumberedArgs -expandNumber numberedArgs s = +expandNumber :: NumberedArgs -> Lexer.Lexeme -> Maybe NumberedArgs +expandNumber numberedArgs (Lexer.Numeric s) = + (\nums -> [arg | i <- nums, Just arg <- [vargs Vector.!? (i - 1)]]) <$> expandedNumber + where + vargs = Vector.fromList numberedArgs + expandedNumber = pure <$> readMay s +expandNumber numberedArgs (Lexer.Textual s) = (\nums -> [arg | i <- nums, Just arg <- [vargs Vector.!? (i - 1)]]) <$> expandedNumber where vargs = Vector.fromList numberedArgs @@ -146,55 +245,62 @@ expandNumber numberedArgs s = Nothing -> -- check for a range case (junk, moreJunk, ns) of - ("", "", [from, to]) -> - (\x y -> [x .. y]) <$> readMay from <*> readMay to - _ -> Nothing + ("", "", [from, to]) -> enumFromTo <$> readMay from <*> readMay to + (_, _, _) -> Nothing +expandNumber _ _ = Nothing data FZFResolveFailure - = NoFZFResolverForArgumentType InputPattern.ArgumentDescription - | NoFZFOptions Text {- argument description -} + = NoFZFResolverForArgumentType InputPattern.ParameterDescription + | NoFZFOptions + -- | argument description + Text | FZFCancelled + | -- | More arguments were provided than the command supports. + FZFOversaturated + -- | The arguments that couldn’t be assigned to a parameter. + (NonEmpty InputPattern.Argument) -fzfResolve :: Codebase IO Symbol Ann -> PP.ProjectPath -> (IO (Branch0 IO)) -> InputPattern -> InputPattern.Arguments -> IO (Either FZFResolveFailure InputPattern.Arguments) -fzfResolve codebase ppCtx getCurrentBranch pat args = runExceptT do +fzfResolve :: + Codebase IO Symbol Ann -> + PP.ProjectPath -> + (IO (Branch0 IO)) -> + InputPattern.Parameters -> + InputPattern.Arguments -> + IO (Either FZFResolveFailure InputPattern.Arguments) +fzfResolve codebase ppCtx getCurrentBranch InputPattern.Parameters {requiredParams, trailingParams} args = runExceptT do -- We resolve args in two steps, first we check that all arguments that will require a fzf -- resolver have one, and only if so do we prompt the user to actually do a fuzzy search. -- Otherwise, we might ask the user to perform a search only to realize we don't have a resolver -- for a later arg. - argumentResolvers :: [ExceptT FZFResolveFailure IO InputPattern.Arguments] <- - (Align.align (InputPattern.args pat) args) - & traverse \case - This (argName, opt, InputPattern.ArgumentType {fzfResolver}) - | opt == InputPattern.Required || opt == InputPattern.OnePlus -> - case fzfResolver of - Nothing -> throwError $ NoFZFResolverForArgumentType argName - Just fzfResolver -> pure $ fuzzyFillArg opt argName fzfResolver - | otherwise -> pure $ pure [] - That arg -> pure $ pure [arg] - These _ arg -> pure $ pure [arg] + let argumentResolvers :: [ExceptT FZFResolveFailure IO InputPattern.Arguments] = + fmap (pure . pure) args + <> map (meh False) requiredParams + <> case trailingParams of + InputPattern.Optional _ _ -> mempty + InputPattern.OnePlus p -> pure $ meh True p argumentResolvers & foldMapM id where - fuzzyFillArg :: InputPattern.IsOptional -> Text -> InputPattern.FZFResolver -> ExceptT FZFResolveFailure IO InputPattern.Arguments - fuzzyFillArg opt argDesc InputPattern.FZFResolver {getOptions} = do + meh :: Bool -> InputPattern.Parameter -> ExceptT FZFResolveFailure IO InputPattern.Arguments + meh allowMulti (argName, InputPattern.ParameterType {fzfResolver}) = + maybe + (throwError $ NoFZFResolverForArgumentType argName) + (fuzzyFillArg allowMulti argName) + fzfResolver + + fuzzyFillArg :: Bool -> Text -> InputPattern.FZFResolver -> ExceptT FZFResolveFailure IO InputPattern.Arguments + fuzzyFillArg allowMulti argDesc InputPattern.FZFResolver {getOptions} = do currentBranch <- Branch.withoutTransitiveLibs <$> liftIO getCurrentBranch options <- liftIO $ getOptions codebase ppCtx currentBranch - when (null options) $ throwError $ NoFZFOptions argDesc + when (null options) . throwError $ NoFZFOptions argDesc liftIO $ Text.putStrLn (FZFResolvers.fuzzySelectHeader argDesc) results <- - liftIO (Fuzzy.fuzzySelect Fuzzy.defaultOptions {Fuzzy.allowMultiSelect = multiSelectForOptional opt} id options) + liftIO (Fuzzy.fuzzySelect Fuzzy.defaultOptions {Fuzzy.allowMultiSelect = allowMulti} id options) `whenNothingM` throwError FZFCancelled -- If the user triggered the fuzzy finder, but selected nothing, abort the command rather than continuing execution -- with no arguments. if null results then throwError FZFCancelled - else pure (Left . Text.unpack <$> results) - - multiSelectForOptional :: InputPattern.IsOptional -> Bool - multiSelectForOptional = \case - InputPattern.Required -> False - InputPattern.Optional -> False - InputPattern.OnePlus -> True - InputPattern.ZeroPlus -> True + else pure (fmap (Left . Token.payload) . Lexer.lexer "UCM" . Text.unpack =<< results) prompt :: String prompt = "> " diff --git a/unison-cli/src/Unison/CommandLine/Completion.hs b/unison-cli/src/Unison/CommandLine/Completion.hs index 10a838373e..c3920b6ed4 100644 --- a/unison-cli/src/Unison/CommandLine/Completion.hs +++ b/unison-cli/src/Unison/CommandLine/Completion.hs @@ -83,8 +83,8 @@ haskelineTabComplete patterns codebase authedHTTPClient ppCtx = Line.completeWor case words $ reverse prev of h : t -> fromMaybe (pure []) $ do p <- Map.lookup h patterns - argType <- IP.argType p (length t) - pure $ IP.suggestions argType word codebase authedHTTPClient ppCtx + paramType <- IP.paramType (IP.params p) (length t) + pure $ IP.suggestions paramType word codebase authedHTTPClient ppCtx _ -> pure [] -- | Things which we may want to complete for. diff --git a/unison-cli/src/Unison/CommandLine/InputPattern.hs b/unison-cli/src/Unison/CommandLine/InputPattern.hs index cc628559e6..65ed256ddf 100644 --- a/unison-cli/src/Unison/CommandLine/InputPattern.hs +++ b/unison-cli/src/Unison/CommandLine/InputPattern.hs @@ -4,13 +4,18 @@ module Unison.CommandLine.InputPattern ( InputPattern (..), + ParameterDescription, + ParameterType (..), + Parameter, + TrailingParameters (..), + Parameters (..), Argument, - ArgumentType (..), - ArgumentDescription, Arguments, - argType, + foldArgs, + foldArgs', + noParams, + paramType, FZFResolver (..), - IsOptional (..), Visibility (..), -- * Currently Unused @@ -23,6 +28,7 @@ where import Control.Lens import Data.List.Extra qualified as List +import Data.List.NonEmpty (NonEmpty (..)) import System.Console.Haskeline qualified as Line import Unison.Auth.HTTPClient (AuthenticatedHttpClient) import Unison.Codebase (Codebase) @@ -31,19 +37,11 @@ import Unison.Codebase.Editor.StructuredArgument (StructuredArgument) import Unison.Codebase.ProjectPath qualified as PP import Unison.CommandLine.FZFResolvers (FZFResolver (..)) import Unison.Prelude +import Unison.Syntax.Lexer.Unison qualified as Lexer import Unison.Util.ColorText qualified as CT import Unison.Util.Monoid (foldMapM) import Unison.Util.Pretty qualified as P --- InputPatterns accept some fixed number of Required arguments of various --- types, followed by a variable number of a single type of argument. -data IsOptional - = Required -- 1, at the start - | Optional -- 0 or 1, at the end - | ZeroPlus -- 0 or more, at the end - | OnePlus -- 1 or more, at the end - deriving (Show, Eq) - data Visibility = Hidden | Visible deriving (Show, Eq, Ord) @@ -51,20 +49,20 @@ data Visibility = Hidden | Visible -- needs to be parsed or a numbered argument that doesn’t need to be parsed, as -- we’ve preserved its representation (although the numbered argument could -- still be of the wrong type, which should result in an error). -type Argument = Either String StructuredArgument +type Argument = Either Lexer.Lexeme StructuredArgument type Arguments = [Argument] -- | Argument description -- It should fit grammatically into sentences like "I was expecting an argument for the " -- e.g. "namespace to merge", "definition to delete", "remote target to push to" etc. -type ArgumentDescription = Text +type ParameterDescription = Text data InputPattern = InputPattern { patternName :: String, aliases :: [String], visibility :: Visibility, -- Allow hiding certain commands when debugging or work-in-progress - args :: [(ArgumentDescription, IsOptional, ArgumentType)], + params :: Parameters, help :: P.Pretty CT.ColorText, -- | Parse the arguments and return either an error message or a command `Input`. -- @@ -74,11 +72,14 @@ data InputPattern = InputPattern -- message, and shouldn’t plan for the context it is being output to (e.g., don’t `P.indentN` the entire -- message). parse :: + -- \| This list is always a valid length for the pattern. It may be necessary to have a catch-all case for + -- coverage, but the implementation can assume that, say, a `OnePlus` parameter will always be provided at least + -- one argument. Arguments -> Either (P.Pretty CT.ColorText) Input } -data ArgumentType = ArgumentType +data ParameterType = ParameterType { typeName :: String, -- | Generate completion suggestions for this argument type suggestions :: @@ -91,73 +92,124 @@ data ArgumentType = ArgumentType m [Line.Completion], -- | If an argument is marked as required, but not provided, the fuzzy finder will be triggered if -- available. - fzfResolver :: Maybe FZFResolver + fzfResolver :: Maybe FZFResolver, + isStructured :: Bool } -instance Show ArgumentType where - show at = "ArgumentType " <> typeName at +type Parameter = (ParameterDescription, ParameterType) + +data TrailingParameters + = -- | Optional args followed by a possibly-empty catch-all + Optional [Parameter] (Maybe Parameter) + | -- | A catch-all that requires at least one value + OnePlus Parameter + +-- | The `Parameters` for an `InputPattern` are roughly +-- +-- > [required …] ([optional …] [catchAll] | NonEmpty catchAll) +data Parameters = Parameters {requiredParams :: [Parameter], trailingParams :: TrailingParameters} + +-- | Aligns the pattern parameters with a set of concrete arguments. +-- +-- If too many arguments are provided, it returns the overflow arguments. In addition to the fold result, it returns +-- `Parameters` representing what can still be provided (e.g., via fuzzy completion). Note that if the result +-- `Parameters` has `OnePlus` or non-`null` `requiredArgs`, the application must fail unless more arguments are +-- provided somehow. +foldArgs :: (Parameter -> arg -> a -> a) -> a -> Parameters -> [arg] -> Either (NonEmpty arg) (Parameters, a) +foldArgs fn z Parameters {requiredParams, trailingParams} = foldRequiredArgs requiredParams + where + foldRequiredArgs = curry \case + ([], as) -> foldTrailingArgs as + (ps, []) -> pure (Parameters ps trailingParams, z) + (p : ps, a : as) -> fmap (fn p a) <$> foldRequiredArgs ps as + foldTrailingArgs = case trailingParams of + Optional optParams zeroPlus -> foldOptionalArgs zeroPlus optParams + OnePlus param -> foldOnePlusArgs param + foldOptionalArgs zp = curry \case + (ps, []) -> pure (Parameters [] $ Optional ps zp, z) + ([], a : as) -> foldZeroPlusArgs zp $ a :| as + (p : ps, a : as) -> fmap (fn p a) <$> foldOptionalArgs zp ps as + foldZeroPlusArgs = maybe Left (\p -> pure . (Parameters [] . Optional [] $ pure p,) . foldr (fn p) z) + foldOnePlusArgs p = \case + [] -> pure (Parameters [] $ OnePlus p, z) + args -> pure (Parameters [] . Optional [] $ pure p, foldr (fn p) z args) + +foldArgs' :: + forall a arg. + (a -> Parameter -> arg -> (a, [arg])) -> + a -> + Parameters -> + [arg] -> + Either (NonEmpty arg) (Parameters, a) +foldArgs' fn z Parameters {requiredParams, trailingParams} = foldRequiredArgs z requiredParams + where + foldRequiredArgs :: a -> [Parameter] -> [arg] -> Either (NonEmpty arg) (Parameters, a) + foldRequiredArgs res = curry \case + ([], as) -> foldTrailingArgs res as + (ps, []) -> pure (Parameters ps trailingParams, res) + (p : ps, a : as) -> + let (res', extraArgs) = fn res p a + in foldRequiredArgs res' ps $ extraArgs <> as + foldTrailingArgs :: a -> [arg] -> Either (NonEmpty arg) (Parameters, a) + foldTrailingArgs res = case trailingParams of + Optional optParams zeroPlus -> foldOptionalArgs res zeroPlus optParams + OnePlus param -> foldOnePlusArgs res param + foldOptionalArgs :: a -> Maybe Parameter -> [Parameter] -> [arg] -> Either (NonEmpty arg) (Parameters, a) + foldOptionalArgs res zp = curry \case + (ps, []) -> pure (Parameters [] $ Optional ps zp, res) + ([], a : as) -> foldZeroPlusArgs res zp $ a :| as + (p : ps, a : as) -> + let (res', extraArgs) = fn res p a + in foldOptionalArgs res' zp ps $ extraArgs <> as + foldZeroPlusArgs :: a -> Maybe Parameter -> NonEmpty arg -> Either (NonEmpty arg) (Parameters, a) + foldZeroPlusArgs res = maybe Left $ foldCatchallArg res + foldOnePlusArgs :: a -> Parameter -> [arg] -> Either (NonEmpty arg) (Parameters, a) + foldOnePlusArgs res p = \case + [] -> pure (Parameters [] $ OnePlus p, res) + a : args -> foldCatchallArg res p $ a :| args + foldCatchallArg :: a -> Parameter -> NonEmpty arg -> Either (NonEmpty arg) (Parameters, a) + foldCatchallArg res p = + let meh prevRes (a : args) = + let (res', extraArgs) = fn prevRes p a + in meh res' $ extraArgs <> args + meh prevRes [] = pure (Parameters [] . Optional [] $ pure p, prevRes) + in meh res . toList + +noParams :: Parameters +noParams = Parameters [] $ Optional [] Nothing -- `argType` gets called when the user tries to autocomplete an `i`th argument (zero-indexed). -- todo: would be nice if we could alert the user if they try to autocomplete -- past the end. It would also be nice if -argInfo :: InputPattern -> Int -> Maybe (ArgumentDescription, ArgumentType) -argInfo InputPattern {args, patternName} i = go (i, args) - where - -- Strategy: all of these input patterns take some number of arguments. - -- If it takes no arguments, then don't autocomplete. - go :: (Int, [(Text, IsOptional, ArgumentType)]) -> Maybe (ArgumentDescription, ArgumentType) - go (_, []) = Nothing - -- If requesting the 0th of >=1 arguments, return it. - go (0, (argName, _, t) : _) = Just (argName, t) - -- Vararg parameters should appear at the end of the arg list, and work for - -- any later argument number. - go (_, [(argName, ZeroPlus, t)]) = Just (argName, t) - go (_, [(argName, OnePlus, t)]) = Just (argName, t) - -- If requesting a later parameter, decrement and drop one. - go (n, (_argName, o, _) : argTypes) - | o == Optional || o == Required = go (n - 1, argTypes) - -- The argument list spec is invalid if something follows a vararg - go args = - error $ - "Input pattern " - <> show patternName - <> " has an invalid argument list: " - <> show args +paramInfo :: Parameters -> Int -> Maybe (ParameterDescription, ParameterType) +paramInfo Parameters {requiredParams, trailingParams} i = + if i < length requiredParams + then pure $ requiredParams !! i + else case trailingParams of + Optional optParams zeroPlus -> + let rem = i - length requiredParams + in if rem < length optParams + then pure $ optParams !! rem + else zeroPlus + OnePlus arg -> pure arg -- `argType` gets called when the user tries to autocomplete an `i`th argument (zero-indexed). -- todo: would be nice if we could alert the user if they try to autocomplete -- past the end. It would also be nice if -argType :: InputPattern -> Int -> Maybe ArgumentType -argType ip i = snd <$> (argInfo ip i) +paramType :: Parameters -> Int -> Maybe ParameterType +paramType p = fmap snd . paramInfo p -minArgs :: InputPattern -> Int -minArgs (InputPattern {args, patternName}) = - go (args ^.. folded . _2) - where - go [] = 0 - go (Required : argTypes) = 1 + go argTypes - go [_] = 0 - go _ = - error $ - "Invalid args for InputPattern (" - <> show patternName - <> "): " - <> show args - -maxArgs :: InputPattern -> Maybe Int -maxArgs (InputPattern {args, patternName}) = go argTypes - where - argTypes = args ^.. folded . _2 - go [] = Just 0 - go (Required : argTypes) = (1 +) <$> go argTypes - go [Optional] = Just 0 - go [_] = Nothing - go _ = - error $ - "Invalid args for InputPattern (" - <> show patternName - <> "): " - <> show argTypes +minArgs :: Parameters -> Int +minArgs Parameters {requiredParams, trailingParams} = + length requiredParams + case trailingParams of + Optional _ _ -> 0 + OnePlus _ -> 1 + +maxArgs :: Parameters -> Maybe Int +maxArgs Parameters {requiredParams, trailingParams} = + case trailingParams of + Optional optParams Nothing -> pure $ length requiredParams + length optParams + _ -> Nothing -- | Union suggestions from all possible completions unionSuggestions :: diff --git a/unison-cli/src/Unison/CommandLine/InputPatterns.hs b/unison-cli/src/Unison/CommandLine/InputPatterns.hs index 179c1d1567..cabe16874e 100644 --- a/unison-cli/src/Unison/CommandLine/InputPatterns.hs +++ b/unison-cli/src/Unison/CommandLine/InputPatterns.hs @@ -145,7 +145,6 @@ where import Control.Lens.Cons qualified as Cons import Data.Bitraversable (bitraverse) -import Data.Char (isSpace) import Data.List (intercalate) import Data.List.Extra qualified as List import Data.List.NonEmpty qualified as NE @@ -190,7 +189,6 @@ import Unison.Codebase.Editor.UriParser (readRemoteNamespaceParser) import Unison.Codebase.Editor.UriParser qualified as UriParser import Unison.Codebase.Path (Path, Path') import Unison.Codebase.Path qualified as Path -import Unison.Codebase.Path.Parse qualified as Path import Unison.Codebase.ProjectPath (ProjectPath) import Unison.Codebase.ProjectPath qualified as PP import Unison.Codebase.PushBehavior qualified as PushBehavior @@ -201,7 +199,14 @@ import Unison.CommandLine.BranchRelativePath qualified as BranchRelativePath import Unison.CommandLine.Completion import Unison.CommandLine.FZFResolvers qualified as Resolvers import Unison.CommandLine.Helpers (aside, backtick, tip) -import Unison.CommandLine.InputPattern (ArgumentType (..), InputPattern (InputPattern), IsOptional (..), unionSuggestions) +import Unison.CommandLine.InputPattern + ( InputPattern (InputPattern), + ParameterType (..), + Parameters (..), + TrailingParameters (..), + noParams, + unionSuggestions, + ) import Unison.CommandLine.InputPattern qualified as I import Unison.Core.Project (ProjectBranchName (..)) import Unison.HashQualified qualified as HQ @@ -227,8 +232,10 @@ import Unison.Server.Backend qualified as Backend import Unison.Server.SearchResult (SearchResult) import Unison.Server.SearchResult qualified as SR import Unison.ShortHash (ShortHash) -import Unison.Syntax.HashQualified qualified as HQ (parseText, toText) -import Unison.Syntax.Name qualified as Name (parseTextEither, toText) +import Unison.Syntax.HashQualified qualified as HQ (toText) +import Unison.Syntax.HashQualifiedPrime qualified as HQ' (toText) +import Unison.Syntax.Lexer.Unison qualified as Lexer +import Unison.Syntax.Name qualified as Name (toText) import Unison.Syntax.NameSegment qualified as NameSegment import Unison.Util.ColorText qualified as CT import Unison.Util.Monoid (intercalateMap) @@ -283,17 +290,17 @@ formatStructuredArgument schLength = \case -- | Converts an arbitrary argument to a `String`. This is for cases where the -- command /should/ accept a structured argument of some type, but currently -- wants a `String`. -unifyArgument :: I.Argument -> String -unifyArgument = either id (Text.unpack . formatStructuredArgument Nothing) +unifyArgument :: I.Argument -> Either (P.Pretty CT.ColorText) String +unifyArgument = either extractLexemeString (pure . Text.unpack . formatStructuredArgument Nothing) showPatternHelp :: InputPattern -> P.Pretty CT.ColorText showPatternHelp i = P.lines - [ P.bold (fromString $ I.patternName i) - <> fromString - ( if not . null $ I.aliases i - then " (or " <> intercalate ", " (I.aliases i) <> ")" - else "" + [ P.bold (patternName i) + <> P.string + ( if null $ I.aliases i + then "" + else " (or " <> intercalate ", " (I.aliases i) <> ")" ), I.help i ] @@ -317,7 +324,7 @@ searchResultToHQ oprefix = \case unsupportedStructuredArgument :: InputPattern -> Text -> I.Argument -> Either (P.Pretty CT.ColorText) String unsupportedStructuredArgument command expected = - either pure . const . Left . P.wrap $ + either extractLexemeString . const . Left . P.wrap $ makeExample' command <> "can’t accept a numbered argument for" <> P.text expected @@ -370,7 +377,7 @@ patternName = fromString . I.patternName makeExample, makeExampleNoBackticks :: InputPattern -> [P.Pretty CT.ColorText] -> P.Pretty CT.ColorText makeExample p args = P.group . backtick $ makeExampleNoBackticks p args makeExampleNoBackticks p args = - P.group $ intercalateMap " " id (P.nonEmpty $ fromString (I.patternName p) : args) + P.group $ intercalateMap " " id (P.nonEmpty $ patternName p : args) makeExample' :: InputPattern -> P.Pretty CT.ColorText makeExample' p = makeExample p [] @@ -378,7 +385,7 @@ makeExample' p = makeExample p [] makeExampleEOS :: InputPattern -> [P.Pretty CT.ColorText] -> P.Pretty CT.ColorText makeExampleEOS p args = P.group $ - backtick (intercalateMap " " id (P.nonEmpty $ fromString (I.patternName p) : args)) <> "." + backtick (intercalateMap " " id (P.nonEmpty $ patternName p : args)) <> "." helpFor :: InputPattern -> P.Pretty CT.ColorText helpFor = I.help @@ -386,7 +393,7 @@ helpFor = I.help handleProjectArg :: I.Argument -> Either (P.Pretty CT.ColorText) ProjectName handleProjectArg = either - (\name -> first (const $ expectedButActually' "a project" name) . tryInto @ProjectName $ Text.pack name) + ((\name -> first (const $ expectedButActually' "a project" name) . tryInto @ProjectName $ Text.pack name) <=< extractLexemeString) \case SA.Project project -> pure project otherArgType -> Left $ wrongStructuredArgument "a project" otherArgType @@ -395,7 +402,7 @@ handleMaybeProjectBranchArg :: I.Argument -> Either (P.Pretty CT.ColorText) (ProjectAndBranch (Maybe ProjectName) ProjectBranchName) handleMaybeProjectBranchArg = either - (megaparse branchWithOptionalProjectParser . Text.pack) + (megaparse branchWithOptionalProjectParser . Text.pack <=< extractLexemeString) \case SA.ProjectBranch pb -> pure pb otherArgType -> Left $ wrongStructuredArgument "a branch" otherArgType @@ -404,7 +411,7 @@ handleProjectMaybeBranchArg :: I.Argument -> Either (P.Pretty CT.ColorText) (ProjectAndBranch ProjectName (Maybe ProjectBranchNameOrLatestRelease)) handleProjectMaybeBranchArg = either - (\str -> first (const $ expectedButActually' "a project or branch" str) . tryInto $ Text.pack str) + ((\str -> first (const $ expectedButActually' "a project or branch" str) . tryInto $ Text.pack str) <=< extractLexemeString) \case SA.Project proj -> pure $ ProjectAndBranch proj Nothing SA.ProjectBranch (ProjectAndBranch (Just proj) branch) -> @@ -414,7 +421,7 @@ handleProjectMaybeBranchArg = handleHashQualifiedNameArg :: I.Argument -> Either (P.Pretty CT.ColorText) (HQ.HashQualified Name) handleHashQualifiedNameArg = either - parseHashQualifiedName + extractLexemeHQ \case SA.Name name -> pure $ HQ.NameOnly name SA.NameWithBranchPrefix mprefix name -> @@ -430,7 +437,7 @@ handleHashQualifiedNameArg = handlePathArg :: I.Argument -> Either (P.Pretty CT.ColorText) Path handlePathArg = either - (first P.text . Path.parsePath) + extractLexemePath \case SA.Name name -> pure $ Path.fromName name SA.NameWithBranchPrefix _ name -> pure $ Path.fromName name @@ -448,7 +455,7 @@ handlePathArg = handlePath'Arg :: I.Argument -> Either (P.Pretty CT.ColorText) Path' handlePath'Arg = either - (first P.text . Path.parsePath') + extractLexemePath' \case SA.AbsolutePath path -> pure $ Path.absoluteToPath' path SA.Name name -> pure $ Path.fromName' name @@ -459,21 +466,16 @@ handlePath'Arg = handleNewName :: I.Argument -> Either (P.Pretty CT.ColorText) Path.Split' handleNewName = - either - (first P.text . Path.parseSplit') - (const . Left $ "can’t use a numbered argument for a new name") + either extractLexemeSplit' (const . Left $ "can’t use a numbered argument for a new name") handleNewPath :: I.Argument -> Either (P.Pretty CT.ColorText) Path' -handleNewPath = - either - (first P.text . Path.parsePath') - (const . Left $ "can’t use a numbered argument for a new namespace") +handleNewPath = either extractLexemePath' (const . Left $ "can’t use a numbered argument for a new namespace") -- | When only a relative name is allowed. handleSplitArg :: I.Argument -> Either (P.Pretty CT.ColorText) Path.Split handleSplitArg = either - (first P.text . Path.parseSplit) + extractLexemeSplit \case SA.Name name | Name.isRelative name -> pure $ Path.splitFromName name SA.NameWithBranchPrefix _ name | Name.isRelative name -> pure $ Path.splitFromName name @@ -482,7 +484,7 @@ handleSplitArg = handleSplit'Arg :: I.Argument -> Either (P.Pretty CT.ColorText) Path.Split' handleSplit'Arg = either - (first P.text . Path.parseSplit') + extractLexemeSplit' \case SA.Name name -> pure $ Path.splitFromName' name SA.NameWithBranchPrefix (BranchAtSCH _) name -> pure $ Path.splitFromName' name @@ -493,7 +495,7 @@ handleSplit'Arg = handleProjectBranchNameArg :: I.Argument -> Either (P.Pretty CT.ColorText) ProjectBranchName handleProjectBranchNameArg = either - (first (const $ P.text "Wanted a branch name, but it wasn’t") . tryInto . Text.pack) + (first (const $ P.text "Wanted a branch name, but it wasn’t") . tryInto . Text.pack <=< extractLexemeString) \case SA.ProjectBranch (ProjectAndBranch _ branch) -> pure branch otherNumArg -> Left $ wrongStructuredArgument "a branch name" otherNumArg @@ -501,7 +503,7 @@ handleProjectBranchNameArg = handleBranchIdArg :: I.Argument -> Either (P.Pretty CT.ColorText) Input.BranchId handleBranchIdArg = either - (first P.text . Input.parseBranchId) + (first P.text . Input.parseBranchId <=< extractLexemeString) \case SA.AbsolutePath path -> pure . BranchAtPath $ Path.absoluteToPath' path SA.Name name -> pure . BranchAtPath $ Path.fromName' name @@ -523,7 +525,7 @@ _handleBranchIdOrProjectArg :: Either (P.Pretty CT.ColorText) (These Input.BranchId (ProjectAndBranch (Maybe ProjectName) ProjectBranchName)) _handleBranchIdOrProjectArg = either - (\str -> maybe (Left $ expectedButActually' "a branch" str) pure $ branchIdOrProject str) + ((\str -> maybe (Left $ expectedButActually' "a branch" str) pure $ branchIdOrProject str) <=< extractLexemeString) \case SA.Namespace hash -> pure . This . BranchAtSCH $ SCH.fromFullHash hash SA.AbsolutePath path -> pure . This . BranchAtPath $ Path.absoluteToPath' path @@ -555,7 +557,7 @@ _handleBranchIdOrProjectArg = handleBranchId2Arg :: I.Argument -> Either (P.Pretty P.ColorText) Input.BranchId2 handleBranchId2Arg = either - Input.parseBranchId2 + (Input.parseBranchId2 <=< extractLexemeString) \case SA.Namespace hash -> pure . Left $ SCH.fromFullHash hash SA.AbsolutePath path -> pure . pure . UnqualifiedPath $ Path.absoluteToPath' path @@ -572,7 +574,7 @@ handleBranchId2Arg = handleBranchRelativePathArg :: I.Argument -> Either (P.Pretty P.ColorText) BranchRelativePath handleBranchRelativePathArg = either - parseBranchRelativePath + (parseBranchRelativePath <=< extractLexemeString) \case SA.AbsolutePath path -> pure . UnqualifiedPath $ Path.absoluteToPath' path SA.Name name -> pure . UnqualifiedPath $ Path.fromName' name @@ -610,7 +612,7 @@ hq'NameToSplit = \case handleHashQualifiedSplit'Arg :: I.Argument -> Either (P.Pretty CT.ColorText) Path.HQSplit' handleHashQualifiedSplit'Arg = either - (first P.text . Path.parseHQSplit') + extractLexemeHQSplit' \case SA.Name name -> pure $ Path.hqSplitFromName' name hq@(SA.HashQualified name) -> first (const $ expectedButActually "a name" hq "a hash") $ hqNameToSplit' name @@ -626,7 +628,7 @@ handleHashQualifiedSplit'Arg = handleHashQualifiedSplitArg :: I.Argument -> Either (P.Pretty CT.ColorText) Path.HQSplit handleHashQualifiedSplitArg = either - (first P.text . Path.parseHQSplit) + extractLexemeHQSplit \case n@(SA.Name name) -> bitraverse @@ -648,7 +650,7 @@ handleHashQualifiedSplitArg = handleShortCausalHashArg :: I.Argument -> Either (P.Pretty CT.ColorText) ShortCausalHash handleShortCausalHashArg = either - (first (P.text . Text.pack) . Input.parseShortCausalHash) + (first (P.text . Text.pack) . Input.parseShortCausalHash <=< extractLexemeString) \case SA.Namespace hash -> pure $ SCH.fromFullHash hash otherNumArg -> Left $ wrongStructuredArgument "a causal hash" otherNumArg @@ -657,7 +659,7 @@ handleShortHashOrHQSplit'Arg :: I.Argument -> Either (P.Pretty CT.ColorText) (Either ShortHash Path.HQSplit') handleShortHashOrHQSplit'Arg = either - (first P.text . Path.parseShortHashOrHQSplit') + extractLexemeHashOrSplit' \case SA.HashQualified name -> pure $ hqNameToSplit' name SA.HashQualifiedWithBranchPrefix (BranchAtSCH _) hqname -> pure . pure $ hq'NameToSplit' hqname @@ -679,7 +681,7 @@ handleRelativeNameSegmentArg arg = do handleNameArg :: I.Argument -> Either (P.Pretty CT.ColorText) Name handleNameArg = either - (first P.text . Name.parseTextEither . Text.pack) + extractLexemeName \case SA.Name name -> pure name SA.NameWithBranchPrefix (BranchAtSCH _) name -> pure name @@ -701,7 +703,7 @@ handlePullSourceArg :: (ReadRemoteNamespace (These ProjectName ProjectBranchNameOrLatestRelease)) handlePullSourceArg = either - (megaparse (readRemoteNamespaceParser ProjectBranchSpecifier'NameOrLatestRelease) . Text.pack) + (megaparse (readRemoteNamespaceParser ProjectBranchSpecifier'NameOrLatestRelease) . Text.pack <=< extractLexemeString) \case SA.Project project -> pure . RemoteRepo.ReadShare'ProjectBranch $ This project SA.ProjectBranch (ProjectAndBranch project branch) -> @@ -713,7 +715,7 @@ handlePushTargetArg :: I.Argument -> Either (P.Pretty CT.ColorText) (These ProjectName ProjectBranchName) handlePushTargetArg = either - (\str -> maybe (Left $ expectedButActually' "a target to push to" str) pure $ parsePushTarget str) + ((\str -> maybe (Left $ expectedButActually' "a target to push to" str) pure $ parsePushTarget str) <=< extractLexemeString) $ \case SA.Project project -> pure $ This project SA.ProjectBranch (ProjectAndBranch project branch) -> pure $ maybe That These project branch @@ -722,7 +724,7 @@ handlePushTargetArg = handlePushSourceArg :: I.Argument -> Either (P.Pretty CT.ColorText) Input.PushSource handlePushSourceArg = either - (\str -> maybe (Left $ expectedButActually' "a source to push from" str) pure $ parsePushSource str) + ((\str -> maybe (Left $ expectedButActually' "a source to push from" str) pure $ parsePushSource str) <=< extractLexemeString) \case SA.Project project -> pure . Input.ProjySource $ This project SA.ProjectBranch (ProjectAndBranch project branch) -> pure . Input.ProjySource $ maybe That These project branch @@ -731,19 +733,72 @@ handlePushSourceArg = handleProjectAndBranchNamesArg :: I.Argument -> Either (P.Pretty CT.ColorText) ProjectAndBranchNames handleProjectAndBranchNamesArg = either - (\str -> first (const $ expectedButActually' "a project or branch" str) . tryInto @ProjectAndBranchNames $ Text.pack str) + ((\str -> first (const $ expectedButActually' "a project or branch" str) . tryInto @ProjectAndBranchNames $ Text.pack str) <=< extractLexemeString) $ fmap ProjectAndBranchNames'Unambiguous . \case SA.Project project -> pure $ This project SA.ProjectBranch (ProjectAndBranch mproj branch) -> pure $ maybe That These mproj branch otherNumArg -> Left $ wrongStructuredArgument "a project or branch" otherNumArg +extractLexemeHQ' :: Lexer.Lexeme -> Either (P.Pretty CT.ColorText) (HQ'.HashQualified Name) +extractLexemeHQ' = \case + Lexer.SymbolyId hq -> pure hq + Lexer.WordyId hq -> pure hq + lexeme -> Left $ "Was expecting a name, but received " <> P.string (show lexeme) + +extractLexemeHQ :: Lexer.Lexeme -> Either (P.Pretty CT.ColorText) (HQ.HashQualified Name) +extractLexemeHQ = \case + Lexer.Hash hash -> pure $ HQ.HashOnly hash + lexeme -> + bimap (const $ "Was expecting a name or hash, but received " <> P.string (show lexeme)) HQ'.toHQ $ + extractLexemeHQ' lexeme + +extractLexemeName :: Lexer.Lexeme -> Either (P.Pretty CT.ColorText) Name +extractLexemeName = fmap HQ'.toName . extractLexemeHQ' + +extractLexemePath' :: Lexer.Lexeme -> Either (P.Pretty CT.ColorText) Path' +extractLexemePath' = \case + Lexer.Reserved "." -> pure Path.absoluteEmpty' + lexeme -> + bimap (const $ "Was expecting a path, but received " <> P.string (show lexeme)) Path.fromName' $ + extractLexemeName lexeme + +extractLexemePath :: Lexer.Lexeme -> Either (P.Pretty CT.ColorText) Path +extractLexemePath = fmap Path.fromName . extractLexemeName + +extractLexemeSplit :: Lexer.Lexeme -> Either (P.Pretty CT.ColorText) Path.Split +extractLexemeSplit = fmap Path.splitFromName . extractLexemeName + +extractLexemeSplit' :: Lexer.Lexeme -> Either (P.Pretty CT.ColorText) Path.Split' +extractLexemeSplit' = fmap Path.splitFromName' . extractLexemeName + +extractLexemeHQSplit' :: Lexer.Lexeme -> Either (P.Pretty CT.ColorText) Path.HQSplit' +extractLexemeHQSplit' = fmap hq'NameToSplit' . extractLexemeHQ' + +extractLexemeHQSplit :: Lexer.Lexeme -> Either (P.Pretty CT.ColorText) Path.HQSplit +extractLexemeHQSplit = fmap hq'NameToSplit . extractLexemeHQ' + +extractLexemeHashOrSplit' :: Lexer.Lexeme -> Either (P.Pretty CT.ColorText) (Either ShortHash Path.HQSplit') +extractLexemeHashOrSplit' = \case + Lexer.Hash hash -> pure $ Left hash + lex -> pure <$> extractLexemeHQSplit' lex + +extractLexemeString :: Lexer.Lexeme -> Either (P.Pretty CT.ColorText) String +extractLexemeString = \case + Lexer.Character char -> pure $ pure char + Lexer.Numeric str -> pure str + Lexer.Reserved str -> pure str + Lexer.SymbolyId hq -> pure . Text.unpack $ HQ'.toText hq + Lexer.Textual str -> pure str + Lexer.WordyId hq -> pure . Text.unpack $ HQ'.toText hq + eme -> Left $ "Was expecting a string, but received " <> P.string (show eme) + mergeBuiltins :: InputPattern mergeBuiltins = InputPattern "builtins.merge" [] I.Hidden - [("namespace", Optional, namespaceArg)] + (Parameters [] $ Optional [("namespace", namespaceArg)] Nothing) "Adds the builtins (excluding `io` and misc) to the specified namespace. Defaults to `builtin.`" \case [] -> pure . Input.MergeBuiltinsI $ Nothing @@ -756,7 +811,7 @@ mergeIOBuiltins = "builtins.mergeio" [] I.Hidden - [("namespace", Optional, namespaceArg)] + (Parameters [] $ Optional [("namespace", namespaceArg)] Nothing) "Adds all the builtins, including `io` and misc., to the specified namespace. Defaults to `builtin.`" \case [] -> pure . Input.MergeIOBuiltinsI $ Nothing @@ -769,11 +824,12 @@ updateBuiltins = "builtins.update" [] I.Hidden - [] + noParams ( "Adds all the builtins that are missing from this namespace, " <> "and deprecate the ones that don't exist in this version of Unison." ) - (const . pure $ Input.UpdateBuiltinsI) + . const + $ pure Input.UpdateBuiltinsI todo :: InputPattern todo = @@ -781,15 +837,14 @@ todo = "todo" [] I.Visible - [] + noParams ( P.wrap $ makeExample' todo <> "lists the current namespace's outstanding issues, including conflicted names, dependencies with missing" <> "names, and merge precondition violations." ) - \case - [] -> Right Input.TodoI - args -> wrongArgsLength "no arguments" args + . const + $ pure Input.TodoI load :: InputPattern load = @@ -797,7 +852,7 @@ load = "load" [] I.Visible - [("scratch file", Optional, filePathArg)] + (Parameters [] $ Optional [("scratch file", filePathArg)] Nothing) ( P.wrapColumn2 [ ( makeExample' load, "parses, typechecks, and evaluates the most recent scratch file." @@ -818,16 +873,15 @@ clear = "clear" [] I.Visible - [] + noParams ( P.wrapColumn2 [ ( makeExample' clear, "Clears the screen." ) ] ) - \case - [] -> pure Input.ClearI - args -> wrongArgsLength "no arguments" args + . const + $ pure Input.ClearI add :: InputPattern add = @@ -835,7 +889,7 @@ add = "add" [] I.Visible - [("definition", ZeroPlus, noCompletionsArg)] + (Parameters [] . Optional [] $ Just ("definition", exactDefinitionArg)) ( "`add` adds to the codebase all the definitions from the most recently " <> "typechecked file." ) @@ -847,7 +901,7 @@ previewAdd = "add.preview" [] I.Visible - [("definition", ZeroPlus, noCompletionsArg)] + (Parameters [] . Optional [] $ Just ("definition", exactDefinitionArg)) ( "`add.preview` previews additions to the codebase from the most recently " <> "typechecked file. This command only displays cached typechecking " <> "results. Use `load` to reparse & typecheck the file if the context " @@ -861,16 +915,14 @@ update = { patternName = "update", aliases = [], visibility = I.Visible, - args = [], + params = noParams, help = P.wrap $ "Adds everything in the most recently typechecked file to the namespace," <> "replacing existing definitions having the same name, and attempts to update all the existing dependents accordingly. If the process" <> "can't be completed automatically, the dependents will be added back to the scratch file" <> "for your review.", - parse = \case - [] -> pure Input.Update2I - args -> wrongArgsLength "no arguments" args + parse = const $ pure Input.Update2I } updateOldNoPatch :: InputPattern @@ -879,7 +931,7 @@ updateOldNoPatch = "update.old.nopatch" [] I.Visible - [("definition", ZeroPlus, noCompletionsArg)] + (Parameters [] . Optional [] $ Just ("definition", exactDefinitionArg)) ( P.wrap ( makeExample' updateOldNoPatch <> "works like" @@ -907,7 +959,7 @@ updateOld = "update.old" [] I.Visible - [("patch", Optional, patchArg), ("definition", ZeroPlus, noCompletionsArg)] + (Parameters [] . Optional [("patch", patchArg)] $ Just ("definition", exactDefinitionArg)) ( P.wrap ( makeExample' updateOld <> "works like" @@ -944,7 +996,7 @@ previewUpdate = "update.old.preview" [] I.Visible - [("definition", ZeroPlus, noCompletionsArg)] + (Parameters [] . Optional [] $ Just ("definition", exactDefinitionArg)) ( "`update.old.preview` previews updates to the codebase from the most " <> "recently typechecked file. This command only displays cached " <> "typechecking results. Use `load` to reparse & typecheck the file if " @@ -958,7 +1010,7 @@ view = "view" [] I.Visible - [("definition to view", OnePlus, definitionQueryArg)] + (Parameters [] $ OnePlus ("definition to view", definitionQueryArg)) ( P.lines [ P.wrap $ makeExample view ["foo"] <> "shows definitions named `foo` within your current namespace.", P.wrap $ makeExample view [] <> "without arguments invokes a search to select definitions to view, which requires that `fzf` can be found within your PATH.", @@ -984,7 +1036,7 @@ viewGlobal = "view.global" [] I.Visible - [("definition to view", ZeroPlus, definitionQueryArg)] + (Parameters [] . Optional [] $ Just ("definition to view", definitionQueryArg)) ( P.lines [ "`view.global foo` prints definitions of `foo` within your codebase.", "`view.global` without arguments invokes a search to select definitions to view, which requires that `fzf` can be found within your PATH." @@ -1004,7 +1056,7 @@ display = "display" [] I.Visible - [("definition to display", OnePlus, definitionQueryArg)] + (Parameters [] $ OnePlus ("definition to display", definitionQueryArg)) ( P.lines [ "`display foo` prints a rendered version of the term `foo`.", "`display` without arguments invokes a search to select a definition to display, which requires that `fzf` can be found within your PATH." @@ -1021,22 +1073,17 @@ displayTo = "display.to" [] I.Visible - [("destination file name", Required, filePathArg), ("definition to display", OnePlus, definitionQueryArg)] + (Parameters [("destination file name", filePathArg)] $ OnePlus ("definition to display", definitionQueryArg)) ( P.wrap $ makeExample displayTo ["", "foo"] <> "prints a rendered version of the term `foo` to the given file." ) $ \case - file : defs -> - maybe - (wrongArgsLength "at least two arguments" [file]) - ( \defs -> do - file <- unsupportedStructuredArgument displayTo "a file name" file - names <- traverse handleHashQualifiedNameArg defs - pure (Input.DisplayI (Input.FileLocation file Input.AboveFold) names) - ) - $ NE.nonEmpty defs - [] -> wrongArgsLength "at least two arguments" [] + file : def : defs -> do + file <- unsupportedStructuredArgument displayTo "a file name" file + names <- traverse handleHashQualifiedNameArg $ def NE.:| defs + pure (Input.DisplayI (Input.FileLocation file Input.AboveFold) names) + args -> wrongArgsLength "at least two arguments" args docs :: InputPattern docs = @@ -1044,7 +1091,7 @@ docs = "docs" [] I.Visible - [("definition", OnePlus, definitionQueryArg)] + (Parameters [] $ OnePlus ("definition", definitionQueryArg)) ( P.lines [ "`docs foo` shows documentation for the definition `foo`.", "`docs` without arguments invokes a search to select which definition to view documentation for, which requires that `fzf` can be found within your PATH." @@ -1058,9 +1105,10 @@ api = "api" [] I.Visible - [] + noParams "`api` provides details about the API." - (const $ pure Input.ApiI) + . const + $ pure Input.ApiI ui :: InputPattern ui = @@ -1068,7 +1116,7 @@ ui = { patternName = "ui", aliases = [], visibility = I.Visible, - args = [("definition to load", Optional, namespaceOrDefinitionArg)], + params = Parameters [] $ Optional [("definition to load", namespaceOrDefinitionArg)] Nothing, help = P.wrap "`ui` opens the Local UI in the default browser.", parse = \case [] -> pure $ Input.UiI Path.relativeEmpty' @@ -1082,21 +1130,20 @@ undo = "undo" [] I.Visible - [] + noParams "`undo` reverts the most recent change to the codebase." - (const $ pure Input.UndoI) + . const + $ pure Input.UndoI textfind :: Bool -> InputPattern textfind allowLib = - InputPattern cmdName aliases I.Visible [("token", OnePlus, noCompletionsArg)] msg parse + InputPattern cmdName aliases I.Visible (Parameters [] $ OnePlus ("token", noCompletionsArg)) msg parse where (cmdName, aliases, alternate) = if allowLib then ("text.find.all", ["grep.all"], "Use `text.find` to exclude `lib` from search.") else ("text.find", ["grep"], "Use `text.find.all` to include search of `lib`.") - parse = \case - [] -> Left (P.text "Please supply at least one token.") - words -> pure $ Input.TextFindI allowLib (untokenize $ [e | Left e <- words]) + parse = fmap (Input.TextFindI allowLib) . traverse (unsupportedStructuredArgument (textfind allowLib) "text") msg = P.lines [ P.wrap $ @@ -1111,24 +1158,15 @@ textfind allowLib = P.wrap alternate ] --- | Reinterprets `"` in the expected way, combining tokens until reaching --- the closing quote. --- Example: `untokenize ["\"uno", "dos\""]` becomes `["uno dos"]`. -untokenize :: [String] -> [String] -untokenize words = go (unwords words) - where - go words = case words of - [] -> [] - '"' : quoted -> takeWhile (/= '"') quoted : go (drop 1 . dropWhile (/= '"') $ quoted) - unquoted -> case span ok unquoted of - ("", rem) -> go (dropWhile isSpace rem) - (tok, rem) -> tok : go (dropWhile isSpace rem) - where - ok ch = ch /= '"' && not (isSpace ch) - sfind :: InputPattern sfind = - InputPattern "rewrite.find" ["sfind"] I.Visible [("rewrite-rule definition", Required, definitionQueryArg)] msg parse + InputPattern + "rewrite.find" + ["sfind"] + I.Visible + (Parameters [("rewrite-rule definition", definitionQueryArg)] $ Optional [] Nothing) + msg + parse where parse = \case [q] -> Input.StructuredFindI (Input.FindLocal Path.relativeEmpty') <$> handleHashQualifiedNameArg q @@ -1160,7 +1198,13 @@ sfind = sfindReplace :: InputPattern sfindReplace = - InputPattern "rewrite" ["sfind.replace"] I.Visible [("rewrite-rule definition", Required, definitionQueryArg)] msg parse + InputPattern + "rewrite" + ["sfind.replace"] + I.Visible + (Parameters [("rewrite-rule definition", definitionQueryArg)] $ Optional [] Nothing) + msg + parse where parse [q] = Input.StructuredFindReplaceI <$> handleHashQualifiedNameArg q parse args = wrongArgsLength "exactly one argument" args @@ -1207,10 +1251,10 @@ findIn' cmd mkfscope = cmd [] I.Visible - [("namespace", Required, namespaceArg), ("query", ZeroPlus, exactDefinitionArg)] + (Parameters [("namespace", namespaceArg)] . Optional [] $ Just ("query", exactDefinitionArg)) findHelp \case - p : args -> Input.FindI False . mkfscope <$> handlePath'Arg p <*> pure (unifyArgument <$> args) + p : args -> Input.FindI False . mkfscope <$> handlePath'Arg p <*> traverse unifyArgument args args -> wrongArgsLength "at least one argument" args findHelp :: P.Pretty CT.ColorText @@ -1255,9 +1299,9 @@ find' cmd fscope = cmd [] I.Visible - [("query", ZeroPlus, exactDefinitionArg)] + (Parameters [] . Optional [] $ Just ("query", exactDefinitionArg)) findHelp - (pure . Input.FindI False fscope . fmap unifyArgument) + $ fmap (Input.FindI False fscope) . traverse unifyArgument findShallow :: InputPattern findShallow = @@ -1265,7 +1309,7 @@ findShallow = "list" ["ls", "dir"] I.Visible - [("namespace", Optional, namespaceArg)] + (Parameters [] $ Optional [("namespace", namespaceArg)] Nothing) ( P.wrapColumn2 [ ("`list`", "lists definitions and namespaces at the current level of the current namespace."), ("`list foo`", "lists the 'foo' namespace."), @@ -1284,11 +1328,11 @@ findVerbose = "find.verbose" [] I.Visible - [("query", ZeroPlus, exactDefinitionArg)] + (Parameters [] . Optional [] $ Just ("query", exactDefinitionArg)) ( "`find.verbose` searches for definitions like `find`, but includes hashes " <> "and aliases in the results." ) - (pure . Input.FindI True (Input.FindLocal Path.relativeEmpty') . fmap unifyArgument) + $ fmap (Input.FindI True $ Input.FindLocal Path.relativeEmpty') . traverse unifyArgument findVerboseAll :: InputPattern findVerboseAll = @@ -1296,11 +1340,11 @@ findVerboseAll = "find.all.verbose" [] I.Visible - [("query", ZeroPlus, exactDefinitionArg)] + (Parameters [] . Optional [] $ Just ("query", exactDefinitionArg)) ( "`find.all.verbose` searches for definitions like `find.all`, but includes hashes " <> "and aliases in the results." ) - (pure . Input.FindI True (Input.FindLocalAndDeps Path.relativeEmpty') . fmap unifyArgument) + $ fmap (Input.FindI True $ Input.FindLocalAndDeps Path.relativeEmpty') . traverse unifyArgument renameTerm :: InputPattern renameTerm = @@ -1308,9 +1352,9 @@ renameTerm = "move.term" ["rename.term"] I.Visible - [ ("definition to move", Required, exactDefinitionTermQueryArg), - ("new location", Required, newNameArg) - ] + ( Parameters [("definition to move", exactDefinitionTermQueryArg), ("new location", newNameArg)] $ + Optional [] Nothing + ) "`move.term foo bar` renames `foo` to `bar`." \case [oldName, newName] -> Input.MoveTermI <$> handleHashQualifiedSplit'Arg oldName <*> handleNewName newName @@ -1322,9 +1366,7 @@ moveAll = "move" ["rename"] I.Visible - [ ("definition to move", Required, namespaceOrDefinitionArg), - ("new location", Required, newNameArg) - ] + (Parameters [("definition to move", namespaceOrDefinitionArg), ("new location", newNameArg)] $ Optional [] Nothing) "`move foo bar` renames the term, type, and namespace foo to bar." \case [oldName, newName] -> Input.MoveAllI <$> handlePath'Arg oldName <*> handleNewPath newName @@ -1336,16 +1378,14 @@ renameType = "move.type" ["rename.type"] I.Visible - [ ("type to move", Required, exactDefinitionTypeQueryArg), - ("new location", Required, newNameArg) - ] + (Parameters [("type to move", exactDefinitionTypeQueryArg), ("new location", newNameArg)] $ Optional [] Nothing) "`move.type foo bar` renames `foo` to `bar`." \case [oldName, newName] -> Input.MoveTypeI <$> handleHashQualifiedSplit'Arg oldName <*> handleNewName newName _ -> Left $ P.wrap "`rename.type` takes two arguments, like `rename.type oldname newname`." -deleteGen :: Maybe String -> ArgumentType -> String -> ([Path.HQSplit'] -> DeleteTarget) -> InputPattern +deleteGen :: Maybe String -> ParameterType -> String -> ([Path.HQSplit'] -> DeleteTarget) -> InputPattern deleteGen suffix queryCompletionArg target mkTarget = let cmd = maybe "delete" ("delete." <>) suffix info = @@ -1380,7 +1420,7 @@ deleteGen suffix queryCompletionArg target mkTarget = cmd [] I.Visible - [("definition to delete", OnePlus, queryCompletionArg)] + (Parameters [] $ OnePlus ("definition to delete", queryCompletionArg)) info \case [] -> Left $ P.wrap warning @@ -1410,7 +1450,7 @@ deleteProject = { patternName = "delete.project", aliases = ["project.delete"], visibility = I.Visible, - args = [("project to delete", Required, projectNameArg)], + params = Parameters [("project to delete", projectNameArg)] $ Optional [] Nothing, help = P.wrapColumn2 [ ("`delete.project foo`", "deletes the local project `foo`") @@ -1426,7 +1466,7 @@ deleteBranch = { patternName = "delete.branch", aliases = ["branch.delete"], visibility = I.Visible, - args = [("branch to delete", Required, projectBranchNameArg suggestionsConfig)], + params = Parameters [("branch to delete", projectBranchNameArg suggestionsConfig)] $ Optional [] Nothing, help = P.wrapColumn2 [ ("`delete.branch foo/bar`", "deletes the branch `bar` in the project `foo`"), @@ -1450,7 +1490,8 @@ aliasTerm = { patternName = "alias.term", aliases = [], visibility = I.Visible, - args = [("term to alias", Required, exactDefinitionTermQueryArg), ("alias name", Required, newNameArg)], + params = + Parameters [("term to alias", exactDefinitionTermQueryArg), ("alias name", newNameArg)] $ Optional [] Nothing, help = "`alias.term foo bar` introduces `bar` with the same definition as `foo`.", parse = \case [oldName, newName] -> Input.AliasTermI False <$> handleShortHashOrHQSplit'Arg oldName <*> handleSplit'Arg newName @@ -1463,7 +1504,8 @@ debugAliasTermForce = { patternName = "debug.alias.term.force", aliases = [], visibility = I.Hidden, - args = [("term to alias", Required, exactDefinitionTermQueryArg), ("alias name", Required, newNameArg)], + params = + Parameters [("term to alias", exactDefinitionTermQueryArg), ("alias name", newNameArg)] $ Optional [] Nothing, help = "`debug.alias.term.force foo bar` introduces `bar` with the same definition as `foo`.", parse = \case [oldName, newName] -> Input.AliasTermI True <$> handleShortHashOrHQSplit'Arg oldName <*> handleSplit'Arg newName @@ -1478,7 +1520,7 @@ aliasType = "alias.type" [] I.Visible - [("type to alias", Required, exactDefinitionTypeQueryArg), ("alias name", Required, newNameArg)] + (Parameters [("type to alias", exactDefinitionTypeQueryArg), ("alias name", newNameArg)] $ Optional [] Nothing) "`alias.type Foo Bar` introduces `Bar` with the same definition as `Foo`." \case [oldName, newName] -> Input.AliasTypeI False <$> handleShortHashOrHQSplit'Arg oldName <*> handleSplit'Arg newName @@ -1490,7 +1532,8 @@ debugAliasTypeForce = { patternName = "debug.alias.type.force", aliases = [], visibility = I.Hidden, - args = [("type to alias", Required, exactDefinitionTypeQueryArg), ("alias name", Required, newNameArg)], + params = + Parameters [("type to alias", exactDefinitionTypeQueryArg), ("alias name", newNameArg)] $ Optional [] Nothing, help = "`debug.alias.type.force Foo Bar` introduces `Bar` with the same definition as `Foo`.", parse = \case [oldName, newName] -> Input.AliasTypeI True <$> handleShortHashOrHQSplit'Arg oldName <*> handleSplit'Arg newName @@ -1505,7 +1548,7 @@ aliasMany = "alias.many" ["copy"] I.Visible - [("definition to alias", Required, definitionQueryArg), ("alias names", OnePlus, exactDefinitionArg)] + (Parameters [("definition to alias", definitionQueryArg)] $ OnePlus ("alias names", exactDefinitionArg)) ( P.group . P.lines $ [ P.wrap $ P.group (makeExample aliasMany ["", "[relative2...]", ""]) @@ -1526,11 +1569,10 @@ up = "deprecated.up" [] I.Hidden - [] + noParams (P.wrapColumn2 [(makeExample up [], "move current path up one level (deprecated)")]) - \case - [] -> Right Input.UpI - args -> wrongArgsLength "no arguments" args + . const + $ pure Input.UpI cd :: InputPattern cd = @@ -1538,7 +1580,7 @@ cd = "deprecated.cd" ["deprecated.namespace"] I.Visible - [("namespace", Required, namespaceArg)] + (Parameters [("namespace", namespaceArg)] $ Optional [] Nothing) ( P.lines [ "Moves your perspective to a different namespace. Deprecated for now because too many important things depend on your perspective selection.", "", @@ -1559,7 +1601,8 @@ cd = ] ) \case - [Left ".."] -> Right Input.UpI + -- FIXME: Test this – ideal if it doesn’t need to be quoted + [Left (Lexer.Textual "..")] -> Right Input.UpI [p] -> Input.SwitchBranchI <$> handlePath'Arg p args -> wrongArgsLength "exactly one argument" args @@ -1569,16 +1612,15 @@ back = "back" ["popd"] I.Visible - [] + noParams ( P.wrapColumn2 [ ( makeExample back [], "undoes the last" <> makeExample' projectSwitch <> "command." ) ] ) - \case - [] -> pure Input.PopBranchI - args -> wrongArgsLength "no arguments" args + . const + $ pure Input.PopBranchI deleteNamespace :: InputPattern deleteNamespace = @@ -1586,7 +1628,7 @@ deleteNamespace = "delete.namespace" [] I.Visible - [("namespace to delete", Required, namespaceArg)] + (Parameters [("namespace to delete", namespaceArg)] $ Optional [] Nothing) "`delete.namespace ` deletes the namespace `foo`" (deleteNamespaceParser Input.Try) @@ -1596,7 +1638,7 @@ deleteNamespaceForce = "delete.namespace.force" [] I.Visible - [("namespace to delete", Required, namespaceArg)] + (Parameters [("namespace to delete", namespaceArg)] $ Optional [] Nothing) ( "`delete.namespace.force ` deletes the namespace `foo`," <> "deletion will proceed even if other code depends on definitions in foo." ) @@ -1604,7 +1646,7 @@ deleteNamespaceForce = deleteNamespaceParser :: Input.Insistence -> I.Arguments -> Either (P.Pretty CT.ColorText) Input deleteNamespaceParser insistence = \case - [Left "."] -> first fromString . pure $ Input.DeleteI (DeleteTarget'Namespace insistence Nothing) + [Left (Lexer.Reserved ".")] -> pure $ Input.DeleteI (DeleteTarget'Namespace insistence Nothing) [p] -> Input.DeleteI . DeleteTarget'Namespace insistence <$> (Just <$> handleSplitArg p) args -> wrongArgsLength "exactly one argument" args @@ -1614,7 +1656,7 @@ renameBranch = "move.namespace" ["rename.namespace"] I.Visible - [("namespace to move", Required, namespaceArg), ("new location", Required, newNameArg)] + (Parameters [("namespace to move", namespaceArg), ("new location", newNameArg)] $ Optional [] Nothing) "`move.namespace foo bar` renames the path `foo` to `bar`." \case [src, dest] -> Input.MoveBranchI <$> handlePath'Arg src <*> handlePath'Arg dest @@ -1626,7 +1668,7 @@ history = "history" [] I.Visible - [("namespace", Optional, namespaceArg)] + (Parameters [] $ Optional [("namespace", namespaceArg)] Nothing) ( P.wrapColumn2 [ (makeExample history [], "Shows the history of the current path."), (makeExample history [".foo"], "Shows history of the path .foo."), @@ -1647,9 +1689,9 @@ forkLocal = "fork" ["copy.namespace"] I.Visible - [ ("source location", Required, branchRelativePathArg), - ("dest location", Required, branchRelativePathArg) - ] + ( Parameters [("source location", branchRelativePathArg), ("dest location", branchRelativePathArg)] $ + Optional [] Nothing + ) ( P.wrapColumn2 [ ( makeExample forkLocal ["src", "dest"], "creates the namespace `dest` as a copy of `src`." @@ -1672,7 +1714,7 @@ libInstallInputPattern = { patternName = "lib.install", aliases = ["install.lib"], visibility = I.Visible, - args = [], + params = Parameters [("library name", noCompletionsArg)] $ Optional [] Nothing, help = P.lines [ P.wrap $ @@ -1703,9 +1745,9 @@ reset = "reset" [] I.Visible - [ ("namespace, hash, or branch to reset to", Required, namespaceOrProjectBranchArg config), - ("namespace to be reset", Optional, namespaceOrProjectBranchArg config) - ] + ( Parameters [("namespace, hash, or branch to reset to", namespaceOrProjectBranchArg config)] $ + Optional [("namespace to be reset", namespaceOrProjectBranchArg config)] Nothing + ) ( P.lines [ P.wrapColumn2 [ ("`reset #pvfd222s8n`", "reset the current namespace to the hash `#pvfd222s8n`"), @@ -1749,18 +1791,20 @@ pullImpl name aliases pullMode addendum = do { patternName = name, aliases = aliases, visibility = I.Visible, - args = - [ ("remote namespace to pull", Optional, remoteNamespaceArg), - ( "destination branch", - Optional, - projectBranchNameArg - ProjectBranchSuggestionsConfig - { showProjectCompletions = False, - projectInclusion = AllProjects, - branchInclusion = AllBranches - } - ) - ], + params = + Parameters [] $ + Optional + [ ("remote namespace to pull", remoteNamespaceArg), + ( "destination branch", + projectBranchNameArg + ProjectBranchSuggestionsConfig + { showProjectCompletions = False, + projectInclusion = AllProjects, + branchInclusion = AllBranches + } + ) + ] + Nothing, help = P.lines [ P.wrap $ @@ -1851,7 +1895,7 @@ debugTabCompletion = "debug.tab-complete" [] I.Hidden - [("command arguments", ZeroPlus, noCompletionsArg)] + (Parameters [] . Optional [] $ Just ("command arguments", noCompletionsArg)) ( P.lines [ P.wrap $ "This command can be used to test and debug ucm's tab-completion within transcripts.", P.wrap $ "Completions which are finished are prefixed with a * represent finished completions." @@ -1865,7 +1909,7 @@ debugLspNameCompletion = "debug.lsp-name-completion" [] I.Hidden - [("Completion prefix", OnePlus, noCompletionsArg)] + (Parameters [] $ OnePlus ("Completion prefix", noCompletionsArg)) ( P.lines [ P.wrap $ "This command can be used to test and debug ucm's LSP name-completion within transcripts." ] @@ -1880,7 +1924,7 @@ debugFuzzyOptions = "debug.fuzzy-options" [] I.Hidden - [("command arguments", OnePlus, noCompletionsArg)] + (Parameters [("command", commandNameArg)] . Optional [] $ Just ("arguments", noCompletionsArg)) ( P.lines [ P.wrap $ "This command can be used to test and debug ucm's fuzzy-options within transcripts.", P.wrap $ "Write a command invocation with _ for any args you'd like to see completion options for.", @@ -1890,9 +1934,9 @@ debugFuzzyOptions = ] ) \case - (cmd : args) -> + cmd : args -> Input.DebugFuzzyOptionsI - <$> unsupportedStructuredArgument debugFuzzyOptions "a command" cmd + <$> unifyArgument cmd <*> traverse (unsupportedStructuredArgument debugFuzzyOptions "text") args args -> wrongArgsLength "at least one argument" args @@ -1902,7 +1946,7 @@ debugFormat = "debug.format" [] I.Hidden - [("source-file", Optional, filePathArg)] + (Parameters [] $ Optional [("source-file", filePathArg)] Nothing) ( P.lines [ P.wrap $ "This command can be used to test ucm's file formatter on the latest typechecked file.", makeExample' debugFormat @@ -1919,7 +1963,11 @@ push = "push" [] I.Visible - [("remote destination", Optional, remoteNamespaceArg), ("local target", Optional, namespaceOrProjectBranchArg suggestionsConfig)] + ( Parameters [] $ + Optional + [("remote destination", remoteNamespaceArg), ("local target", namespaceOrProjectBranchArg suggestionsConfig)] + Nothing + ) ( P.lines [ P.wrap "The `push` command merges a local project or namespace into a remote project or namespace.", @@ -1972,10 +2020,13 @@ pushCreate = "push.create" [] I.Visible - [("remote destination", Optional, remoteNamespaceArg), ("local target", Optional, namespaceOrProjectBranchArg suggestionsConfig)] + ( Parameters [] $ + Optional + [("remote destination", remoteNamespaceArg), ("local target", namespaceOrProjectBranchArg suggestionsConfig)] + Nothing + ) ( P.lines - [ P.wrap - "The `push.create` command pushes a local namespace to an empty remote namespace.", + [ P.wrap "The `push.create` command pushes a local namespace to an empty remote namespace.", "", P.wrapColumn2 [ ( "`push.create remote local`", @@ -2023,7 +2074,11 @@ pushForce = "unsafe.force-push" ["push.unsafe-force"] I.Visible - [("remote destination", Optional, remoteNamespaceArg), ("local source", Optional, namespaceOrProjectBranchArg suggestionsConfig)] + ( Parameters [] $ + Optional + [("remote destination", remoteNamespaceArg), ("local source", namespaceOrProjectBranchArg suggestionsConfig)] + Nothing + ) (P.wrap "Like `push`, but forcibly overwrites the remote namespace.") $ fmap ( \sourceTarget -> @@ -2053,7 +2108,11 @@ pushExhaustive = "debug.push-exhaustive" [] I.Hidden - [("remote destination", Optional, remoteNamespaceArg), ("local target", Optional, namespaceOrProjectBranchArg suggestionsConfig)] + ( Parameters [] $ + Optional + [("remote destination", remoteNamespaceArg), ("local target", namespaceOrProjectBranchArg suggestionsConfig)] + Nothing + ) ( P.lines [ P.wrap $ "The " @@ -2093,10 +2152,12 @@ mergeOldSquashInputPattern = { patternName = "merge.old.squash", aliases = ["squash.old"], visibility = I.Hidden, - args = - [ ("namespace or branch to be squashed", Required, namespaceOrProjectBranchArg suggestionsConfig), - ("merge destination", Required, namespaceOrProjectBranchArg suggestionsConfig) - ], + params = + Parameters + [ ("namespace or branch to be squashed", namespaceOrProjectBranchArg suggestionsConfig), + ("merge destination", namespaceOrProjectBranchArg suggestionsConfig) + ] + $ Optional [] Nothing, help = P.wrap $ makeExample mergeOldSquashInputPattern ["src", "dest"] @@ -2131,9 +2192,9 @@ mergeOldInputPattern = "merge.old" [] I.Hidden - [ ("branch or namespace to merge", Required, namespaceOrProjectBranchArg config), - ("merge destination", Optional, namespaceOrProjectBranchArg config) - ] + ( Parameters [("branch or namespace to merge", namespaceOrProjectBranchArg config)] $ + Optional [("merge destination", namespaceOrProjectBranchArg config)] Nothing + ) ( P.column2 [ ( makeExample mergeOldInputPattern ["foo/bar", "baz/qux"], "merges the `foo/bar` branch into the `baz/qux` branch" @@ -2176,17 +2237,18 @@ mergeInputPattern = { patternName = "merge", aliases = [], visibility = I.Visible, - args = - [ ( "branch to merge", - Required, - projectBranchNameArg - ProjectBranchSuggestionsConfig - { showProjectCompletions = True, - projectInclusion = AllProjects, - branchInclusion = ExcludeCurrentBranch - } - ) - ], + params = + Parameters + [ ( "branch to merge", + projectBranchNameArg + ProjectBranchSuggestionsConfig + { showProjectCompletions = True, + projectInclusion = AllProjects, + branchInclusion = ExcludeCurrentBranch + } + ) + ] + $ Optional [] Nothing, help = P.wrap $ makeExample mergeInputPattern ["/branch"] <> "merges `branch` into the current branch", parse = \case @@ -2200,7 +2262,7 @@ mergeCommitInputPattern = { patternName = "merge.commit", aliases = ["commit.merge"], visibility = I.Visible, - args = [], + params = noParams, help = let mainBranch = UnsafeProjectBranchName "main" tempBranch = UnsafeProjectBranchName "merge-topic-into-main" @@ -2231,9 +2293,7 @@ mergeCommitInputPattern = makeExampleNoBackticks deleteBranch [prettySlashProjectBranchName tempBranch] ] ), - parse = \case - [] -> Right Input.MergeCommitI - args -> wrongArgsLength "no arguments" args + parse = const $ pure Input.MergeCommitI } diffNamespace :: InputPattern @@ -2242,7 +2302,9 @@ diffNamespace = "diff.namespace" [] I.Visible - [("before namespace", Required, namespaceOrProjectBranchArg suggestionsConfig), ("after namespace", Optional, namespaceOrProjectBranchArg suggestionsConfig)] + ( Parameters [("before namespace", namespaceOrProjectBranchArg suggestionsConfig)] $ + Optional [("after namespace", namespaceOrProjectBranchArg suggestionsConfig)] Nothing + ) ( P.column2 [ ( "`diff.namespace before after`", P.wrap "shows how the namespace `after` differs from the namespace `before`" @@ -2271,7 +2333,9 @@ mergeOldPreviewInputPattern = "merge.old.preview" [] I.Hidden - [("branch or namespace to merge", Required, namespaceOrProjectBranchArg suggestionsConfig), ("merge destination", Optional, namespaceOrProjectBranchArg suggestionsConfig)] + ( Parameters [("branch or namespace to merge", namespaceOrProjectBranchArg suggestionsConfig)] $ + Optional [("merge destination", namespaceOrProjectBranchArg suggestionsConfig)] Nothing + ) ( P.column2 [ ( makeExample mergeOldPreviewInputPattern ["src"], "shows how the current namespace will change after a " <> makeExample mergeOldInputPattern ["src"] @@ -2301,17 +2365,13 @@ deprecatedViewRootReflog = "deprecated.root-reflog" [] I.Visible - [] + noParams ( "`deprecated.root-reflog` lists the changes that have affected the root namespace. This has been deprecated in favor of " <> makeExample branchReflog [] <> " which shows the reflog for the current project." ) - ( \case - [] -> pure Input.ShowRootReflogI - _ -> - Left . P.string $ - I.patternName deprecatedViewRootReflog ++ " doesn't take any arguments." - ) + . const + $ pure Input.ShowRootReflogI branchReflog :: InputPattern branchReflog = @@ -2319,7 +2379,7 @@ branchReflog = "reflog" ["reflog.branch", "branch.reflog"] I.Visible - [] + (Parameters [] $ Optional [("branch name", noCompletionsArg)] Nothing) ( P.lines [ "`reflog` lists all the changes that have affected the current branch.", "`reflog /mybranch` lists all the changes that have affected /mybranch." @@ -2337,7 +2397,7 @@ projectReflog = "project.reflog" ["reflog.project"] I.Visible - [] + (Parameters [] $ Optional [("project name", noCompletionsArg)] Nothing) ( P.lines [ "`project.reflog` lists all the changes that have affected any branches in the current project.", "`project.reflog myproject` lists all the changes that have affected any branches in myproject." @@ -2355,15 +2415,13 @@ globalReflog = "reflog.global" [] I.Visible - [] + noParams ( P.lines [ "`reflog.global` lists all recent changes across all projects and branches." ] ) - ( \case - [] -> pure $ Input.ShowGlobalReflogI - _ -> Left (I.help globalReflog) - ) + . const + $ pure Input.ShowGlobalReflogI edit :: InputPattern edit = @@ -2371,7 +2429,7 @@ edit = { patternName = "edit", aliases = [], visibility = I.Visible, - args = [("definition to edit", OnePlus, definitionQueryArg)], + params = Parameters [] $ OnePlus ("definition to edit", definitionQueryArg), help = P.lines [ "`edit foo` prepends the definition of `foo` to the top of the most " @@ -2393,7 +2451,7 @@ editNew = { patternName = "edit.new", aliases = [], visibility = I.Visible, - args = [("definition to edit", OnePlus, definitionQueryArg)], + params = Parameters [] $ OnePlus ("definition to edit", definitionQueryArg), help = "Like `edit`, but adds a new fold line below the definitions.", parse = maybe @@ -2410,7 +2468,7 @@ editNamespace = { patternName = "edit.namespace", aliases = [], visibility = I.Visible, - args = [("namespace to load definitions from", ZeroPlus, namespaceArg)], + params = Parameters [] . Optional [] $ Just ("namespace to load definitions from", namespaceArg), help = P.lines [ "`edit.namespace` will load all terms and types contained within the current namespace into your scratch file. This includes definitions in namespaces, but excludes libraries.", @@ -2419,13 +2477,23 @@ editNamespace = parse = fmap Input.EditNamespaceI . traverse handlePathArg } -topicNameArg :: ArgumentType +newBranchNameArg :: ParameterType +newBranchNameArg = + ParameterType + { typeName = "new-branch", + suggestions = \_ _ _ _ -> pure [], + fzfResolver = Nothing, + isStructured = False + } + +topicNameArg :: ParameterType topicNameArg = let topics = Map.keys helpTopicsMap - in ArgumentType + in ParameterType { typeName = "topic", - suggestions = \q _ _ _ -> pure (exactComplete q topics), - fzfResolver = Just $ Resolvers.fuzzySelectFromList (Text.pack <$> topics) + suggestions = \q _ _ _ -> pure $ exactComplete q topics, + fzfResolver = Just . Resolvers.fuzzySelectFromList $ Text.pack <$> topics, + isStructured = True } helpTopics :: InputPattern @@ -2434,17 +2502,16 @@ helpTopics = "help-topics" ["help-topic"] I.Visible - [("topic", Optional, topicNameArg)] + (Parameters [] $ Optional [("topic", topicNameArg)] Nothing) ("`help-topics` lists all topics and `help-topics ` shows an explanation of that topic.") - ( \case - [] -> Right $ Input.CreateMessage topics - [topic] -> do - topic <- unsupportedStructuredArgument helpTopics "a help topic" topic - case Map.lookup topic helpTopicsMap of - Nothing -> Left $ "I don't know of that topic. Try `help-topics`." - Just t -> Right $ Input.CreateMessage t - _ -> Left $ "Use `help-topics ` or `help-topics`." - ) + \case + [] -> Right $ Input.CreateMessage topics + [topic] -> do + topic <- unifyArgument topic + case Map.lookup topic helpTopicsMap of + Nothing -> Left $ "I don't know of that topic. Try `help-topics`." + Just t -> Right $ Input.CreateMessage t + _ -> Left $ "Use `help-topics ` or `help-topics`." where topics = P.callout "🌻" $ @@ -2618,7 +2685,7 @@ help = "help" ["?"] I.Visible - [("command", Optional, commandNameArg)] + (Parameters [] $ Optional [("command", commandNameArg)] Nothing) "`help` shows general help and `help ` shows help for one command." $ \case [] -> @@ -2628,7 +2695,7 @@ help = showPatternHelp visibleInputs [cmd] -> do - cmd <- unsupportedStructuredArgument help "a command" cmd + cmd <- unifyArgument cmd case (Map.lookup cmd commandsByName, isHelp cmd) of (Nothing, Just msg) -> Right $ Input.CreateMessage msg (Nothing, Nothing) -> Left $ "I don't know of that command. Try" <> makeExampleEOS help [] @@ -2661,11 +2728,10 @@ quit = "quit" ["exit", ":q"] I.Visible - [] + noParams "Exits the Unison command line interface." - \case - [] -> pure Input.QuitI - _ -> Left "Use `quit`, `exit`, or to quit." + . const + $ pure Input.QuitI names :: Input.IsGlobal -> InputPattern names isGlobal = @@ -2673,7 +2739,7 @@ names isGlobal = cmdName [] I.Visible - [("name or hash", Required, definitionQueryArg)] + (Parameters [("name or hash", definitionQueryArg)] $ Optional [] Nothing) (P.wrap $ makeExample (names isGlobal) ["foo"] <> description) $ \case [thing] -> Input.NamesI isGlobal <$> handleHashQualifiedNameArg thing @@ -2690,7 +2756,7 @@ dependents = "dependents" [] I.Visible - [("definition", Required, definitionQueryArg)] + (Parameters [("definition", definitionQueryArg)] $ Optional [] Nothing) "List the named dependents of the specified definition." $ \case [thing] -> Input.ListDependentsI <$> handleHashQualifiedNameArg thing @@ -2700,7 +2766,7 @@ dependencies = "dependencies" [] I.Visible - [("definition", Required, definitionQueryArg)] + (Parameters [("definition", definitionQueryArg)] $ Optional [] Nothing) "List the dependencies of the specified definition." $ \case [thing] -> Input.ListDependenciesI <$> handleHashQualifiedNameArg thing @@ -2712,7 +2778,7 @@ namespaceDependencies = "namespace.dependencies" [] I.Visible - [("namespace", Optional, namespaceArg)] + (Parameters [] $ Optional [("namespace", namespaceArg)] Nothing) "List the external dependencies of the specified namespace." $ \case [p] -> Input.NamespaceDependenciesI . pure <$> handlePath'Arg p @@ -2725,9 +2791,10 @@ debugNumberedArgs = "debug.numberedArgs" [] I.Visible - [] + noParams "Dump the contents of the numbered args state." - (const $ Right Input.DebugNumberedArgsI) + . const + $ pure Input.DebugNumberedArgsI debugFileHashes :: InputPattern debugFileHashes = @@ -2735,9 +2802,10 @@ debugFileHashes = "debug.file" [] I.Visible - [] + noParams "View details about the most recent successfully typechecked file." - (const $ Right Input.DebugTypecheckedUnisonFileI) + . const + $ pure Input.DebugTypecheckedUnisonFileI debugDumpNamespace :: InputPattern debugDumpNamespace = @@ -2745,9 +2813,10 @@ debugDumpNamespace = "debug.dump-namespace" [] I.Visible - [] + noParams "Dump the namespace to a text file" - (const $ Right Input.DebugDumpNamespacesI) + . const + $ pure Input.DebugDumpNamespacesI debugDumpNamespaceSimple :: InputPattern debugDumpNamespaceSimple = @@ -2755,9 +2824,10 @@ debugDumpNamespaceSimple = "debug.dump-namespace-simple" [] I.Visible - [] + noParams "Dump the namespace to a text file" - (const $ Right Input.DebugDumpNamespaceSimpleI) + . const + $ pure Input.DebugDumpNamespaceSimpleI debugTerm :: InputPattern debugTerm = @@ -2765,7 +2835,7 @@ debugTerm = "debug.term.abt" [] I.Hidden - [("term", Required, exactDefinitionTermQueryArg)] + (Parameters [("term", exactDefinitionTermQueryArg)] $ Optional [] Nothing) "View debugging information for a given term." ( \case [thing] -> Input.DebugTermI False <$> handleHashQualifiedNameArg thing @@ -2778,7 +2848,7 @@ debugTermVerbose = "debug.term.abt.verbose" [] I.Hidden - [("term", Required, exactDefinitionTermQueryArg)] + (Parameters [("term", exactDefinitionTermQueryArg)] $ Optional [] Nothing) "View verbose debugging information for a given term." ( \case [thing] -> Input.DebugTermI True <$> handleHashQualifiedNameArg thing @@ -2791,7 +2861,7 @@ debugType = "debug.type.abt" [] I.Hidden - [("type", Required, exactDefinitionTypeQueryArg)] + (Parameters [("type", exactDefinitionTypeQueryArg)] $ Optional [] Nothing) "View debugging information for a given type." ( \case [thing] -> Input.DebugTypeI <$> handleHashQualifiedNameArg thing @@ -2804,9 +2874,10 @@ debugLSPFoldRanges = "debug.lsp.fold-ranges" [] I.Hidden - [] + noParams "Output the source from the most recently parsed file, but annotated with the computed fold ranges." - (const $ Right Input.DebugLSPFoldRangesI) + . const + $ pure Input.DebugLSPFoldRangesI debugClearWatchCache :: InputPattern debugClearWatchCache = @@ -2814,9 +2885,10 @@ debugClearWatchCache = "debug.clear-cache" [] I.Visible - [] + noParams "Clear the watch expression cache" - (const $ Right Input.DebugClearWatchI) + . const + $ pure Input.DebugClearWatchI debugDoctor :: InputPattern debugDoctor = @@ -2824,13 +2896,10 @@ debugDoctor = "debug.doctor" [] I.Visible - [] - ( P.wrap "Analyze your codebase for errors and inconsistencies." - ) - ( \case - [] -> Right $ Input.DebugDoctorI - args -> wrongArgsLength "no arguments" args - ) + noParams + (P.wrap "Analyze your codebase for errors and inconsistencies.") + . const + $ pure Input.DebugDoctorI debugNameDiff :: InputPattern debugNameDiff = @@ -2838,7 +2907,7 @@ debugNameDiff = { patternName = "debug.name-diff", aliases = [], visibility = I.Hidden, - args = [("before namespace", Required, namespaceArg), ("after namespace", Required, namespaceArg)], + params = Parameters [("before namespace", namespaceArg), ("after namespace", namespaceArg)] $ Optional [] Nothing, help = P.wrap "List all name changes between two causal hashes. Does not detect patch changes.", parse = \case [from, to] -> Input.DebugNameDiffI <$> handleShortCausalHashArg from <*> handleShortCausalHashArg to @@ -2851,7 +2920,7 @@ test = { patternName = "test", aliases = [], visibility = I.Visible, - args = [("namespace", Optional, namespaceArg)], + params = Parameters [] $ Optional [("namespace", namespaceArg)] Nothing, help = P.wrapColumn2 [ ("`test`", "runs unit tests for the current branch"), @@ -2881,7 +2950,7 @@ testNative = { patternName = "test.native", aliases = [], visibility = I.Hidden, - args = [("namespace", Optional, namespaceArg)], + params = Parameters [] $ Optional [("namespace", namespaceArg)] Nothing, help = P.wrapColumn2 [ ( "`test.native`", @@ -2913,19 +2982,18 @@ testAll = "test.all" [] I.Visible - [] + noParams "`test.all` runs unit tests for the current branch (including the `lib` namespace)." - ( const $ - pure $ - Input.TestI - False - Input.TestInput - { includeLibNamespace = True, - path = Path.empty, - showFailures = True, - showSuccesses = True - } - ) + . const + . pure + $ Input.TestI + False + Input.TestInput + { includeLibNamespace = True, + path = Path.empty, + showFailures = True, + showSuccesses = True + } testAllNative :: InputPattern testAllNative = @@ -2933,19 +3001,18 @@ testAllNative = "test.native.all" ["test.all.native"] I.Hidden - [] + noParams "`test.native.all` runs unit tests for the current branch (including the `lib` namespace) on the native runtime." - ( const $ - pure $ - Input.TestI - True - Input.TestInput - { includeLibNamespace = True, - path = Path.empty, - showFailures = True, - showSuccesses = True - } - ) + . const + . pure + $ Input.TestI + True + Input.TestInput + { includeLibNamespace = True, + path = Path.empty, + showFailures = True, + showSuccesses = True + } docsToHtml :: InputPattern docsToHtml = @@ -2953,7 +3020,7 @@ docsToHtml = "docs.to-html" [] I.Visible - [("namespace", Required, branchRelativePathArg), ("", Required, filePathArg)] + (Parameters [("namespace", branchRelativePathArg), ("output directory", filePathArg)] $ Optional [] Nothing) ( P.wrapColumn2 [ ( makeExample docsToHtml [".path.to.ns", "doc-dir"], "Render all docs contained within the namespace `.path.to.ns`, no matter how deep, to html files in `doc-dir` in the directory UCM was run from." @@ -2976,7 +3043,7 @@ docToMarkdown = "debug.doc-to-markdown" [] I.Visible - [("doc to render", Required, exactDefinitionTermQueryArg)] + (Parameters [("doc to render", exactDefinitionTermQueryArg)] $ Optional [] Nothing) ( P.wrapColumn2 [ ( "`debug.doc-to-markdown term.doc`", "Render a doc to markdown." @@ -2993,7 +3060,7 @@ execute = "run" [] I.Visible - [("definition to execute", Required, exactDefinitionTermQueryArg), ("argument", ZeroPlus, noCompletionsArg)] + (Parameters [("definition to execute", exactDefinitionTermQueryArg)] . Optional [] $ Just ("argument", noCompletionsArg)) ( P.wrapColumn2 [ ( "`run mymain args...`", "Runs `!mymain`, where `mymain` is searched for in the most recent" @@ -3003,7 +3070,7 @@ execute = ) ] ) - $ \case + \case main : args -> Input.ExecuteI <$> handleHashQualifiedNameArg main @@ -3016,7 +3083,7 @@ saveExecuteResult = "add.run" [] I.Visible - [("new name", Required, newNameArg)] + (Parameters [("new name", newNameArg)] $ Optional [] Nothing) ( "`add.run name` adds to the codebase the result of the most recent `run` command" <> " as `name`." ) @@ -3030,7 +3097,7 @@ ioTest = { patternName = "io.test", aliases = ["test.io"], visibility = I.Visible, - args = [("test to run", Required, exactDefinitionTermQueryArg)], + params = Parameters [("test to run", exactDefinitionTermQueryArg)] $ Optional [] Nothing, help = P.wrapColumn2 [ ( "`io.test mytest`", @@ -3048,7 +3115,7 @@ ioTestNative = { patternName = "io.test.native", aliases = ["test.io.native", "test.native.io"], visibility = I.Hidden, - args = [("test to run", Required, exactDefinitionTermQueryArg)], + params = Parameters [("test to run", exactDefinitionTermQueryArg)] $ Optional [] Nothing, help = P.wrapColumn2 [ ( "`io.test.native mytest`", @@ -3068,16 +3135,14 @@ ioTestAll = { patternName = "io.test.all", aliases = ["test.io.all"], visibility = I.Visible, - args = [], + params = noParams, help = P.wrapColumn2 [ ( "`io.test.all`", "runs unit tests for the current branch that use IO" ) ], - parse = \case - [] -> Right (Input.IOTestAllI False) - args -> wrongArgsLength "no arguments" args + parse = const . pure $ Input.IOTestAllI False } ioTestAllNative :: InputPattern @@ -3086,16 +3151,14 @@ ioTestAllNative = { patternName = "io.test.native.all", aliases = ["test.io.native.all", "test.native.io.all"], visibility = I.Hidden, - args = [], + params = noParams, help = P.wrapColumn2 [ ( "`io.test.native.all`", "runs unit tests for the current branch that use IO" ) ], - parse = \case - [] -> Right (Input.IOTestAllI True) - args -> wrongArgsLength "no arguments" args + parse = const . pure $ Input.IOTestAllI True } makeStandalone :: InputPattern @@ -3104,7 +3167,9 @@ makeStandalone = "compile" ["compile.output"] I.Visible - [("definition to compile", Required, exactDefinitionTermQueryArg), ("output file", Required, filePathArg)] + ( Parameters [("definition to compile", exactDefinitionTermQueryArg), ("output file", filePathArg)] $ + Optional [] Nothing + ) ( P.wrapColumn2 [ ( "`compile main file`", "Outputs a stand alone file that can be directly loaded and" @@ -3113,7 +3178,7 @@ makeStandalone = ) ] ) - $ \case + \case [main, file] -> Input.MakeStandaloneI <$> unsupportedStructuredArgument makeStandalone "a file name" file @@ -3126,14 +3191,16 @@ runScheme = "run.native" [] I.Visible - [("definition to run", Required, exactDefinitionTermQueryArg), ("arguments", ZeroPlus, noCompletionsArg)] + ( Parameters [("definition to run", exactDefinitionTermQueryArg)] . Optional [] $ + Just ("arguments", noCompletionsArg) + ) ( P.wrapColumn2 [ ( makeExample runScheme ["main", "args"], "Executes !main using native compilation via scheme." ) ] ) - $ \case + \case main : args -> Input.ExecuteSchemeI <$> handleHashQualifiedNameArg main @@ -3146,10 +3213,9 @@ compileScheme = "compile.native" [] I.Hidden - [ ("definition to compile", Required, exactDefinitionTermQueryArg), - ("output file", Required, filePathArg), - ("profile", Optional, profileArg) - ] + ( Parameters [("definition to compile", exactDefinitionTermQueryArg), ("output file", filePathArg)] $ + Optional [("profile", profileArg)] Nothing + ) ( P.wrapColumn2 [ ( makeExample compileScheme ["main", "file", "profile"], "Creates stand alone executable via compilation to" @@ -3184,7 +3250,8 @@ createAuthor = "create.author" [] I.Visible - [("definition name", Required, noCompletionsArg), ("author name", Required, noCompletionsArg)] + -- FIXME: This isn’t actually a dependency, but it has the same structure + (Parameters [("definition name", dependencyArg), ("author name", noCompletionsArg)] $ Optional [] Nothing) ( makeExample createAuthor ["alicecoder", "\"Alice McGee\""] <> " " <> P.wrap @@ -3197,20 +3264,11 @@ createAuthor = ) ) \case - symbolStr : authorStr@(_ : _) -> + [symbolStr, authorStr] -> Input.CreateAuthorI <$> handleRelativeNameSegmentArg symbolStr - <*> fmap - (parseAuthorName . unwords) - (traverse (unsupportedStructuredArgument createAuthor "text") authorStr) + <*> fmap Text.pack (unsupportedStructuredArgument createAuthor "text" authorStr) args -> wrongArgsLength "at least two arguments" args - where - -- let's have a real parser in not too long - parseAuthorName :: String -> Text - parseAuthorName = - Text.pack . \case - ('"' : quoted) -> init quoted - bare -> bare authLogin :: InputPattern authLogin = @@ -3218,17 +3276,14 @@ authLogin = "auth.login" [] I.Visible - [] + noParams ( P.lines [ P.wrap "Obtain an authentication session with Unison Share.", - makeExample authLogin [] - <> "authenticates ucm with Unison Share." + makeExample authLogin [] <> "authenticates ucm with Unison Share." ] ) - ( \case - [] -> Right $ Input.AuthLoginI - args -> wrongArgsLength "no arguments" args - ) + . const + $ pure Input.AuthLoginI printVersion :: InputPattern printVersion = @@ -3236,13 +3291,10 @@ printVersion = "version" [] I.Visible - [] - ( P.wrap "Print the version of unison you're running" - ) - ( \case - [] -> Right $ Input.VersionI - args -> wrongArgsLength "no arguments" args - ) + noParams + (P.wrap "Print the version of unison you're running") + . const + $ pure Input.VersionI projectCreate :: InputPattern projectCreate = @@ -3250,7 +3302,7 @@ projectCreate = { patternName = "project.create", aliases = ["create.project"], visibility = I.Visible, - args = [], + params = Parameters [] $ Optional [("project name", noCompletionsArg)] Nothing, help = P.wrapColumn2 [ ("`project.create`", "creates a project with a random name"), @@ -3268,7 +3320,7 @@ projectCreateEmptyInputPattern = { patternName = "project.create-empty", aliases = ["create.empty-project"], visibility = I.Hidden, - args = [], + params = Parameters [] $ Optional [("project name", noCompletionsArg)] Nothing, help = P.wrapColumn2 [ ("`project.create-empty`", "creates an empty project with a random name"), @@ -3286,7 +3338,7 @@ projectRenameInputPattern = { patternName = "project.rename", aliases = ["rename.project"], visibility = I.Visible, - args = [("new name", Required, projectNameArg)], + params = Parameters [("new name", projectNameArg)] $ Optional [] Nothing, help = P.wrapColumn2 [ ("`project.rename foo`", "renames the current project to `foo`") @@ -3302,7 +3354,9 @@ projectSwitch = { patternName = "switch", aliases = [], visibility = I.Visible, - args = [("project or branch to switch to", Required, projectAndBranchNamesArg suggestionsConfig)], + params = + Parameters [("project or branch to switch to", projectAndBranchNamesArg suggestionsConfig)] $ + Optional [] Nothing, help = P.wrapColumn2 [ ("`switch`", "opens an interactive selector to pick a project and branch"), @@ -3328,9 +3382,9 @@ projectsInputPattern = { patternName = "projects", aliases = ["list.project", "ls.project", "project.list"], visibility = I.Visible, - args = [], + params = noParams, help = P.wrap "List projects.", - parse = \_ -> Right Input.ProjectsI + parse = const $ pure Input.ProjectsI } branchesInputPattern :: InputPattern @@ -3339,7 +3393,7 @@ branchesInputPattern = { patternName = "branches", aliases = ["list.branch", "ls.branch", "branch.list"], visibility = I.Visible, - args = [("project", Optional, projectNameArg)], + params = Parameters [] $ Optional [("project", projectNameArg)] Nothing, help = P.wrapColumn2 [ ("`branches`", "lists all branches in the current project"), @@ -3357,10 +3411,9 @@ branchInputPattern = { patternName = "branch", aliases = ["branch.create", "create.branch"], visibility = I.Visible, - args = - [ ("branch", Required, projectBranchNameArg suggestionsConfig), - ("branch", Optional, newBranchNameArg) - ], + params = + Parameters [("branch", projectBranchNameArg suggestionsConfig)] $ + Optional [("branch", newBranchNameArg)] Nothing, help = P.wrapColumn2 [ ("`branch foo`", "forks the current project branch to a new branch `foo`"), @@ -3375,12 +3428,6 @@ branchInputPattern = args -> wrongArgsLength "one or two arguments" args } where - newBranchNameArg = - ArgumentType - { typeName = "new-branch", - suggestions = \_ _ _ _ -> pure [], - fzfResolver = Nothing - } suggestionsConfig = ProjectBranchSuggestionsConfig { showProjectCompletions = False, @@ -3394,7 +3441,7 @@ branchEmptyInputPattern = { patternName = "branch.empty", aliases = ["branch.create-empty", "create.empty-branch"], visibility = I.Visible, - args = [], + params = Parameters [("branch", newBranchNameArg)] $ Optional [] Nothing, help = P.wrap "Create a new empty branch.", parse = \case [name] -> @@ -3409,7 +3456,7 @@ branchRenameInputPattern = { patternName = "branch.rename", aliases = ["rename.branch"], visibility = I.Visible, - args = [], + params = Parameters [("branch", newBranchNameArg)] $ Optional [] Nothing, help = P.wrapColumn2 [("`branch.rename foo`", "renames the current branch to `foo`")], @@ -3424,7 +3471,9 @@ clone = { patternName = "clone", aliases = [], visibility = I.Visible, - args = [], + params = + Parameters [("source branch", projectAndBranchNamesArg suggestionsConfig)] $ + Optional [("target branch", newBranchNameArg)] Nothing, help = P.wrapColumn2 [ ( "`clone @unison/json/topic json/my-topic`", @@ -3455,6 +3504,13 @@ clone = <*> fmap pure (handleProjectAndBranchNamesArg localNames) args -> wrongArgsLength "one or two arguments" args } + where + suggestionsConfig = + ProjectBranchSuggestionsConfig + { showProjectCompletions = True, + projectInclusion = AllProjects, + branchInclusion = ExcludeCurrentBranch + } releaseDraft :: InputPattern releaseDraft = @@ -3462,7 +3518,7 @@ releaseDraft = { patternName = "release.draft", aliases = ["draft.release"], visibility = I.Visible, - args = [], + params = Parameters [("version", noCompletionsArg)] $ Optional [] Nothing, help = P.wrap "Draft a release.", parse = \case [semverString] -> @@ -3479,7 +3535,9 @@ upgrade = { patternName = "upgrade", aliases = [], visibility = I.Visible, - args = [("dependency to upgrade", Required, dependencyArg), ("dependency to upgrade to", Required, dependencyArg)], + params = + Parameters [("dependency to upgrade", dependencyArg), ("dependency to upgrade to", dependencyArg)] $ + Optional [] Nothing, help = P.wrap $ "`upgrade old new` upgrades library dependency `lib.old` to `lib.new`, and, if successful, deletes `lib.old`.", @@ -3495,7 +3553,7 @@ upgradeCommitInputPattern = { patternName = "upgrade.commit", aliases = ["commit.upgrade"], visibility = I.Visible, - args = [], + params = noParams, help = let mainBranch = UnsafeProjectBranchName "main" tempBranch = UnsafeProjectBranchName "upgrade-foo-to-bar" @@ -3526,9 +3584,7 @@ upgradeCommitInputPattern = makeExampleNoBackticks deleteBranch [prettySlashProjectBranchName tempBranch] ] ), - parse = \case - [] -> Right Input.UpgradeCommitI - args -> wrongArgsLength "no arguments" args + parse = const $ pure Input.UpgradeCommitI } debugSynhashTermInputPattern :: InputPattern @@ -3537,7 +3593,7 @@ debugSynhashTermInputPattern = { patternName = "debug.synhash.term", aliases = [], visibility = I.Hidden, - args = [("term", Required, exactDefinitionTermQueryArg)], + params = Parameters [("term", exactDefinitionTermQueryArg)] $ Optional [] Nothing, help = mempty, parse = \case [arg] -> Input.DebugSynhashTermI <$> handleNameArg arg @@ -3695,70 +3751,77 @@ visibleInputs = filter ((== I.Visible) . I.visibility) validInputs commandNames :: [String] commandNames = visibleInputs >>= \i -> I.patternName i : I.aliases i -commandNameArg :: ArgumentType +commandNameArg :: ParameterType commandNameArg = let options = commandNames <> Map.keys helpTopicsMap - in ArgumentType + in ParameterType { typeName = "command", suggestions = \q _ _ _ -> pure (exactComplete q options), - fzfResolver = Just $ Resolvers.fuzzySelectFromList (Text.pack <$> options) + fzfResolver = Just $ Resolvers.fuzzySelectFromList (Text.pack <$> options), + isStructured = False } -exactDefinitionArg :: ArgumentType +exactDefinitionArg :: ParameterType exactDefinitionArg = - ArgumentType + ParameterType { typeName = "definition", suggestions = \q cb _http p -> Codebase.runTransaction cb (prefixCompleteTermOrType q p), - fzfResolver = Just Resolvers.definitionResolver + fzfResolver = Just Resolvers.definitionResolver, + isStructured = True } -definitionQueryArg :: ArgumentType +definitionQueryArg :: ParameterType definitionQueryArg = exactDefinitionArg {typeName = "definition query"} -exactDefinitionTypeQueryArg :: ArgumentType +exactDefinitionTypeQueryArg :: ParameterType exactDefinitionTypeQueryArg = - ArgumentType + ParameterType { typeName = "type definition query", suggestions = \q cb _http p -> Codebase.runTransaction cb (prefixCompleteType q p), - fzfResolver = Just Resolvers.typeDefinitionResolver + fzfResolver = Just Resolvers.typeDefinitionResolver, + isStructured = True } -exactDefinitionTypeOrTermQueryArg :: ArgumentType +exactDefinitionTypeOrTermQueryArg :: ParameterType exactDefinitionTypeOrTermQueryArg = - ArgumentType + ParameterType { typeName = "type or term definition query", suggestions = \q cb _http p -> Codebase.runTransaction cb (prefixCompleteTermOrType q p), - fzfResolver = Just Resolvers.definitionResolver + fzfResolver = Just Resolvers.definitionResolver, + isStructured = True } -exactDefinitionTermQueryArg :: ArgumentType +exactDefinitionTermQueryArg :: ParameterType exactDefinitionTermQueryArg = - ArgumentType + ParameterType { typeName = "term definition query", suggestions = \q cb _http p -> Codebase.runTransaction cb (prefixCompleteTerm q p), - fzfResolver = Just Resolvers.termDefinitionResolver + fzfResolver = Just Resolvers.termDefinitionResolver, + isStructured = True } -patchArg :: ArgumentType +patchArg :: ParameterType patchArg = - ArgumentType + ParameterType { typeName = "patch", suggestions = \q cb _http p -> Codebase.runTransaction cb (prefixCompletePatch q p), - fzfResolver = Nothing + fzfResolver = Nothing, + isStructured = True } -namespaceArg :: ArgumentType +namespaceArg :: ParameterType namespaceArg = - ArgumentType + ParameterType { typeName = "namespace", suggestions = \q cb _http p -> Codebase.runTransaction cb (prefixCompleteNamespace q p), - fzfResolver = Just Resolvers.namespaceResolver + fzfResolver = Just Resolvers.namespaceResolver, + isStructured = True } -- | Usually you'll want one or the other, but some commands support both right now. -namespaceOrProjectBranchArg :: ProjectBranchSuggestionsConfig -> ArgumentType +namespaceOrProjectBranchArg :: ProjectBranchSuggestionsConfig -> ParameterType namespaceOrProjectBranchArg config = - ArgumentType + ParameterType { typeName = "namespace or branch", suggestions = let namespaceSuggestions = \q cb _http pp -> Codebase.runTransaction cb (prefixCompleteNamespace q pp) @@ -3766,72 +3829,80 @@ namespaceOrProjectBranchArg config = [ projectAndOrBranchSuggestions config, namespaceSuggestions ], - fzfResolver = Just Resolvers.projectOrBranchResolver + fzfResolver = Just Resolvers.projectOrBranchResolver, + isStructured = True } -namespaceOrDefinitionArg :: ArgumentType +namespaceOrDefinitionArg :: ParameterType namespaceOrDefinitionArg = - ArgumentType + ParameterType { typeName = "term, type, or namespace", suggestions = \q cb _http p -> Codebase.runTransaction cb do namespaces <- prefixCompleteNamespace q p termsTypes <- prefixCompleteTermOrType q p pure (List.nubOrd $ namespaces <> termsTypes), fzfResolver = - Just Resolvers.namespaceOrDefinitionResolver + Just Resolvers.namespaceOrDefinitionResolver, + isStructured = True } -- | A dependency name. E.g. if your project has `lib.base`, `base` would be a dependency -- name. -dependencyArg :: ArgumentType +dependencyArg :: ParameterType dependencyArg = - ArgumentType + ParameterType { typeName = "project dependency", suggestions = \q cb _http pp -> Codebase.runTransaction cb do prefixCompleteNamespace q (pp & PP.path_ .~ Path.singleton NameSegment.libSegment), - fzfResolver = Just Resolvers.projectDependencyResolver + fzfResolver = Just Resolvers.projectDependencyResolver, + isStructured = True } -newNameArg :: ArgumentType +newNameArg :: ParameterType newNameArg = - ArgumentType + ParameterType { typeName = "new-name", suggestions = \q cb _http p -> Codebase.runTransaction cb (prefixCompleteNamespace q p), - fzfResolver = Nothing + fzfResolver = Nothing, + isStructured = True } -noCompletionsArg :: ArgumentType +noCompletionsArg :: ParameterType noCompletionsArg = - ArgumentType + ParameterType { typeName = "word", suggestions = noCompletions, - fzfResolver = Nothing + fzfResolver = Nothing, + isStructured = False } -filePathArg :: ArgumentType +filePathArg :: ParameterType filePathArg = - ArgumentType + ParameterType { typeName = "file-path", suggestions = noCompletions, - fzfResolver = Nothing + fzfResolver = Nothing, + isStructured = False } -- | Refers to a namespace on some remote code host. -remoteNamespaceArg :: ArgumentType +remoteNamespaceArg :: ParameterType remoteNamespaceArg = - ArgumentType + ParameterType { typeName = "remote-namespace", suggestions = \input _cb http _p -> sharePathCompletion http input, - fzfResolver = Nothing + fzfResolver = Nothing, + isStructured = True } -profileArg :: ArgumentType +profileArg :: ParameterType profileArg = - ArgumentType + ParameterType { typeName = "profile", suggestions = \_input _cb _http _p -> pure [Line.simpleCompletion "profile"], - fzfResolver = Nothing + fzfResolver = Nothing, + isStructured = False } data ProjectInclusion = OnlyWithinCurrentProject | OnlyOutsideCurrentProject | AllProjects @@ -4145,29 +4216,32 @@ branchRelativePathSuggestions config inputStr codebase _httpClient pp = do branchPathSep = ":" -- | A project name, branch name, or both. -projectAndBranchNamesArg :: ProjectBranchSuggestionsConfig -> ArgumentType +projectAndBranchNamesArg :: ProjectBranchSuggestionsConfig -> ParameterType projectAndBranchNamesArg config = - ArgumentType + ParameterType { typeName = "project-and-branch-names", suggestions = projectAndOrBranchSuggestions config, - fzfResolver = Just Resolvers.projectAndOrBranchArg + fzfResolver = Just Resolvers.projectAndOrBranchArg, + isStructured = True } -- | A project branch name. -projectBranchNameArg :: ProjectBranchSuggestionsConfig -> ArgumentType +projectBranchNameArg :: ProjectBranchSuggestionsConfig -> ParameterType projectBranchNameArg config = - ArgumentType + ParameterType { typeName = "project-branch-name", suggestions = projectAndOrBranchSuggestions config, - fzfResolver = Just Resolvers.projectBranchResolver + fzfResolver = Just Resolvers.projectBranchResolver, + isStructured = True } -branchRelativePathArg :: ArgumentType +branchRelativePathArg :: ParameterType branchRelativePathArg = - ArgumentType + ParameterType { typeName = "branch-relative-path", suggestions = branchRelativePathSuggestions config, - fzfResolver = Nothing + fzfResolver = Nothing, + isStructured = True } where config = @@ -4178,12 +4252,13 @@ branchRelativePathArg = } -- | A project name. -projectNameArg :: ArgumentType +projectNameArg :: ParameterType projectNameArg = - ArgumentType + ParameterType { typeName = "project-name", suggestions = \input codebase _httpClient _path -> projectNameSuggestions NoSlash input codebase, - fzfResolver = Just $ Resolvers.multiResolver [Resolvers.projectNameOptions] + fzfResolver = Just $ Resolvers.multiResolver [Resolvers.projectNameOptions], + isStructured = True } data OptionalSlash @@ -4228,19 +4303,6 @@ parsePushSource sourceStr = parsePushTarget :: String -> Maybe (These ProjectName ProjectBranchName) parsePushTarget = Megaparsec.parseMaybe UriParser.writeRemoteNamespace . Text.pack -parseHashQualifiedName :: - String -> Either (P.Pretty CT.ColorText) (HQ.HashQualified Name) -parseHashQualifiedName s = - maybe - ( Left - . P.wrap - $ P.string s - <> " is not a well-formed name, hash, or hash-qualified name. " - <> "I expected something like `foo`, `#abc123`, or `foo#abc123`." - ) - Right - $ HQ.parseText (Text.pack s) - explainRemote :: PushPull -> P.Pretty CT.ColorText explainRemote pushPull = P.group $ diff --git a/unison-cli/src/Unison/CommandLine/Main.hs b/unison-cli/src/Unison/CommandLine/Main.hs index 3b86508eb0..cf18c18e47 100644 --- a/unison-cli/src/Unison/CommandLine/Main.hs +++ b/unison-cli/src/Unison/CommandLine/Main.hs @@ -91,26 +91,25 @@ getUserInput codebase authHTTPClient pp currentProjectRoot numberedArgs = line <- Line.getInputLine fullPrompt case line of Nothing -> pure QuitI - Just l -> case words l of - [] -> go - ws -> do - liftIO (parseInput codebase pp currentProjectRoot numberedArgs IP.patternMap ws) >>= \case - Left msg -> do - -- We still add history that failed to parse so the user can easily reload - -- the input and fix it. - Line.modifyHistory $ Line.addHistoryUnlessConsecutiveDupe $ l - liftIO $ putPrettyLn msg - go - Right Nothing -> do - -- Ctrl-c or some input cancel, re-run the prompt - go - Right (Just (expandedArgs, i)) -> do - let expandedArgs' = IP.unifyArgument <$> expandedArgs - expandedArgsStr = unwords expandedArgs' - when (expandedArgs' /= ws) $ do - liftIO . putStrLn $ fullPrompt <> expandedArgsStr - Line.modifyHistory $ Line.addHistoryUnlessConsecutiveDupe $ expandedArgsStr - pure i + Just l -> do + liftIO (parseInput codebase pp currentProjectRoot numberedArgs IP.patternMap l) >>= \case + Left msg -> do + -- We still add history that failed to parse so the user can easily reload + -- the input and fix it. + Line.modifyHistory $ Line.addHistoryUnlessConsecutiveDupe $ l + liftIO $ putPrettyLn msg + go + Right Nothing -> do + -- Ctrl-c or some input cancel, re-run the prompt + go + Right (Just (command, expandedArgs, i)) -> do + expandedArgs' <- either (const $ fail "meh") pure $ traverse IP.unifyArgument expandedArgs + let expandedArgsStr = unwords $ command : expandedArgs' + -- This uses `words` for some sloppy whitespace normalization + when (words expandedArgsStr /= words l) $ do + liftIO . putStrLn $ fullPrompt <> expandedArgsStr + Line.modifyHistory $ Line.addHistoryUnlessConsecutiveDupe $ expandedArgsStr + pure i settings :: Line.Settings IO settings = Line.Settings diff --git a/unison-cli/src/Unison/CommandLine/OutputMessages.hs b/unison-cli/src/Unison/CommandLine/OutputMessages.hs index 147b3f32f4..81471c9f1e 100644 --- a/unison-cli/src/Unison/CommandLine/OutputMessages.hs +++ b/unison-cli/src/Unison/CommandLine/OutputMessages.hs @@ -1585,6 +1585,8 @@ notifyUser dir = \case pure $ P.lines [P.text (FZFResolvers.fuzzySelectHeader argDesc), P.indentN 2 $ P.bulleted (P.string <$> fuzzyOptions)] + DebugFuzzyOptionsIncorrectArgs _ -> pure $ P.string "Too many arguments were provided." + DebugFuzzyOptionsNoCommand command -> pure $ "The command “" <> P.string command <> "” doesn’t exist." DebugFuzzyOptionsNoResolver -> pure "No resolver found for fuzzy options in this slot." ClearScreen -> do ANSI.clearScreen diff --git a/unison-src/transcripts-manual/benchmarks.md b/unison-src/transcripts-manual/benchmarks.md index c1ae19d148..15c5cf73b9 100644 --- a/unison-src/transcripts-manual/benchmarks.md +++ b/unison-src/transcripts-manual/benchmarks.md @@ -41,56 +41,56 @@ scratch/main> run prepare ## Benchmarks ``` ucm -scratch/main> load unison-src/transcripts-manual/benchmarks/each.u +scratch/main> load "unison-src/transcripts-manual/benchmarks/each.u" scratch/main> run main ``` ``` ucm -scratch/main> load unison-src/transcripts-manual/benchmarks/listmap.u +scratch/main> load "unison-src/transcripts-manual/benchmarks/listmap.u" scratch/main> run main ``` ``` ucm -scratch/main> load unison-src/transcripts-manual/benchmarks/listfilter.u +scratch/main> load "unison-src/transcripts-manual/benchmarks/listfilter.u" scratch/main> run main ``` ``` ucm -scratch/main> load unison-src/transcripts-manual/benchmarks/random.u +scratch/main> load "unison-src/transcripts-manual/benchmarks/random.u" scratch/main> run main ``` ``` ucm -scratch/main> load unison-src/transcripts-manual/benchmarks/simpleloop.u +scratch/main> load "unison-src/transcripts-manual/benchmarks/simpleloop.u" scratch/main> run main ``` ``` ucm -scratch/main> load unison-src/transcripts-manual/benchmarks/fibonacci.u +scratch/main> load "unison-src/transcripts-manual/benchmarks/fibonacci.u" scratch/main> run main ``` ``` ucm -scratch/main> load unison-src/transcripts-manual/benchmarks/map.u +scratch/main> load "unison-src/transcripts-manual/benchmarks/map.u" scratch/main> run main ``` ``` ucm -scratch/main> load unison-src/transcripts-manual/benchmarks/natmap.u +scratch/main> load "unison-src/transcripts-manual/benchmarks/natmap.u" scratch/main> run main ``` ``` ucm -scratch/main> load unison-src/transcripts-manual/benchmarks/stm.u +scratch/main> load "unison-src/transcripts-manual/benchmarks/stm.u" scratch/main> run main ``` ``` ucm -scratch/main> load unison-src/transcripts-manual/benchmarks/tmap.u +scratch/main> load "unison-src/transcripts-manual/benchmarks/tmap.u" scratch/main> run main ``` ``` ucm -scratch/main> load unison-src/transcripts-manual/benchmarks/array-sort.u +scratch/main> load "unison-src/transcripts-manual/benchmarks/array-sort.u" scratch/main> run main ``` diff --git a/unison-src/transcripts-manual/rewrites.md b/unison-src/transcripts-manual/rewrites.md index f77c87502a..8f83753df8 100644 --- a/unison-src/transcripts-manual/rewrites.md +++ b/unison-src/transcripts-manual/rewrites.md @@ -1,6 +1,6 @@ ``` ucm :hide scratch/main> builtins.mergeio -scratch/main> load unison-src/transcripts-using-base/base.u +scratch/main> load "unison-src/transcripts-using-base/base.u" scratch/main> add ``` diff --git a/unison-src/transcripts-round-trip/main.md b/unison-src/transcripts-round-trip/main.md index fc9e320c04..90f7a94dc3 100644 --- a/unison-src/transcripts-round-trip/main.md +++ b/unison-src/transcripts-round-trip/main.md @@ -7,7 +7,7 @@ scratch/a2> builtins.mergeio lib.builtins ``` ``` ucm :hide -scratch/a1> load unison-src/transcripts-round-trip/reparses-with-same-hash.u +scratch/a1> load "unison-src/transcripts-round-trip/reparses-with-same-hash.u" scratch/a1> add ``` @@ -48,7 +48,7 @@ Now check that definitions in 'reparses.u' at least parse on round trip: ``` ucm :hide scratch/a3> builtins.mergeio lib.builtins -scratch/a3> load unison-src/transcripts-round-trip/reparses.u +scratch/a3> load "unison-src/transcripts-round-trip/reparses.u" scratch/a3> add ``` diff --git a/unison-src/transcripts-using-base/_base.md b/unison-src/transcripts-using-base/_base.md index 9ce21e6118..20d8c2a34d 100644 --- a/unison-src/transcripts-using-base/_base.md +++ b/unison-src/transcripts-using-base/_base.md @@ -11,7 +11,7 @@ transcripts which contain less boilerplate. ``` ucm :hide scratch/main> builtins.mergeio -scratch/main> load unison-src/transcripts-using-base/base.u +scratch/main> load "unison-src/transcripts-using-base/base.u" scratch/main> add ``` diff --git a/unison-src/transcripts-using-base/_base.output.md b/unison-src/transcripts-using-base/_base.output.md index 52910967b2..b59da94b25 100644 --- a/unison-src/transcripts-using-base/_base.output.md +++ b/unison-src/transcripts-using-base/_base.output.md @@ -12,7 +12,7 @@ transcripts which contain less boilerplate. ``` ucm :hide scratch/main> builtins.mergeio -scratch/main> load unison-src/transcripts-using-base/base.u +scratch/main> load "unison-src/transcripts-using-base/base.u" scratch/main> add ``` diff --git a/unison-src/transcripts-using-base/doc.md b/unison-src/transcripts-using-base/doc.md index d80e60ce58..5e11024883 100644 --- a/unison-src/transcripts-using-base/doc.md +++ b/unison-src/transcripts-using-base/doc.md @@ -45,7 +45,7 @@ The `docs ImportantConstant` command will look for `ImportantConstant.doc` in th First, we'll load the `syntax.u` file which has examples of all the syntax: ``` ucm -scratch/main> load ./unison-src/transcripts-using-base/doc.md.files/syntax.u +scratch/main> load "./unison-src/transcripts-using-base/doc.md.files/syntax.u" ``` ``` ucm :hide diff --git a/unison-src/transcripts-using-base/doc.output.md b/unison-src/transcripts-using-base/doc.output.md index aca445303c..62a476c082 100644 --- a/unison-src/transcripts-using-base/doc.output.md +++ b/unison-src/transcripts-using-base/doc.output.md @@ -72,7 +72,7 @@ The `docs ImportantConstant` command will look for `ImportantConstant.doc` in th First, we'll load the `syntax.u` file which has examples of all the syntax: ``` ucm -scratch/main> load ./unison-src/transcripts-using-base/doc.md.files/syntax.u +scratch/main> load "./unison-src/transcripts-using-base/doc.md.files/syntax.u" Loading changes detected in ./unison-src/transcripts-using-base/doc.md.files/syntax.u. diff --git a/unison-src/transcripts/idempotent/any-extract.md b/unison-src/transcripts/idempotent/any-extract.md index a6621b64ba..6aefb35f1b 100644 --- a/unison-src/transcripts/idempotent/any-extract.md +++ b/unison-src/transcripts/idempotent/any-extract.md @@ -3,7 +3,7 @@ ``` ucm :hide scratch/main> builtins.mergeio -scratch/main> load unison-src/transcripts-using-base/base.u +scratch/main> load "unison-src/transcripts-using-base/base.u" scratch/main> add ``` diff --git a/unison-src/transcripts/idempotent/api-list-projects-branches.md b/unison-src/transcripts/idempotent/api-list-projects-branches.md index 02d2d2541f..b921833742 100644 --- a/unison-src/transcripts/idempotent/api-list-projects-branches.md +++ b/unison-src/transcripts/idempotent/api-list-projects-branches.md @@ -1,17 +1,17 @@ # List Projects And Branches Test ``` ucm :hide -scratch/main> project.create-empty project-one +scratch/main> project.create-empty "project-one" -scratch/main> project.create-empty project-two +scratch/main> project.create-empty "project-two" -scratch/main> project.create-empty project-three +scratch/main> project.create-empty "project-three" -project-one/main> branch branch-one +project-one/main> branch "branch-one" -project-one/main> branch branch-two +project-one/main> branch "branch-two" -project-one/main> branch branch-three +project-one/main> branch "branch-three" ``` ``` api diff --git a/unison-src/transcripts/idempotent/branch-command.md b/unison-src/transcripts/idempotent/branch-command.md index 67e97a1b4c..af114766f3 100644 --- a/unison-src/transcripts/idempotent/branch-command.md +++ b/unison-src/transcripts/idempotent/branch-command.md @@ -37,14 +37,14 @@ foo/main> branch topic1 Tip: To merge your work back into the main branch, first `switch /main` then `merge /topic1`. -foo/main> branch /topic2 +foo/main> branch "/topic2" Done. I've created the topic2 branch based off of main. Tip: To merge your work back into the main branch, first `switch /main` then `merge /topic2`. -foo/main> branch foo/topic3 +foo/main> branch "foo/topic3" Done. I've created the topic3 branch based off of main. @@ -58,75 +58,75 @@ foo/main> branch main topic4 Tip: To merge your work back into the main branch, first `switch /main` then `merge /topic4`. -foo/main> branch main /topic5 +foo/main> branch main "/topic5" Done. I've created the topic5 branch based off of main. Tip: To merge your work back into the main branch, first `switch /main` then `merge /topic5`. -foo/main> branch main foo/topic6 +foo/main> branch main "foo/topic6" Done. I've created the topic6 branch based off of main. Tip: To merge your work back into the main branch, first `switch /main` then `merge /topic6`. -foo/main> branch /main topic7 +foo/main> branch "/main" topic7 Done. I've created the topic7 branch based off of main. Tip: To merge your work back into the main branch, first `switch /main` then `merge /topic7`. -foo/main> branch /main /topic8 +foo/main> branch "/main" "/topic8" Done. I've created the topic8 branch based off of main. Tip: To merge your work back into the main branch, first `switch /main` then `merge /topic8`. -foo/main> branch /main foo/topic9 +foo/main> branch "/main" "foo/topic9" Done. I've created the topic9 branch based off of main. Tip: To merge your work back into the main branch, first `switch /main` then `merge /topic9`. -foo/main> branch foo/main topic10 +foo/main> branch "foo/main" topic10 Done. I've created the topic10 branch based off of main. Tip: To merge your work back into the main branch, first `switch /main` then `merge /topic10`. -foo/main> branch foo/main /topic11 +foo/main> branch "foo/main" "/topic11" Done. I've created the topic11 branch based off of main. Tip: To merge your work back into the main branch, first `switch /main` then `merge /topic11`. -scratch/main> branch foo/main foo/topic12 +scratch/main> branch "foo/main" "foo/topic12" Done. I've created the topic12 branch based off of main. Tip: To merge your work back into the main branch, first `switch /main` then `merge /topic12`. -foo/main> branch bar/topic +foo/main> branch "bar/topic" Done. I've created the bar/topic branch based off foo/main. -bar/main> branch foo/main topic2 +bar/main> branch "foo/main" topic2 Done. I've created the bar/topic2 branch based off foo/main. -bar/main> branch foo/main /topic3 +bar/main> branch "foo/main" "/topic3" Done. I've created the bar/topic3 branch based off foo/main. -scratch/main> branch foo/main bar/topic4 +scratch/main> branch "foo/main" "bar/topic4" Done. I've created the bar/topic4 branch based off foo/main. @@ -136,19 +136,19 @@ foo/main> branch.empty empty1 Tip: Use `merge /somebranch` to initialize this branch. -foo/main> branch.empty /empty2 +foo/main> branch.empty "/empty2" Done. I've created an empty branch foo/empty2. Tip: Use `merge /somebranch` to initialize this branch. -foo/main> branch.empty foo/empty3 +foo/main> branch.empty "foo/empty3" Done. I've created an empty branch foo/empty3. Tip: Use `merge /somebranch` to initialize this branch. -scratch/main> branch.empty foo/empty4 +scratch/main> branch.empty "foo/empty4" Done. I've created an empty branch foo/empty4. @@ -158,7 +158,7 @@ scratch/main> branch.empty foo/empty4 The `branch` command can create branches named `releases/drafts/*` (because why not). ``` ucm -foo/main> branch releases/drafts/1.2.3 +foo/main> branch "releases/drafts/1.2.3" Done. I've created the releases/drafts/1.2.3 branch based off of main. @@ -166,13 +166,13 @@ foo/main> branch releases/drafts/1.2.3 Tip: To merge your work back into the main branch, first `switch /main` then `merge /releases/drafts/1.2.3`. -foo/main> switch /releases/drafts/1.2.3 +foo/main> switch "/releases/drafts/1.2.3" ``` The `branch` command can't create branches named `releases/*` nor `releases/drafts/*`. ``` ucm :error -foo/main> branch releases/1.2.3 +foo/main> branch "releases/1.2.3" Branch names like releases/1.2.3 are reserved for releases. @@ -181,7 +181,7 @@ foo/main> branch releases/1.2.3 Tip: to draft a new release, try `release.draft 1.2.3`. -foo/main> switch /releases/1.2.3 +foo/main> switch "/releases/1.2.3" foo/releases/1.2.3 does not exist. ``` diff --git a/unison-src/transcripts/idempotent/branch-relative-path.md b/unison-src/transcripts/idempotent/branch-relative-path.md index 67775adbb8..9d89f0a82d 100644 --- a/unison-src/transcripts/idempotent/branch-relative-path.md +++ b/unison-src/transcripts/idempotent/branch-relative-path.md @@ -53,7 +53,7 @@ p1/main> add bonk : ##Nat donk.bonk : ##Nat -p1/main> fork p0/main: zzz +p1/main> fork "p0/main:" zzz Done. @@ -62,7 +62,7 @@ p1/main> find zzz 1. zzz.foo : ##Nat 2. zzz.foo.bar : ##Nat -p1/main> fork p0/main:foo yyy +p1/main> fork "p0/main:foo" yyy Done. @@ -70,7 +70,7 @@ p1/main> find yyy 1. yyy.bar : ##Nat -p0/main> fork p1/main: p0/main:p1 +p0/main> fork "p1/main:" "p0/main:p1" Done. diff --git a/unison-src/transcripts/idempotent/bug-strange-closure.md b/unison-src/transcripts/idempotent/bug-strange-closure.md index 15c5aace2d..6ba081ea2c 100644 --- a/unison-src/transcripts/idempotent/bug-strange-closure.md +++ b/unison-src/transcripts/idempotent/bug-strange-closure.md @@ -1,7 +1,7 @@ ``` ucm :hide scratch/main> builtins.mergeio lib.builtins -scratch/main> load unison-src/transcripts-using-base/doc.md.files/syntax.u +scratch/main> load "unison-src/transcripts-using-base/doc.md.files/syntax.u" ``` We can display the guide before and after adding it to the codebase: diff --git a/unison-src/transcripts/idempotent/builtins.md b/unison-src/transcripts/idempotent/builtins.md index e36c81246d..2166bc0b11 100644 --- a/unison-src/transcripts/idempotent/builtins.md +++ b/unison-src/transcripts/idempotent/builtins.md @@ -3,7 +3,7 @@ ``` ucm :hide scratch/main> builtins.mergeio -scratch/main> load unison-src/transcripts-using-base/base.u +scratch/main> load "unison-src/transcripts-using-base/base.u" scratch/main> add ``` diff --git a/unison-src/transcripts/idempotent/deep-names.md b/unison-src/transcripts/idempotent/deep-names.md index 34d842859d..716cf91a31 100644 --- a/unison-src/transcripts/idempotent/deep-names.md +++ b/unison-src/transcripts/idempotent/deep-names.md @@ -15,9 +15,9 @@ http.z = 8 ``` ucm :hide scratch/main> add -scratch/main> branch /app1 +scratch/main> branch "/app1" -scratch/main> branch /app2 +scratch/main> branch "/app2" ``` Our `app1` project includes the text library twice and the http library twice as direct dependencies. diff --git a/unison-src/transcripts/idempotent/delete-namespace-dependents-check.md b/unison-src/transcripts/idempotent/delete-namespace-dependents-check.md index 0ded266003..cee2a5bd2a 100644 --- a/unison-src/transcripts/idempotent/delete-namespace-dependents-check.md +++ b/unison-src/transcripts/idempotent/delete-namespace-dependents-check.md @@ -35,7 +35,7 @@ myproject/main> add dependent : Nat sub.dependency : Nat -myproject/main> branch /new +myproject/main> branch "/new" Done. I've created the new branch based off of main. diff --git a/unison-src/transcripts/idempotent/delete-project-branch.md b/unison-src/transcripts/idempotent/delete-project-branch.md index 62f93b38b0..dc2a000d98 100644 --- a/unison-src/transcripts/idempotent/delete-project-branch.md +++ b/unison-src/transcripts/idempotent/delete-project-branch.md @@ -9,7 +9,7 @@ foo/main> branch topic Tip: To merge your work back into the main branch, first `switch /main` then `merge /topic`. -foo/topic> delete.branch /topic +foo/topic> delete.branch "/topic" ``` A branch need not be preceded by a forward slash. @@ -35,19 +35,19 @@ foo/main> branch topic Tip: To merge your work back into the main branch, first `switch /main` then `merge /topic`. -scratch/main> delete.branch foo/topic +scratch/main> delete.branch "foo/topic" ``` You can delete the only branch in a project. ``` ucm -foo/main> delete.branch /main +foo/main> delete.branch "/main" ``` You can delete the last branch in the project, a new one will be created. ``` ucm -scratch/main> delete.branch scratch/main +scratch/main> delete.branch "scratch/main" scratch/main> branches @@ -59,9 +59,9 @@ scratch/main> branches If the the last branch isn't /main, then /main will be created. ``` ucm -scratch/main2> delete.branch /main +scratch/main2> delete.branch "/main" -scratch/main2> delete.branch /main2 +scratch/main2> delete.branch "/main2" scratch/other> branches diff --git a/unison-src/transcripts/idempotent/diff-namespace.md b/unison-src/transcripts/idempotent/diff-namespace.md index 64063922aa..969442b9c1 100644 --- a/unison-src/transcripts/idempotent/diff-namespace.md +++ b/unison-src/transcripts/idempotent/diff-namespace.md @@ -45,7 +45,7 @@ scratch/b1> debug.alias.term.force .x .fslkdjflskdjflksjdf ``` ``` ucm -scratch/main> diff.namespace /b1: /b2: +scratch/main> diff.namespace "/b1:" "/b2:" Resolved name conflicts: @@ -105,7 +105,7 @@ scratch/ns1> alias.term helloWorld helloWorld2 Done. -scratch/ns1> branch /ns2 +scratch/ns1> branch "/ns2" Done. I've created the ns2 branch based off of ns1. @@ -116,7 +116,7 @@ scratch/ns1> branch /ns2 Here's what we've done so far: ``` ucm :error -scratch/main> diff.namespace .nothing /ns1: +scratch/main> diff.namespace .nothing "/ns1:" ⚠️ @@ -124,7 +124,7 @@ scratch/main> diff.namespace .nothing /ns1: ``` ``` ucm :error -scratch/main> diff.namespace /ns1: /ns2: +scratch/main> diff.namespace "/ns1:" "/ns2:" The namespaces are identical. ``` @@ -170,7 +170,7 @@ scratch/ns2> update Done. -scratch/main> diff.namespace /ns1: /ns2: +scratch/main> diff.namespace "/ns1:" "/ns2:" Resolved name conflicts: @@ -223,7 +223,7 @@ scratch/ns2> alias.term X.x X'.x Done. -scratch/main> diff.namespace /ns1: /ns2: +scratch/main> diff.namespace "/ns1:" "/ns2:" Resolved name conflicts: @@ -281,7 +281,7 @@ scratch/ns2> alias.term A'.A A''.A Done. -scratch/ns2> branch /ns3 +scratch/ns2> branch "/ns3" Done. I've created the ns3 branch based off of ns2. @@ -303,7 +303,7 @@ scratch/ns2> delete.term.verbose fromJust' Tip: You can use `undo` or use a hash from `reflog` to undo this change. -scratch/main> diff.namespace /ns3: /ns2: +scratch/main> diff.namespace "/ns3:" "/ns2:" Name changes: @@ -324,7 +324,7 @@ scratch/ns3> update Done. -scratch/main> diff.namespace /ns2: /ns3: +scratch/main> diff.namespace "/ns2:" "/ns3:" Updates: @@ -360,14 +360,14 @@ scratch/nsx> add b : Nat forconflicts : Nat -scratch/nsx> branch /nsy +scratch/nsx> branch "/nsy" Done. I've created the nsy branch based off of nsx. Tip: To merge your work back into the nsx branch, first `switch /nsx` then `merge /nsy`. -scratch/nsx> branch /nsz +scratch/nsx> branch "/nsz" Done. I've created the nsz branch based off of nsx. @@ -408,7 +408,7 @@ scratch/nsz> update Done. -scratch/nsy> branch /nsw +scratch/nsy> branch "/nsw" Done. I've created the nsw branch based off of nsy. @@ -425,7 +425,7 @@ scratch/nsw> debug.alias.term.force .forconflicts .b ``` ``` ucm -scratch/main> diff.namespace /nsx: /nsw: +scratch/main> diff.namespace "/nsx:" "/nsw:" New name conflicts: diff --git a/unison-src/transcripts/idempotent/fix-5326.md b/unison-src/transcripts/idempotent/fix-5326.md index cc5d8a12e2..11feaa9de8 100644 --- a/unison-src/transcripts/idempotent/fix-5326.md +++ b/unison-src/transcripts/idempotent/fix-5326.md @@ -189,7 +189,7 @@ D - C - B - A ``` ``` ucm -scratch/main> merge /foo +scratch/main> merge "/foo" I merged scratch/foo into scratch/main. ``` @@ -207,7 +207,7 @@ F - D - C - B - A ``` ``` ucm -scratch/main> merge /bar +scratch/main> merge "/bar" 😶 diff --git a/unison-src/transcripts/idempotent/fix1327.md b/unison-src/transcripts/idempotent/fix1327.md index a6f700bc83..b29d0065b3 100644 --- a/unison-src/transcripts/idempotent/fix1327.md +++ b/unison-src/transcripts/idempotent/fix1327.md @@ -34,7 +34,7 @@ scratch/main> ls 1. bar (##Nat) 2. foo (##Nat) -scratch/main> alias.many 1-2 .ns1_nohistory +scratch/main> alias.many "1-2" .ns1_nohistory Here's what changed in .ns1_nohistory : diff --git a/unison-src/transcripts/idempotent/fix2254.md b/unison-src/transcripts/idempotent/fix2254.md index 694c90acb4..9e577ae806 100644 --- a/unison-src/transcripts/idempotent/fix2254.md +++ b/unison-src/transcripts/idempotent/fix2254.md @@ -48,7 +48,7 @@ scratch/a> add f3 : NeedsA Nat Nat -> Nat g : A Nat Nat Nat Nat -> Nat -scratch/a> branch /a2 +scratch/a> branch "/a2" Done. I've created the a2 branch based off of a. diff --git a/unison-src/transcripts/idempotent/io.md b/unison-src/transcripts/idempotent/io.md index 314a76e1b4..aab446c828 100644 --- a/unison-src/transcripts/idempotent/io.md +++ b/unison-src/transcripts/idempotent/io.md @@ -5,7 +5,7 @@ scratch/main> builtins.merge scratch/main> builtins.mergeio -scratch/main> load unison-src/transcripts-using-base/base.u +scratch/main> load "unison-src/transcripts-using-base/base.u" scratch/main> add ``` diff --git a/unison-src/transcripts/idempotent/numbered-args.md b/unison-src/transcripts/idempotent/numbered-args.md index 1b6166f0d4..3d2ac77c32 100644 --- a/unison-src/transcripts/idempotent/numbered-args.md +++ b/unison-src/transcripts/idempotent/numbered-args.md @@ -117,7 +117,7 @@ scratch/main> find 6. qux : Text 7. builtin type Text -scratch/main> view 2-4 +scratch/main> view "2-4" baz : Text baz = "baz" @@ -142,7 +142,7 @@ scratch/main> find 6. qux : Text 7. builtin type Text -scratch/main> view 1-3 4 5-6 +scratch/main> view "1-3" 4 "5-6" bar : Text bar = "bar" diff --git a/unison-src/transcripts/idempotent/pull-errors.md b/unison-src/transcripts/idempotent/pull-errors.md index bb1746e231..3784241ce6 100644 --- a/unison-src/transcripts/idempotent/pull-errors.md +++ b/unison-src/transcripts/idempotent/pull-errors.md @@ -1,5 +1,5 @@ ``` ucm :error -test/main> pull @aryairani/test-almost-empty/main lib.base_latest +test/main> pull "@aryairani/test-almost-empty/main" lib.base_latest The use of `pull` to install libraries is now deprecated. Going forward, you can use @@ -10,7 +10,7 @@ test/main> pull @aryairani/test-almost-empty/main lib.base_latest I installed @aryairani/test-almost-empty/main as aryairani_test_almost_empty_main. -test/main> pull @aryairani/test-almost-empty/main a.b +test/main> pull "@aryairani/test-almost-empty/main" a.b ⚠️ @@ -22,13 +22,13 @@ test/main> pull @aryairani/test-almost-empty/main a.b You can run `help pull` for more information on using `pull`. -test/main> pull @aryairani/test-almost-empty/main a +test/main> pull "@aryairani/test-almost-empty/main" a I think you want to merge @aryairani/test-almost-empty/main into the a branch, but it doesn't exist. If you want, you can create it with `branch.empty a`, and then `pull` again. -test/main> pull @aryairani/test-almost-empty/main .a +test/main> pull "@aryairani/test-almost-empty/main" .a ⚠️ diff --git a/unison-src/transcripts/idempotent/records.md b/unison-src/transcripts/idempotent/records.md index 40ab77e278..a451e517f2 100644 --- a/unison-src/transcripts/idempotent/records.md +++ b/unison-src/transcripts/idempotent/records.md @@ -3,7 +3,7 @@ Ensure that Records keep their syntax after being added to the codebase ``` ucm :hide scratch/main> builtins.merge -scratch/main> load unison-src/transcripts-using-base/base.u +scratch/main> load "unison-src/transcripts-using-base/base.u" ``` ## Record with 1 field diff --git a/unison-src/transcripts/idempotent/reflog.md b/unison-src/transcripts/idempotent/reflog.md index 357ffb6200..127a52f46e 100644 --- a/unison-src/transcripts/idempotent/reflog.md +++ b/unison-src/transcripts/idempotent/reflog.md @@ -51,7 +51,7 @@ scratch/main> add y : Nat -scratch/main> branch /other +scratch/main> branch "/other" Done. I've created the other branch based off of main. diff --git a/unison-src/transcripts/idempotent/release-draft-command.md b/unison-src/transcripts/idempotent/release-draft-command.md index db40f0a607..c1c247be23 100644 --- a/unison-src/transcripts/idempotent/release-draft-command.md +++ b/unison-src/transcripts/idempotent/release-draft-command.md @@ -35,7 +35,7 @@ Now, the `release.draft` demo: `release.draft` accepts a single semver argument. ``` ucm -foo/main> release.draft 1.2.3 +foo/main> release.draft "1.2.3" 😎 Great! I've created a draft release for you at /releases/drafts/1.2.3. @@ -55,7 +55,7 @@ foo/main> release.draft 1.2.3 It's an error to try to create a `releases/drafts/x.y.z` branch that already exists. ``` ucm :error -foo/main> release.draft 1.2.3 +foo/main> release.draft "1.2.3" foo/releases/drafts/1.2.3 already exists. You can switch to it with `switch foo/releases/drafts/1.2.3`. diff --git a/unison-src/transcripts/idempotent/reset.md b/unison-src/transcripts/idempotent/reset.md index 2cd116f87c..6f321049b9 100644 --- a/unison-src/transcripts/idempotent/reset.md +++ b/unison-src/transcripts/idempotent/reset.md @@ -153,7 +153,7 @@ foo/main> update Done. -foo/empty> reset /main: +foo/empty> reset "/main:" Done. diff --git a/unison-src/transcripts/idempotent/switch-command.md b/unison-src/transcripts/idempotent/switch-command.md index 2361485802..509971f470 100644 --- a/unison-src/transcripts/idempotent/switch-command.md +++ b/unison-src/transcripts/idempotent/switch-command.md @@ -53,13 +53,13 @@ forward slash (which makes it unambiguous). ``` ucm scratch/main> switch foo -scratch/main> switch foo/topic +scratch/main> switch "foo/topic" foo/main> switch topic -foo/main> switch /topic +foo/main> switch "/topic" -foo/main> switch bar/ +foo/main> switch "bar/" ``` It's an error to try to switch to something ambiguous. @@ -79,20 +79,20 @@ foo/main> switch bar It's an error to try to switch to something that doesn't exist, of course. ``` ucm :error -scratch/main> switch foo/no-such-branch +scratch/main> switch "foo/no-such-branch" foo/no-such-branch does not exist. ``` ``` ucm :error -scratch/main> switch no-such-project +scratch/main> switch "no-such-project" Neither project no-such-project nor branch /no-such-project exists. ``` ``` ucm :error -foo/main> switch no-such-project-or-branch +foo/main> switch "no-such-project-or-branch" Neither project no-such-project-or-branch nor branch /no-such-project-or-branch exists. diff --git a/unison-src/transcripts/idempotent/tab-completion.md b/unison-src/transcripts/idempotent/tab-completion.md index 83aa787539..e99a300c6b 100644 --- a/unison-src/transcripts/idempotent/tab-completion.md +++ b/unison-src/transcripts/idempotent/tab-completion.md @@ -180,7 +180,7 @@ scratch/main> update.old type Foo add : a -> a -scratch/main> debug.tab-complete delete.type Foo +scratch/main> debug.tab-complete "delete.type" Foo * Foo Foo. diff --git a/unison-src/transcripts/idempotent/textfind.md b/unison-src/transcripts/idempotent/textfind.md index 96bda8abba..b782951eff 100644 --- a/unison-src/transcripts/idempotent/textfind.md +++ b/unison-src/transcripts/idempotent/textfind.md @@ -113,7 +113,7 @@ scratch/main> text.find.all hi Tip: Try `edit 1` or `edit 1-2` to bring these into your scratch file. -scratch/main> view 1-5 +scratch/main> view "1-5" bar : Nat bar = match "well hi there" with @@ -154,7 +154,7 @@ scratch/main> grep quaffle Tip: Try `edit 1` to bring this into your scratch file. -scratch/main> view 1-5 +scratch/main> view "1-5" baz : [Text] baz = ["an", "quaffle", "tres"] @@ -169,7 +169,7 @@ scratch/main> text.find "interesting const" Tip: Try `edit 1` to bring this into your scratch file. -scratch/main> view 1-5 +scratch/main> view "1-5" foo : Nat foo = diff --git a/unison-src/transcripts/merge.md b/unison-src/transcripts/merge.md index 6b759f44ce..e3ecdcc80c 100644 --- a/unison-src/transcripts/merge.md +++ b/unison-src/transcripts/merge.md @@ -43,7 +43,7 @@ scratch/bob> add ``` Merge result: ``` ucm -scratch/alice> merge /bob +scratch/alice> merge "/bob" scratch/alice> view foo bar ``` @@ -84,7 +84,7 @@ scratch/bob> add ``` Merge result: ``` ucm -scratch/alice> merge /bob +scratch/alice> merge "/bob" scratch/alice> view foo bar ``` @@ -136,7 +136,7 @@ scratch/bob> add ``` Merge result: ``` ucm -scratch/alice> merge /bob +scratch/alice> merge "/bob" scratch/alice> view foo bar scratch/alice> display bar ``` @@ -202,7 +202,7 @@ scratch/bob> display foo ``` Merge result: ``` ucm -scratch/alice> merge /bob +scratch/alice> merge "/bob" scratch/alice> view foo bar baz scratch/alice> display foo ``` @@ -272,7 +272,7 @@ scratch/bob> display foo Merge result: ``` ucm -scratch/alice> merge /bob +scratch/alice> merge "/bob" scratch/alice> view foo bar baz scratch/alice> display foo ``` @@ -318,7 +318,7 @@ scratch/bob> delete.term foo Merge result: ``` ucm -scratch/alice> merge /bob +scratch/alice> merge "/bob" scratch/alice> view foo ``` @@ -393,7 +393,7 @@ scratch/main> builtins.mergeio lib.builtins ``` ucm scratch/main> branch alice scratch/main> branch bob -scratch/alice> merge /bob +scratch/alice> merge "/bob" ``` ``` ucm :hide @@ -421,7 +421,7 @@ foo = "foo" ``` ucm scratch/alice> add -scratch/alice> merge /bob +scratch/alice> merge "/bob" ``` ``` ucm :hide @@ -449,7 +449,7 @@ foo = "foo" ``` ucm scratch/bob> add -scratch/alice> merge /bob +scratch/alice> merge "/bob" ``` ``` ucm :hide @@ -460,7 +460,7 @@ scratch/main> project.delete scratch ``` ucm scratch/main> branch topic -scratch/main> merge /topic +scratch/main> merge "/topic" ``` ``` ucm :hide @@ -505,7 +505,7 @@ bar = foo ++ " - " ++ foo ``` ucm :error scratch/bob> add -scratch/alice> merge /bob +scratch/alice> merge "/bob" ``` ``` ucm :hide @@ -555,7 +555,7 @@ scratch/bob> update ``` ``` ucm :error -scratch/alice> merge /bob +scratch/alice> merge "/bob" ``` ``` ucm :hide @@ -616,7 +616,7 @@ baz = "bobs baz" scratch/bob> update ``` ``` ucm :error -scratch/alice> merge /bob +scratch/alice> merge "/bob" ``` ``` ucm @@ -663,7 +663,7 @@ unique type Foo = MkFoo Nat Text scratch/bob> update ``` ``` ucm :error -scratch/alice> merge /bob +scratch/alice> merge "/bob" ``` ``` ucm :hide @@ -703,7 +703,7 @@ Bob's renames `Qux` to `BobQux`: scratch/bob> move.term Foo.Qux Foo.BobQux ``` ``` ucm :error -scratch/alice> merge /bob +scratch/alice> merge "/bob" ``` ``` ucm :hide @@ -1006,7 +1006,7 @@ Attempt to merge: scratch/bob> update ``` ``` ucm :error -scratch/alice> merge /bob +scratch/alice> merge "/bob" ``` Resolve conflicts and commit: @@ -1099,7 +1099,7 @@ scratch/bob> add ``` ``` ucm :error -scratch/alice> merge /bob +scratch/alice> merge "/bob" ``` ``` ucm :hide @@ -1140,7 +1140,7 @@ scratch/bob> add ``` ``` ucm :error -scratch/alice> merge /bob +scratch/alice> merge "/bob" ``` ``` ucm :hide @@ -1186,7 +1186,7 @@ scratch/bob> add ``` ``` ucm :error -scratch/alice> merge /bob +scratch/alice> merge "/bob" ``` ``` ucm :hide @@ -1220,7 +1220,7 @@ scratch/alice> delete.term Foo.Bar Bob's branch: ``` ucm :hide -scratch/main> branch /bob +scratch/main> branch "/bob" ``` ``` unison :hide @@ -1233,7 +1233,7 @@ scratch/bob> add ``` ``` ucm :error -scratch/alice> merge /bob +scratch/alice> merge "/bob" ``` ``` ucm :hide @@ -1281,7 +1281,7 @@ scratch/bob> add ``` ``` ucm :error -scratch/alice> merge /bob +scratch/alice> merge "/bob" ``` ``` ucm :hide @@ -1366,7 +1366,7 @@ scratch/bob> add ``` ``` ucm :error -scratch/alice> merge /bob +scratch/alice> merge "/bob" ``` ``` ucm :hide @@ -1432,7 +1432,7 @@ scratch/bob> add Now we merge: ``` ucm -scratch/alice> merge /bob +scratch/alice> merge "/bob" ``` ``` ucm :hide @@ -1477,7 +1477,7 @@ scratch/bob> add ``` ``` ucm -scratch/alice> merge /bob +scratch/alice> merge "/bob" ``` ``` ucm :hide @@ -1581,7 +1581,7 @@ When we try to merge Bob into Alice, we should see both versions of `baz`, with the underlying namespace. ``` ucm :error -scratch/alice> merge /bob +scratch/alice> merge "/bob" ``` But `bar` was put into the scratch file instead. @@ -1636,8 +1636,8 @@ b = 2 ``` ucm scratch/carol> add -scratch/bob> merge /alice -scratch/carol> merge /bob +scratch/bob> merge "/alice" +scratch/carol> merge "/bob" scratch/carol> history ``` @@ -1696,7 +1696,7 @@ scratch/alice> update ``` ``` ucm -scratch/alice> merge /bob +scratch/alice> merge "/bob" ``` ``` ucm :hide @@ -1726,7 +1726,7 @@ scratch/bob> move.term Foo.Lca Foo.Bob ``` ``` ucm :error -scratch/alice> merge /bob +scratch/alice> merge "/bob" ``` ``` ucm @@ -1789,7 +1789,7 @@ scratch/bob> update Note Bob's `hello` references `foo` (Alice's name), not `bar` (Bob's name). ``` ucm :error -scratch/alice> merge /bob +scratch/alice> merge "/bob" ``` ``` ucm :hide diff --git a/unison-src/transcripts/merge.output.md b/unison-src/transcripts/merge.output.md index 8051f25281..3594cc6816 100644 --- a/unison-src/transcripts/merge.output.md +++ b/unison-src/transcripts/merge.output.md @@ -64,7 +64,7 @@ scratch/bob> add Merge result: ``` ucm -scratch/alice> merge /bob +scratch/alice> merge "/bob" I merged scratch/bob into scratch/alice. @@ -121,7 +121,7 @@ scratch/bob> add Merge result: ``` ucm -scratch/alice> merge /bob +scratch/alice> merge "/bob" I merged scratch/bob into scratch/alice. @@ -192,7 +192,7 @@ scratch/bob> add Merge result: ``` ucm -scratch/alice> merge /bob +scratch/alice> merge "/bob" I merged scratch/bob into scratch/alice. @@ -285,7 +285,7 @@ scratch/bob> display foo Merge result: ``` ucm -scratch/alice> merge /bob +scratch/alice> merge "/bob" I merged scratch/bob into scratch/alice. @@ -387,7 +387,7 @@ scratch/bob> display foo Merge result: ``` ucm -scratch/alice> merge /bob +scratch/alice> merge "/bob" I merged scratch/bob into scratch/alice. @@ -460,7 +460,7 @@ scratch/bob> delete.term foo Merge result: ``` ucm -scratch/alice> merge /bob +scratch/alice> merge "/bob" I merged scratch/bob into scratch/alice. @@ -576,7 +576,7 @@ scratch/main> branch bob Tip: To merge your work back into the main branch, first `switch /main` then `merge /bob`. -scratch/alice> merge /bob +scratch/alice> merge "/bob" 😶 @@ -625,7 +625,7 @@ scratch/alice> add foo : Text -scratch/alice> merge /bob +scratch/alice> merge "/bob" 😶 @@ -674,7 +674,7 @@ scratch/bob> add foo : Text -scratch/alice> merge /bob +scratch/alice> merge "/bob" I fast-forward merged scratch/bob into scratch/alice. ``` @@ -693,7 +693,7 @@ scratch/main> branch topic Tip: To merge your work back into the main branch, first `switch /main` then `merge /topic`. -scratch/main> merge /topic +scratch/main> merge "/topic" 😶 @@ -755,7 +755,7 @@ scratch/bob> add bar : Text -scratch/alice> merge /bob +scratch/alice> merge "/bob" I couldn't automatically merge scratch/bob into scratch/alice. However, I've added the definitions that need attention to the @@ -834,7 +834,7 @@ scratch/bob> update ``` ``` ucm :error -scratch/alice> merge /bob +scratch/alice> merge "/bob" I couldn't automatically merge scratch/bob into scratch/alice. However, I've added the definitions that need attention to the @@ -924,7 +924,7 @@ scratch/bob> update ``` ``` ucm :error -scratch/alice> merge /bob +scratch/alice> merge "/bob" I couldn't automatically merge scratch/bob into scratch/alice. However, I've added the definitions that need attention to the @@ -1019,7 +1019,7 @@ scratch/bob> update ``` ``` ucm :error -scratch/alice> merge /bob +scratch/alice> merge "/bob" I couldn't automatically merge scratch/bob into scratch/alice. However, I've added the definitions that need attention to the @@ -1092,7 +1092,7 @@ scratch/bob> move.term Foo.Qux Foo.BobQux ``` ``` ucm :error -scratch/alice> merge /bob +scratch/alice> merge "/bob" I couldn't automatically merge scratch/bob into scratch/alice. However, I've added the definitions that need attention to the @@ -1602,7 +1602,7 @@ scratch/bob> update ``` ``` ucm :error -scratch/alice> merge /bob +scratch/alice> merge "/bob" I couldn't automatically merge scratch/bob into scratch/alice. However, I've added the definitions that need attention to the @@ -1765,7 +1765,7 @@ scratch/bob> add ``` ``` ucm :error -scratch/alice> merge /bob +scratch/alice> merge "/bob" Sorry, I wasn't able to perform the merge: @@ -1827,7 +1827,7 @@ scratch/bob> add ``` ``` ucm :error -scratch/alice> merge /bob +scratch/alice> merge "/bob" Sorry, I wasn't able to perform the merge: @@ -1888,7 +1888,7 @@ scratch/bob> add ``` ``` ucm :error -scratch/alice> merge /bob +scratch/alice> merge "/bob" Sorry, I wasn't able to perform the merge: @@ -1937,7 +1937,7 @@ scratch/alice> delete.term Foo.Bar Bob's branch: ``` ucm :hide -scratch/main> branch /bob +scratch/main> branch "/bob" ``` ``` unison :hide @@ -1950,7 +1950,7 @@ scratch/bob> add ``` ``` ucm :error -scratch/alice> merge /bob +scratch/alice> merge "/bob" Sorry, I wasn't able to perform the merge: @@ -2013,7 +2013,7 @@ scratch/bob> add ``` ``` ucm :error -scratch/alice> merge /bob +scratch/alice> merge "/bob" On scratch/alice, the type A.inner.X is an alias of A. I'm not able to perform a merge when a type exists nested under an @@ -2119,7 +2119,7 @@ scratch/bob> add ``` ``` ucm :error -scratch/alice> merge /bob +scratch/alice> merge "/bob" Sorry, I wasn't able to perform the merge: @@ -2266,7 +2266,7 @@ scratch/bob> add Now we merge: ``` ucm -scratch/alice> merge /bob +scratch/alice> merge "/bob" I merged scratch/bob into scratch/alice. ``` @@ -2379,7 +2379,7 @@ scratch/bob> add ``` ``` ucm -scratch/alice> merge /bob +scratch/alice> merge "/bob" I merged scratch/bob into scratch/alice. ``` @@ -2611,7 +2611,7 @@ When we try to merge Bob into Alice, we should see both versions of `baz`, with the underlying namespace. ``` ucm :error -scratch/alice> merge /bob +scratch/alice> merge "/bob" I couldn't automatically merge scratch/bob into scratch/alice. However, I've added the definitions that need attention to the @@ -2773,11 +2773,11 @@ scratch/carol> add a : ##Nat b : ##Nat -scratch/bob> merge /alice +scratch/bob> merge "/alice" I merged scratch/alice into scratch/bob. -scratch/carol> merge /bob +scratch/carol> merge "/bob" I merged scratch/bob into scratch/carol. @@ -2919,7 +2919,7 @@ scratch/alice> update ``` ``` ucm -scratch/alice> merge /bob +scratch/alice> merge "/bob" I merged scratch/bob into scratch/alice. ``` @@ -2987,7 +2987,7 @@ scratch/bob> move.term Foo.Lca Foo.Bob ``` ``` ucm :error -scratch/alice> merge /bob +scratch/alice> merge "/bob" I couldn't automatically merge scratch/bob into scratch/alice. However, I've added the definitions that need attention to the @@ -3024,6 +3024,7 @@ type Bar ``` ``` ucm +scratch/merge-bob-into-alice> ``` ``` unison @@ -3176,7 +3177,7 @@ scratch/bob> update Note Bob's `hello` references `foo` (Alice's name), not `bar` (Bob's name). ``` ucm :error -scratch/alice> merge /bob +scratch/alice> merge "/bob" I couldn't automatically merge scratch/bob into scratch/alice. However, I've added the definitions that need attention to the diff --git a/unison-syntax/src/Unison/Syntax/Lexer/Unison.hs b/unison-syntax/src/Unison/Syntax/Lexer/Unison.hs index 6eb51da9cb..510f0d24a9 100644 --- a/unison-syntax/src/Unison/Syntax/Lexer/Unison.hs +++ b/unison-syntax/src/Unison/Syntax/Lexer/Unison.hs @@ -6,6 +6,7 @@ module Unison.Syntax.Lexer.Unison Pos (..), Lexeme (..), lexer, + lexer', preParse, escapeChars, debugFilePreParse, @@ -242,9 +243,20 @@ showErrorFancy = \case GT -> "greater than " P.ErrorCustom a -> P.showErrorComponent a +lexer' :: String -> String -> Either (EP.ParseErrorBundle String (Token Err)) [Token Lexeme] +lexer' scope = flip S.evalState env0 . P.runParserT (lexemes eof) scope + where + eof :: P [Token Lexeme] + eof = P.try do + p <- P.eof >> posP + n <- maybe 0 (const 1) <$> S.gets opening + l <- S.gets layout + pure $ replicate (length l + n) (Token Close p p) + env0 = initialEnv scope + lexer :: String -> String -> [Token Lexeme] lexer scope rem = - case flip S.evalState env0 $ P.runParserT (lexemes eof) scope rem of + case lexer' scope rem of Left e -> let errsWithSourcePos = fst $ @@ -282,12 +294,6 @@ lexer scope rem = in errsWithSourcePos >>= errorToTokens Right ts -> postLex $ Token (Open scope) topLeftCorner topLeftCorner : ts where - eof :: P [Token Lexeme] - eof = P.try do - p <- P.eof >> posP - n <- maybe 0 (const 1) <$> S.gets opening - l <- S.gets layout - pure $ replicate (length l + n) (Token Close p p) errorItemToString :: EP.ErrorItem Char -> String errorItemToString = \case (P.Tokens ts) -> Foldable.toList ts @@ -295,7 +301,6 @@ lexer scope rem = (P.EndOfInput) -> "end of input" customErrs es = [Err <$> e | P.ErrorCustom e <- toList es] toPos (P.SourcePos _ line col) = Pos (P.unPos line) (P.unPos col) - env0 = initialEnv scope -- | hacky postprocessing pass to do some cleanup of stuff that's annoying to -- fix without adding more state to the lexer: