diff --git a/parser-typechecker/src/Unison/Codebase/Branch.hs b/parser-typechecker/src/Unison/Codebase/Branch.hs index 2e981501c9..b62bb6351e 100644 --- a/parser-typechecker/src/Unison/Codebase/Branch.hs +++ b/parser-typechecker/src/Unison/Codebase/Branch.hs @@ -73,6 +73,7 @@ module Unison.Codebase.Branch -- *** Libdep manipulations withoutLib, withoutTransitiveLibs, + onlyLib, deleteLibdep, deleteLibdeps, @@ -184,6 +185,11 @@ withoutTransitiveLibs b0 = ) in b0 & children .~ newChildren +onlyLib :: Branch0 m -> Branch0 m +onlyLib b = + let newChildren = (Map.singleton NameSegment.libSegment (fromMaybe empty $ Map.lookup NameSegment.libSegment (b ^. children))) + in branch0 mempty mempty newChildren mempty + -- | @deleteLibdep name branch@ deletes the libdep named @name@ from @branch@, if it exists. deleteLibdep :: NameSegment -> Branch0 m -> Branch0 m deleteLibdep dep = diff --git a/parser-typechecker/src/Unison/UnisonFile.hs b/parser-typechecker/src/Unison/UnisonFile.hs index 9613ce1642..521dd02468 100644 --- a/parser-typechecker/src/Unison/UnisonFile.hs +++ b/parser-typechecker/src/Unison/UnisonFile.hs @@ -36,6 +36,8 @@ module Unison.UnisonFile typecheckedUnisonFile, Unison.UnisonFile.rewrite, prepareRewrite, + typeLookupForTypecheckedFile, + typeOfReferentFromTypecheckedUnisonFile, ) where @@ -56,6 +58,7 @@ import Unison.LabeledDependency qualified as LD import Unison.Prelude import Unison.Reference (Reference) import Unison.Reference qualified as Reference +import Unison.Referent (Referent) import Unison.Referent qualified as Referent import Unison.Term (Term) import Unison.Term qualified as Term @@ -355,6 +358,33 @@ declsToTypeLookup uf = where wrangle = Map.fromList . Map.elems +-- | Provides a lookup for all types and terms within the unison file. +typeLookupForTypecheckedFile :: Var v => TypecheckedUnisonFile v a -> TL.TypeLookup v a +typeLookupForTypecheckedFile tf = + TL.TypeLookup + termTypeLookup + (wrangle $ dataDeclarationsId' tf) + (wrangle $ effectDeclarationsId' tf) + where + termTypeLookup = + hashTermsId tf + & Map.elems + & fmap + (\(_ann, termRefId, _wk, _trm, typ) -> (Reference.DerivedId termRefId, typ)) + & Map.fromList + wrangle = Map.fromList . fmap (first Reference.DerivedId) . Map.elems + +-- | Gets the type of a reference from either the parsed file or the codebase. +typeOfReferentFromTypecheckedUnisonFile :: Var v => TypecheckedUnisonFile v a -> Referent -> Maybe (Type v a) +typeOfReferentFromTypecheckedUnisonFile tf = \case + Referent.Ref reference -> + Map.lookup reference typeOfTerms + Referent.Con (ConstructorReference typeReference cid) _type -> do + decl <- Map.lookup typeReference dataDecls <|> (DD.toDataDecl <$> Map.lookup typeReference effectDecls) + DD.typeOfConstructor decl cid + where + TL.TypeLookup {typeOfTerms, dataDecls, effectDecls} = typeLookupForTypecheckedFile tf + -- Returns true if the file has any definitions or watches nonEmpty :: TypecheckedUnisonFile v a -> Bool nonEmpty uf = diff --git a/unison-cli/src/Unison/Codebase/Editor/HandleInput.hs b/unison-cli/src/Unison/Codebase/Editor/HandleInput.hs index e55dae32c9..11b3061afb 100644 --- a/unison-cli/src/Unison/Codebase/Editor/HandleInput.hs +++ b/unison-cli/src/Unison/Codebase/Editor/HandleInput.hs @@ -65,7 +65,8 @@ import Unison.Codebase.Editor.HandleInput.EditNamespace (handleEditNamespace) import Unison.Codebase.Editor.HandleInput.FindAndReplace (handleStructuredFindI, handleStructuredFindReplaceI) import Unison.Codebase.Editor.HandleInput.FormatFile qualified as Format import Unison.Codebase.Editor.HandleInput.InstallLib (handleInstallLib) -import Unison.Codebase.Editor.HandleInput.Load (EvalMode (Sandboxed), evalUnisonFile, handleLoad, loadUnisonFile) +import Unison.Codebase.Editor.HandleInput.Load (EvalMode (Sandboxed), LoadMode (LoadForCommit), evalUnisonFile, handleLoad) +import Unison.Codebase.Editor.HandleInput.Load qualified as Load import Unison.Codebase.Editor.HandleInput.Merge2 (handleMerge) import Unison.Codebase.Editor.HandleInput.MoveAll (handleMoveAll) import Unison.Codebase.Editor.HandleInput.MoveBranch (doMoveBranch) @@ -189,14 +190,16 @@ import UnliftIO.Directory qualified as Directory ------------------------------------------------------------------------------------------------------------------------ -- Main loop -loop :: Either Event Input -> Cli () -loop e = do +loop :: LoadMode -> Either Event Input -> Cli () +loop loadMode e = do case e of Left (UnisonFileChanged sourceName text) -> Cli.time "UnisonFileChanged" do -- We skip this update if it was programmatically generated Cli.getLatestFile >>= \case Just (_, True) -> (#latestFile . _Just . _2) .= False - _ -> loadUnisonFile sourceName text + _ -> case loadMode of + Load.LoadForCommit -> void $ Load.loadUnisonFileForCommit False sourceName text + Load.Normal -> void $ Load.loadUnisonFile sourceName text Right input -> let previewResponse sourceName sr uf = do names <- Cli.currentNames @@ -707,7 +710,7 @@ loop e = do FindI isVerbose fscope ws -> handleFindI isVerbose fscope ws input StructuredFindI _fscope ws -> handleStructuredFindI ws StructuredFindReplaceI ws -> handleStructuredFindReplaceI ws - LoadI maybePath -> handleLoad maybePath + LoadI maybePath -> void $ handleLoad True loadMode maybePath ClearI -> Cli.respond ClearScreen AddI requestedNames -> do description <- inputDescription input @@ -732,6 +735,21 @@ loop e = do currentNames <- Branch.toNames <$> Cli.getCurrentBranch0 let sr = Slurp.slurpFile uf vars Slurp.AddOp currentNames previewResponse sourceName sr uf + CommitI mayScratchFile -> do + uf <- handleLoad False LoadForCommit mayScratchFile + currentPath <- Cli.getCurrentPath + libNames <- + Cli.getCurrentBranch0 + <&> Branch.onlyLib + <&> Branch.toNames + let sr = Slurp.slurpFile uf mempty Slurp.AddOp libNames + let adds = SlurpResult.adds sr + Cli.Env {codebase} <- ask + Cli.runTransaction . Codebase.addDefsToCodebase codebase $ uf + description <- inputDescription input + Cli.stepAt description (Path.unabsolute currentPath, doSlurpAdds adds uf . Branch.onlyLib) + CommitPreviewI mayScratchFile -> do + void $ handleLoad False LoadForCommit mayScratchFile UpdateI optionalPatch requestedNames -> handleUpdate input optionalPatch requestedNames Update2I -> handleUpdate2 PreviewUpdateI requestedNames -> do @@ -1051,6 +1069,8 @@ inputDescription input = DeleteTarget'ProjectBranch _ -> wat DeleteTarget'Project _ -> wat AddI _selection -> pure "add" + CommitI mayScratchFile -> pure ("experimental.commit" <> maybe "" Text.pack mayScratchFile) + CommitPreviewI mayScratchFile -> pure ("experimental.commit.preview" <> maybe "" Text.pack mayScratchFile) UpdateI p0 _selection -> do p <- case p0 of diff --git a/unison-cli/src/Unison/Codebase/Editor/HandleInput/Commit.hs b/unison-cli/src/Unison/Codebase/Editor/HandleInput/Commit.hs new file mode 100644 index 0000000000..9778e47fe9 --- /dev/null +++ b/unison-cli/src/Unison/Codebase/Editor/HandleInput/Commit.hs @@ -0,0 +1,4 @@ +module Unison.Codebase.Editor.HandleInput.Commit (commitDiff) where + +commitDiff :: () +commitDiff = () diff --git a/unison-cli/src/Unison/Codebase/Editor/HandleInput/Load.hs b/unison-cli/src/Unison/Codebase/Editor/HandleInput/Load.hs index a9259fc969..c8e1e80e5a 100644 --- a/unison-cli/src/Unison/Codebase/Editor/HandleInput/Load.hs +++ b/unison-cli/src/Unison/Codebase/Editor/HandleInput/Load.hs @@ -1,6 +1,8 @@ module Unison.Codebase.Editor.HandleInput.Load ( handleLoad, loadUnisonFile, + loadUnisonFileForCommit, + LoadMode (..), EvalMode (..), evalUnisonFile, ) @@ -20,9 +22,14 @@ import Unison.Cli.PrettyPrintUtils qualified as Cli import Unison.Cli.TypeCheck (computeTypecheckingEnvironment) import Unison.Cli.UniqueTypeGuidLookup qualified as Cli import Unison.Codebase qualified as Codebase +import Unison.Codebase.Branch qualified as Branch +import Unison.Codebase.Branch.Names qualified as Branch +import Unison.Codebase.Editor.HandleInput.NamespaceDiffUtils (diffFromTypecheckedUnisonFile) import Unison.Codebase.Editor.HandleInput.RuntimeUtils qualified as RuntimeUtils +import Unison.Codebase.Editor.HandleInput.Update qualified as Update import Unison.Codebase.Editor.Output qualified as Output import Unison.Codebase.Editor.Slurp qualified as Slurp +import Unison.Codebase.Editor.SlurpResult qualified as SlurpResult import Unison.Codebase.Runtime qualified as Runtime import Unison.FileParsers qualified as FileParsers import Unison.Names (Names) @@ -43,8 +50,15 @@ import Unison.UnisonFile (TypecheckedUnisonFile) import Unison.UnisonFile.Names qualified as UF import Unison.WatchKind qualified as WK -handleLoad :: Maybe FilePath -> Cli () -handleLoad maybePath = do +data LoadMode + = Normal + | -- Load a file without any names from the codebase except for library dependencies. + -- This mode is used for _replacing_ the current branch whole-sale with code from a scratch file. + LoadForCommit + deriving (Show, Eq, Ord) + +handleLoad :: Bool -> LoadMode -> Maybe FilePath -> Cli (TypecheckedUnisonFile Symbol Ann) +handleLoad showWatchExprs loadMode maybePath = do latestFile <- Cli.getLatestFile path <- (maybePath <|> fst <$> latestFile) & onNothing (Cli.returnEarly Output.NoUnisonFile) Cli.Env {loadSource} <- ask @@ -53,9 +67,11 @@ handleLoad maybePath = do Cli.InvalidSourceNameError -> Cli.returnEarly $ Output.InvalidSourceName path Cli.LoadError -> Cli.returnEarly $ Output.SourceLoadFailed path Cli.LoadSuccess contents -> pure contents - loadUnisonFile (Text.pack path) contents + case loadMode of + Normal -> loadUnisonFile (Text.pack path) contents + LoadForCommit -> loadUnisonFileForCommit showWatchExprs (Text.pack path) contents -loadUnisonFile :: Text -> Text -> Cli () +loadUnisonFile :: Text -> Text -> Cli (TypecheckedUnisonFile Symbol Ann) loadUnisonFile sourceName text = do Cli.respond $ Output.LoadingFile sourceName currentNames <- Cli.currentNames @@ -71,51 +87,78 @@ loadUnisonFile sourceName text = do when (not (null e')) do Cli.respond $ Output.Evaluated text ppe bindings e' #latestTypecheckedFile .= Just (Right unisonFile) - where - withFile :: - Names -> - Text -> - Text -> - Cli (TypecheckedUnisonFile Symbol Ann) - withFile names sourceName text = do + pure unisonFile + +loadUnisonFileForCommit :: Bool -> Text -> Text -> Cli (TypecheckedUnisonFile Symbol Ann) +loadUnisonFileForCommit showWatchExprs sourceName text = do + Cli.respond $ Output.LoadingFile sourceName + beforeBranch0 <- Cli.getCurrentBranch0 + let beforeBranch0LibOnly = Branch.onlyLib beforeBranch0 + beforePPED <- Cli.currentPrettyPrintEnvDecl + let libNames = Branch.toNames beforeBranch0LibOnly + unisonFile <- withFile libNames sourceName text + let sr = Slurp.slurpFile unisonFile mempty Slurp.CheckOp libNames + let adds = SlurpResult.adds sr + let afterBranch0 = Update.doSlurpAdds adds unisonFile beforeBranch0LibOnly + afterPPED <- Cli.prettyPrintEnvDeclFromNames (Branch.toNames afterBranch0) + (_ppe, diff) <- diffFromTypecheckedUnisonFile unisonFile beforeBranch0 afterBranch0 + let pped = afterPPED `PPED.addFallback` beforePPED + let ppe = PPE.suffixifiedPPE pped + currentPath <- Cli.getCurrentPath + Cli.respondNumbered $ Output.ShowDiffNamespace (Right currentPath) (Right currentPath) ppe diff + when showWatchExprs do + (bindings, e) <- evalUnisonFile Permissive ppe unisonFile [] + let e' = Map.map go e + go (ann, kind, _hash, _uneval, eval, isHit) = (ann, kind, eval, isHit) + when (not (null e')) do + Cli.respond $ Output.Evaluated text ppe bindings e' + #latestTypecheckedFile .= Just (Right unisonFile) + pure unisonFile + +withFile :: + Names -> + Text -> + Text -> + Cli (TypecheckedUnisonFile Symbol Ann) +withFile names sourceName text = do + currentPath <- Cli.getCurrentPath + State.modify' \loopState -> + loopState + & #latestFile .~ Just (Text.unpack sourceName, False) + & #latestTypecheckedFile .~ Nothing + Cli.Env {codebase, generateUniqueName} <- ask + uniqueName <- liftIO generateUniqueName + let parsingEnv = + Parser.ParsingEnv + { uniqueNames = uniqueName, + uniqueTypeGuid = Cli.loadUniqueTypeGuid currentPath, + names + } + unisonFile <- + Cli.runTransaction (Parsers.parseFile (Text.unpack sourceName) (Text.unpack text) parsingEnv) + & onLeftM \err -> Cli.returnEarly (Output.ParseErrors text [err]) + -- set that the file at least parsed (but didn't typecheck) + State.modify' (& #latestTypecheckedFile .~ Just (Left unisonFile)) + typecheckingEnv <- + Cli.runTransaction do + computeTypecheckingEnvironment (FileParsers.ShouldUseTndr'Yes parsingEnv) codebase [] unisonFile + let Result.Result notes maybeTypecheckedUnisonFile = FileParsers.synthesizeFile typecheckingEnv unisonFile + maybeTypecheckedUnisonFile & onNothing do + let namesWithFileDefinitions = UF.addNamesFromUnisonFile unisonFile names + pped <- Cli.prettyPrintEnvDeclFromNames namesWithFileDefinitions + let suffixifiedPPE = PPED.suffixifiedPPE pped + let tes = [err | Result.TypeError err <- toList notes] + cbs = + [ bug + | Result.CompilerBug (Result.TypecheckerBug bug) <- + toList notes + ] + when (not (null tes)) do currentPath <- Cli.getCurrentPath - State.modify' \loopState -> - loopState - & #latestFile .~ Just (Text.unpack sourceName, False) - & #latestTypecheckedFile .~ Nothing - Cli.Env {codebase, generateUniqueName} <- ask - uniqueName <- liftIO generateUniqueName - let parsingEnv = - Parser.ParsingEnv - { uniqueNames = uniqueName, - uniqueTypeGuid = Cli.loadUniqueTypeGuid currentPath, - names - } - unisonFile <- - Cli.runTransaction (Parsers.parseFile (Text.unpack sourceName) (Text.unpack text) parsingEnv) - & onLeftM \err -> Cli.returnEarly (Output.ParseErrors text [err]) - -- set that the file at least parsed (but didn't typecheck) - State.modify' (& #latestTypecheckedFile .~ Just (Left unisonFile)) - typecheckingEnv <- - Cli.runTransaction do - computeTypecheckingEnvironment (FileParsers.ShouldUseTndr'Yes parsingEnv) codebase [] unisonFile - let Result.Result notes maybeTypecheckedUnisonFile = FileParsers.synthesizeFile typecheckingEnv unisonFile - maybeTypecheckedUnisonFile & onNothing do - let namesWithFileDefinitions = UF.addNamesFromUnisonFile unisonFile names - pped <- Cli.prettyPrintEnvDeclFromNames namesWithFileDefinitions - let suffixifiedPPE = PPED.suffixifiedPPE pped - let tes = [err | Result.TypeError err <- toList notes] - cbs = - [ bug - | Result.CompilerBug (Result.TypecheckerBug bug) <- - toList notes - ] - when (not (null tes)) do - currentPath <- Cli.getCurrentPath - Cli.respond (Output.TypeErrors currentPath text suffixifiedPPE tes) - when (not (null cbs)) do - Cli.respond (Output.CompilerBugs text suffixifiedPPE cbs) - Cli.returnEarlyWithoutOutput + Cli.respond (Output.TypeErrors currentPath text suffixifiedPPE tes) + when (not (null cbs)) do + Cli.respond (Output.CompilerBugs text suffixifiedPPE cbs) + Cli.returnEarlyWithoutOutput data EvalMode = Sandboxed | Permissive | Native diff --git a/unison-cli/src/Unison/Codebase/Editor/HandleInput/NamespaceDiffUtils.hs b/unison-cli/src/Unison/Codebase/Editor/HandleInput/NamespaceDiffUtils.hs index 0416672e3e..5d0a7f506b 100644 --- a/unison-cli/src/Unison/Codebase/Editor/HandleInput/NamespaceDiffUtils.hs +++ b/unison-cli/src/Unison/Codebase/Editor/HandleInput/NamespaceDiffUtils.hs @@ -1,6 +1,7 @@ -- | Helpers/utils that have to do with namespace diffs. module Unison.Codebase.Editor.HandleInput.NamespaceDiffUtils ( diffHelper, + diffFromTypecheckedUnisonFile, ) where @@ -24,8 +25,14 @@ import Unison.PrettyPrintEnv qualified as PPE import Unison.PrettyPrintEnvDecl qualified as PPED import Unison.Reference (Reference) import Unison.Reference qualified as Reference +import Unison.Referent qualified as Referent import Unison.Sqlite qualified as Sqlite import Unison.Symbol (Symbol) +import Unison.Type (Type) +import Unison.Typechecker.TypeLookup qualified as TL +import Unison.UnisonFile (TypecheckedUnisonFile) +import Unison.UnisonFile qualified as UF +import Unison.UnisonFile.Names qualified as Names diffHelper :: Branch0 IO -> @@ -48,6 +55,46 @@ diffHelper before after = (Branch.toNames after) diff +-- | Like diffHelper, but allows providing definitions from a file which may not have been added to the codebase +-- yet. +diffFromTypecheckedUnisonFile :: + TypecheckedUnisonFile Symbol Ann -> + Branch0 IO -> + Branch0 IO -> + Cli (PPE.PrettyPrintEnv, OBranchDiff.BranchDiffOutput Symbol Ann) +diffFromTypecheckedUnisonFile tf before after = do + Cli.time "diffFromTypecheckedUnisonFile" do + Cli.Env {codebase} <- ask + hqLength <- Cli.runTransaction Codebase.hashLength + diff <- liftIO (BranchDiff.diff0 before after) + names <- Cli.currentNames + pped <- Cli.prettyPrintEnvDeclFromNames names + let suffixifiedPPE = PPED.suffixifiedPPE pped + let beforeNames = Branch.toNames before + let afterNames = Names.addNamesFromTypeCheckedUnisonFile tf (Branch.toNames after) + fmap (suffixifiedPPE,) do + OBranchDiff.toOutput + (getTypeOfReferent codebase) + (getDeclOrBuiltin codebase) + hqLength + beforeNames + afterNames + diff + where + TL.TypeLookup {dataDecls, effectDecls} = UF.typeLookupForTypecheckedFile tf + referentTypeFromFile :: Referent.Referent -> (Maybe (Type Symbol Ann)) + referentTypeFromFile ref = UF.typeOfReferentFromTypecheckedUnisonFile tf ref + getDeclOrBuiltin :: Codebase m Symbol Ann -> Reference -> Cli (Maybe (DD.DeclOrBuiltin Symbol Ann)) + getDeclOrBuiltin codebase ref = runMaybeT do + hoistMaybe (Map.lookup ref dataDecls <&> DD.Decl . Right) + <|> hoistMaybe ((Map.lookup ref effectDecls) <&> DD.Decl . Left) + <|> (MaybeT (Cli.runTransaction $ declOrBuiltin codebase ref)) + getTypeOfReferent codebase ref = + runMaybeT $ + do + (hoistMaybe $ referentTypeFromFile ref) + <|> (MaybeT . Cli.runTransaction $ Codebase.getTypeOfReferent codebase ref) + declOrBuiltin :: Codebase m Symbol Ann -> Reference -> Sqlite.Transaction (Maybe (DD.DeclOrBuiltin Symbol Ann)) declOrBuiltin codebase r = case r of Reference.Builtin {} -> diff --git a/unison-cli/src/Unison/Codebase/Editor/Input.hs b/unison-cli/src/Unison/Codebase/Editor/Input.hs index 969e6d67f6..fac8381cdb 100644 --- a/unison-cli/src/Unison/Codebase/Editor/Input.hs +++ b/unison-cli/src/Unison/Codebase/Editor/Input.hs @@ -147,6 +147,8 @@ data Input | ClearI | AddI (Set Name) | PreviewAddI (Set Name) + | CommitI (Maybe FilePath) + | CommitPreviewI (Maybe FilePath) | UpdateI OptionalPatch (Set Name) | Update2I | PreviewUpdateI (Set Name) diff --git a/unison-cli/src/Unison/Codebase/Editor/Output.hs b/unison-cli/src/Unison/Codebase/Editor/Output.hs index 24f3ae0448..06830bc435 100644 --- a/unison-cli/src/Unison/Codebase/Editor/Output.hs +++ b/unison-cli/src/Unison/Codebase/Editor/Output.hs @@ -31,7 +31,6 @@ import Unison.Cli.MergeTypes (MergeSourceAndTarget, MergeSourceOrTarget) import Unison.Cli.Share.Projects.Types qualified as Share import Unison.Codebase.Editor.Input import Unison.Codebase.Editor.Output.BranchDiff (BranchDiffOutput) -import Unison.Codebase.Editor.Output.BranchDiff qualified as BD import Unison.Codebase.Editor.Output.PushPull (PushPull) import Unison.Codebase.Editor.RemoteRepo import Unison.Codebase.Editor.SlurpResult (SlurpResult (..)) @@ -661,6 +660,6 @@ isNumberedFailure = \case ShowDiffAfterModifyBranch {} -> False ShowDiffAfterPull {} -> False ShowDiffAfterUndo {} -> False - ShowDiffNamespace _ _ _ bd -> BD.isEmpty bd + ShowDiffNamespace _ _ _ _ -> False ListNamespaceDependencies {} -> False TodoOutput _ todo -> TO.todoScore todo > 0 || not (TO.noConflicts todo) diff --git a/unison-cli/src/Unison/Codebase/TranscriptParser.hs b/unison-cli/src/Unison/Codebase/TranscriptParser.hs index 9746c39f91..b3afd877fe 100644 --- a/unison-cli/src/Unison/Codebase/TranscriptParser.hs +++ b/unison-cli/src/Unison/Codebase/TranscriptParser.hs @@ -50,6 +50,7 @@ import Unison.Cli.ProjectUtils qualified as ProjectUtils import Unison.Codebase (Codebase) import Unison.Codebase qualified as Codebase import Unison.Codebase.Editor.HandleInput qualified as HandleInput +import Unison.Codebase.Editor.HandleInput.Load qualified as Load import Unison.Codebase.Editor.Input (Event (UnisonFileChanged), Input (..)) import Unison.Codebase.Editor.Output qualified as Output import Unison.Codebase.Editor.UCMVersion (UCMVersion) @@ -549,7 +550,7 @@ run verbosity dir stanzas codebase runtime sbRuntime nRuntime config ucmVersion loop case input of Left _ -> s Right inp -> s & #lastInput ?~ inp - Cli.runCli env s1 (HandleInput.loop input) >>= \case + Cli.runCli env s1 (HandleInput.loop Load.Normal input) >>= \case (Cli.Success (), s2) -> next s2 (Cli.Continue, s2) -> next s2 (Cli.HaltRepl, _) -> onHalt diff --git a/unison-cli/src/Unison/CommandLine/InputPatterns.hs b/unison-cli/src/Unison/CommandLine/InputPatterns.hs index 0f6bc12d9c..42c2db9f5f 100644 --- a/unison-cli/src/Unison/CommandLine/InputPatterns.hs +++ b/unison-cli/src/Unison/CommandLine/InputPatterns.hs @@ -18,6 +18,8 @@ module Unison.CommandLine.InputPatterns cd, clear, clone, + commit, + commitPreview, compileScheme, createAuthor, debugClearWatchCache, @@ -190,8 +192,8 @@ import Unison.Name (Name) import Unison.Name qualified as Name import Unison.NameSegment (NameSegment) import Unison.NameSegment qualified as NameSegment -import Unison.Prelude hiding (view) import Unison.Parser.Ann (Ann) +import Unison.Prelude hiding (view) import Unison.Project ( ProjectAndBranch (..), ProjectAndBranchNames (..), @@ -802,6 +804,34 @@ previewAdd = ) $ fmap (Input.PreviewAddI . Set.fromList) . traverse handleNameArg +commit :: InputPattern +commit = + InputPattern + "experimental.commit" + [] + I.Visible + [("scratch file", Optional, filePathArg)] + ( "`experimental.commit` *replaces* all your existing non-lib code with the code from a scratch file. Any code which is not present within the file (aside from your libs) will be removed." + ) + \case + [] -> pure $ Input.CommitI Nothing + [file] -> Input.CommitI . Just <$> unsupportedStructuredArgument "a file name" file + _ -> Left (I.help load) + +commitPreview :: InputPattern +commitPreview = + InputPattern + "experimental.commit.preview" + [] + I.Visible + [("scratch file", Optional, filePathArg)] + ( "`experimental.commit.preview` shows the diff which would be applied if you were to run " <> patternName commit + ) + \case + [] -> pure $ Input.CommitPreviewI Nothing + [file] -> Input.CommitPreviewI . Just <$> unsupportedStructuredArgument "a file name" file + _ -> Left (I.help load) + update :: InputPattern update = InputPattern @@ -3159,6 +3189,8 @@ validInputs = clear, clone, compileScheme, + commit, + commitPreview, createAuthor, debugClearWatchCache, debugDoctor, diff --git a/unison-cli/src/Unison/CommandLine/Main.hs b/unison-cli/src/Unison/CommandLine/Main.hs index 451ec731ba..f17ecf5b59 100644 --- a/unison-cli/src/Unison/CommandLine/Main.hs +++ b/unison-cli/src/Unison/CommandLine/Main.hs @@ -32,6 +32,7 @@ import Unison.Codebase (Codebase) import Unison.Codebase qualified as Codebase import Unison.Codebase.Branch qualified as Branch import Unison.Codebase.Editor.HandleInput qualified as HandleInput +import Unison.Codebase.Editor.HandleInput.Load (LoadMode) import Unison.Codebase.Editor.Input (Event, Input (..)) import Unison.Codebase.Editor.Output (NumberedArgs, Output) import Unison.Codebase.Editor.UCMVersion (UCMVersion) @@ -143,8 +144,9 @@ main :: (CausalHash -> STM ()) -> (Path.Absolute -> STM ()) -> ShouldWatchFiles -> + LoadMode -> IO () -main dir welcome initialPath config initialInputs runtime sbRuntime nRuntime codebase serverBaseUrl ucmVersion notifyBranchChange notifyPathChange shouldWatchFiles = Ki.scoped \scope -> do +main dir welcome initialPath config initialInputs runtime sbRuntime nRuntime codebase serverBaseUrl ucmVersion notifyBranchChange notifyPathChange shouldWatchFiles loadMode = Ki.scoped \scope -> do rootVar <- newEmptyTMVarIO initialRootCausalHash <- Codebase.runTransaction codebase Operations.expectRootCausalHash _ <- Ki.fork scope do @@ -269,7 +271,7 @@ main dir welcome initialPath config initialInputs runtime sbRuntime nRuntime cod loop0 s0 = do let step = do input <- awaitInput s0 - (!result, resultState) <- Cli.runCli env s0 (HandleInput.loop input) + (!result, resultState) <- Cli.runCli env s0 (HandleInput.loop loadMode input) let sNext = case input of Left _ -> resultState Right inp -> resultState & #lastInput ?~ inp diff --git a/unison-cli/src/Unison/Main.hs b/unison-cli/src/Unison/Main.hs index 32e829c0b1..9e13dd63ee 100644 --- a/unison-cli/src/Unison/Main.hs +++ b/unison-cli/src/Unison/Main.hs @@ -64,6 +64,8 @@ import U.Codebase.HashTags (CausalHash) import U.Codebase.Sqlite.Queries qualified as Queries import Unison.Codebase (Codebase, CodebasePath) import Unison.Codebase qualified as Codebase +import Unison.Codebase.Editor.HandleInput.Load (LoadMode) +import Unison.Codebase.Editor.HandleInput.Load qualified as Load import Unison.Codebase.Editor.Input qualified as Input import Unison.Codebase.Execute (execute) import Unison.Codebase.Init (CodebaseInitOptions (..), InitError (..), InitResult (..), SpecifiedCodebase (..)) @@ -93,6 +95,7 @@ import Unison.Version (Version) import Unison.Version qualified as Version import UnliftIO qualified import UnliftIO.Directory (getHomeDirectory) +import UnliftIO.Environment (lookupEnv) type Runtimes = (RTI.Runtime Symbol, RTI.Runtime Symbol, RTI.Runtime Symbol) @@ -138,6 +141,10 @@ main version = do (renderUsageInfo, globalOptions, command) <- parseCLIArgs progName (Text.unpack (Version.gitDescribeWithDate version)) nrtp <- fixNativeRuntimePath (nativeRuntimePath globalOptions) let GlobalOptions {codebasePathOption = mCodePathOption, exitOption, lspFormattingConfig} = globalOptions + loadMode <- + lookupEnv "UNISON_LOAD_MODE" >>= \case + Just "commit" -> pure Load.LoadForCommit + _ -> pure Load.Normal withConfig mCodePathOption \config -> do currentDir <- getCurrentDirectory case command of @@ -190,6 +197,7 @@ main version = do noOpRootNotifier noOpPathNotifier CommandLine.ShouldNotWatchFiles + loadMode Run (RunFromPipe mainName) args -> do e <- safeReadUtf8StdIn case e of @@ -217,6 +225,7 @@ main version = do noOpRootNotifier noOpPathNotifier CommandLine.ShouldNotWatchFiles + loadMode Run (RunCompiled file) args -> BL.readFile file >>= \bs -> try (evaluate $ RTI.decodeStandalone bs) >>= \case @@ -351,6 +360,7 @@ main version = do notifyOnRootChanges notifyOnPathChanges shouldWatchFiles + loadMode Exit -> do Exit.exitSuccess where -- (runtime, sandboxed runtime) @@ -530,8 +540,9 @@ launch :: (CausalHash -> STM ()) -> (Path.Absolute -> STM ()) -> CommandLine.ShouldWatchFiles -> + LoadMode -> IO () -launch version dir config runtime sbRuntime nRuntime codebase inputs serverBaseUrl mayStartingPath initResult notifyRootChange notifyPathChange shouldWatchFiles = do +launch version dir config runtime sbRuntime nRuntime codebase inputs serverBaseUrl mayStartingPath initResult notifyRootChange notifyPathChange shouldWatchFiles loadMode = do showWelcomeHint <- Codebase.runTransaction codebase Queries.doProjectsExist let isNewCodebase = case initResult of CreatedCodebase -> NewlyCreatedCodebase @@ -553,6 +564,7 @@ launch version dir config runtime sbRuntime nRuntime codebase inputs serverBaseU notifyRootChange notifyPathChange shouldWatchFiles + loadMode newtype MarkdownFile = MarkdownFile FilePath diff --git a/unison-cli/unison-cli.cabal b/unison-cli/unison-cli.cabal index 79f8ada36d..fbea509629 100644 --- a/unison-cli/unison-cli.cabal +++ b/unison-cli/unison-cli.cabal @@ -55,6 +55,7 @@ library Unison.Codebase.Editor.HandleInput.Branch Unison.Codebase.Editor.HandleInput.Branches Unison.Codebase.Editor.HandleInput.BranchRename + Unison.Codebase.Editor.HandleInput.Commit Unison.Codebase.Editor.HandleInput.DebugDefinition Unison.Codebase.Editor.HandleInput.DebugFoldRanges Unison.Codebase.Editor.HandleInput.DeleteBranch diff --git a/unison-src/transcripts-round-trip/main.md b/unison-src/transcripts-round-trip/main.md index 7287a7ddba..bfa66f363e 100644 --- a/unison-src/transcripts-round-trip/main.md +++ b/unison-src/transcripts-round-trip/main.md @@ -40,7 +40,7 @@ So we can see the pretty-printed output: This diff should be empty if the two namespaces are equivalent. If it's nonempty, the diff will show us the hashes that differ. -```ucm:error +```ucm .> diff.namespace a1 a2 ``` diff --git a/unison-src/transcripts/commit-command.md b/unison-src/transcripts/commit-command.md new file mode 100644 index 0000000000..02008ed04b --- /dev/null +++ b/unison-src/transcripts/commit-command.md @@ -0,0 +1,63 @@ +```ucm:hide +.> builtins.merge lib +``` + +Add some definitions to the codebase for us to later update. + +```unison:hide +type MyRecord = + { nat : Nat + , text : Text + , bool : Boolean + } + +lib.dep.dependency = 1 +termOne = lib.dep.dependency + 2 +termTwo = lib.dep.dependency + 3 + +addToRecordField : MyRecord -> Nat +addToRecordField rec = nat rec + 10 + +> addToRecordField (MyRecord 9 "hi" true) +``` + +```ucm:hide +.> add +``` + +Should be able to easily change and remove record fields and definitions in a single commit. + +```unison +-- Rename and re-type the `nat` field to `getNat` +-- Remove the `bool` field +type MyRecord = + { getNat : () -> Nat + , text : Text + } + + +-- Update termOne, +termOne = dependency + 20 +-- termTwo is deleted simply by omitting it from the scratch file. + +addToRecordField : MyRecord -> Nat +addToRecordField rec = !(getNat rec) + 10 + +> addToRecordField (MyRecord '9 "hi") +``` + +```ucm +.> experimental.commit.preview +.> experimental.commit +.> find +.> view MyRecord +.> ls MyRecord +.> view addToRecordField +.> view termOne +``` + +This term should be deleted. + +```ucm:error +.> view termTwo +``` diff --git a/unison-src/transcripts/commit-command.output.md b/unison-src/transcripts/commit-command.output.md new file mode 100644 index 0000000000..d02ce1de3c --- /dev/null +++ b/unison-src/transcripts/commit-command.output.md @@ -0,0 +1,253 @@ +Add some definitions to the codebase for us to later update. + +```unison +type MyRecord = + { nat : Nat + , text : Text + , bool : Boolean + } + +lib.dep.dependency = 1 +termOne = lib.dep.dependency + 2 +termTwo = lib.dep.dependency + 3 + +addToRecordField : MyRecord -> Nat +addToRecordField rec = nat rec + 10 + +> addToRecordField (MyRecord 9 "hi" true) +``` + +Should be able to easily change and remove record fields and definitions in a single commit. + +```unison +-- Rename and re-type the `nat` field to `getNat` +-- Remove the `bool` field +type MyRecord = + { getNat : () -> Nat + , text : Text + } + + +-- Update termOne, +termOne = dependency + 20 +-- termTwo is deleted simply by omitting it from the scratch file. + +addToRecordField : MyRecord -> Nat +addToRecordField rec = !(getNat rec) + 10 + +> addToRecordField (MyRecord '9 "hi") +``` + +```ucm + + Loading changes detected in scratch.u. + + I found and typechecked these definitions in scratch.u. If you + do an `add` or `update`, here's how your codebase would + change: + + ⍟ These new definitions are ok to `add`: + + MyRecord.getNat : MyRecord -> 'Nat + MyRecord.getNat.modify : ('Nat ->{g} 'Nat) + -> MyRecord + ->{g} MyRecord + MyRecord.getNat.set : 'Nat -> MyRecord -> MyRecord + + ⍟ These names already exist. You can `update` them to your + new definition: + + type MyRecord + MyRecord.text : MyRecord -> Text + MyRecord.text.modify : (Text ->{g} Text) + -> MyRecord + ->{g} MyRecord + MyRecord.text.set : Text -> MyRecord -> MyRecord + addToRecordField : MyRecord -> Nat + termOne : Nat + + Now evaluating any watch expressions (lines starting with + `>`)... Ctrl+C cancels. + + 16 | > addToRecordField (MyRecord '9 "hi") + ⧩ + 19 + +``` +```ucm +.> experimental.commit.preview + + Loading changes detected in scratch.u. + + Updates: + + 1. type MyRecord + ↓ + 2. type MyRecord + + 3. addToRecordField : MyRecord -> Nat + ↓ + 4. addToRecordField : MyRecord -> Nat + + 5. MyRecord.MyRecord : Nat -> Text -> Boolean -> MyRecord + ↓ + 6. MyRecord.MyRecord : 'Nat -> Text -> MyRecord + + 7. MyRecord.text : MyRecord -> Text + ↓ + 8. MyRecord.text : MyRecord -> Text + + 9. MyRecord.text.modify : (Text ->{g} Text) + -> MyRecord + ->{g} MyRecord + ↓ + 10. MyRecord.text.modify : (Text ->{g} Text) + -> MyRecord + ->{g} MyRecord + + 11. MyRecord.text.set : Text -> MyRecord -> MyRecord + ↓ + 12. MyRecord.text.set : Text -> MyRecord -> MyRecord + + 13. termOne : Nat + ↓ + 14. termOne : Nat + + Added definitions: + + 15. MyRecord.getNat : MyRecord -> 'Nat + 16. MyRecord.getNat.modify : ('Nat ->{g} 'Nat) + -> MyRecord + ->{g} MyRecord + 17. MyRecord.getNat.set : 'Nat -> MyRecord -> MyRecord + + Removed definitions: + + 18. MyRecord.bool : MyRecord -> Boolean + 19. MyRecord.bool.modify : (Boolean ->{g} Boolean) + -> MyRecord + ->{g} MyRecord + 20. MyRecord.nat.modify : (Nat ->{g} Nat) + -> MyRecord + ->{g} MyRecord + 21. MyRecord.nat : MyRecord -> Nat + 22. MyRecord.bool.set : Boolean -> MyRecord -> MyRecord + 23. MyRecord.nat.set : Nat -> MyRecord -> MyRecord + 24. termTwo : Nat + +.> experimental.commit + + Loading changes detected in scratch.u. + + Updates: + + 1. type MyRecord + ↓ + 2. type MyRecord + + 3. addToRecordField : MyRecord -> Nat + ↓ + 4. addToRecordField : MyRecord -> Nat + + 5. MyRecord.MyRecord : Nat -> Text -> Boolean -> MyRecord + ↓ + 6. MyRecord.MyRecord : 'Nat -> Text -> MyRecord + + 7. MyRecord.text : MyRecord -> Text + ↓ + 8. MyRecord.text : MyRecord -> Text + + 9. MyRecord.text.modify : (Text ->{g} Text) + -> MyRecord + ->{g} MyRecord + ↓ + 10. MyRecord.text.modify : (Text ->{g} Text) + -> MyRecord + ->{g} MyRecord + + 11. MyRecord.text.set : Text -> MyRecord -> MyRecord + ↓ + 12. MyRecord.text.set : Text -> MyRecord -> MyRecord + + 13. termOne : Nat + ↓ + 14. termOne : Nat + + Added definitions: + + 15. MyRecord.getNat : MyRecord -> 'Nat + 16. MyRecord.getNat.modify : ('Nat ->{g} 'Nat) + -> MyRecord + ->{g} MyRecord + 17. MyRecord.getNat.set : 'Nat -> MyRecord -> MyRecord + + Removed definitions: + + 18. MyRecord.bool : MyRecord -> Boolean + 19. MyRecord.bool.modify : (Boolean ->{g} Boolean) + -> MyRecord + ->{g} MyRecord + 20. MyRecord.nat.modify : (Nat ->{g} Nat) + -> MyRecord + ->{g} MyRecord + 21. MyRecord.nat : MyRecord -> Nat + 22. MyRecord.bool.set : Boolean -> MyRecord -> MyRecord + 23. MyRecord.nat.set : Nat -> MyRecord -> MyRecord + 24. termTwo : Nat + +.> find + + 1. addToRecordField : MyRecord -> Nat + 2. type MyRecord + 3. MyRecord.getNat : MyRecord -> 'Nat + 4. MyRecord.getNat.modify : ('Nat ->{g} 'Nat) + -> MyRecord + ->{g} MyRecord + 5. MyRecord.getNat.set : 'Nat -> MyRecord -> MyRecord + 6. MyRecord.MyRecord : 'Nat -> Text -> MyRecord + 7. MyRecord.text : MyRecord -> Text + 8. MyRecord.text.modify : (Text ->{g} Text) + -> MyRecord + ->{g} MyRecord + 9. MyRecord.text.set : Text -> MyRecord -> MyRecord + 10. termOne : Nat + + +.> view MyRecord + + type MyRecord = { getNat : 'Nat, text : Text } + +.> ls MyRecord + + 1. MyRecord ('Nat -> Text -> MyRecord) + 2. getNat (MyRecord -> 'Nat) + 3. getNat/ (2 terms) + 4. text (MyRecord -> Text) + 5. text/ (2 terms) + +.> view addToRecordField + + addToRecordField : MyRecord -> Nat + addToRecordField rec = + use Nat + + getNat rec () + 10 + +.> view termOne + + termOne : Nat + termOne = + use Nat + + dependency + 20 + +``` +This term should be deleted. + +```ucm +.> view termTwo + + ⚠️ + + The following names were not found in the codebase. Check your spelling. + termTwo + +``` diff --git a/unison-src/transcripts/diff-namespace.md b/unison-src/transcripts/diff-namespace.md index 5e938a79a5..1ebea24745 100644 --- a/unison-src/transcripts/diff-namespace.md +++ b/unison-src/transcripts/diff-namespace.md @@ -56,7 +56,7 @@ Here's what we've done so far: .> diff.namespace nothing ns1 ``` -```ucm:error +```ucm .> diff.namespace ns1 ns2 ```