diff --git a/parser-typechecker/src/U/Codebase/Branch/Diff.hs b/parser-typechecker/src/U/Codebase/Branch/Diff.hs index c66a3f23d0..e816daf034 100644 --- a/parser-typechecker/src/U/Codebase/Branch/Diff.hs +++ b/parser-typechecker/src/U/Codebase/Branch/Diff.hs @@ -1,5 +1,6 @@ module U.Codebase.Branch.Diff ( TreeDiff (..), + hoistTreeDiff, NameChanges (..), DefinitionDiffs (..), Diff (..), @@ -12,6 +13,7 @@ module U.Codebase.Branch.Diff where import Control.Comonad.Cofree +import Control.Comonad.Cofree qualified as Cofree import Control.Lens (ifoldMap) import Control.Lens qualified as Lens import Data.Functor.Compose (Compose (..)) @@ -20,6 +22,7 @@ import Data.Semialign qualified as Align import Data.Set qualified as Set import Data.These import U.Codebase.Branch +import U.Codebase.Branch qualified as V2Branch import U.Codebase.Branch.Type qualified as Branch import U.Codebase.Causal qualified as Causal import U.Codebase.Reference (Reference) @@ -29,6 +32,7 @@ import Unison.Name (Name) import Unison.Name qualified as Name import Unison.NameSegment (NameSegment) import Unison.Prelude +import Unison.Sqlite qualified as Sqlite import Unison.Util.Monoid (foldMapM, ifoldMapM) import Unison.Util.Relation (Relation) import Unison.Util.Relation qualified as Relation @@ -76,6 +80,10 @@ instance (Applicative m) => Semigroup (TreeDiff m) where instance (Applicative m) => Monoid (TreeDiff m) where mempty = TreeDiff (mempty :< Compose mempty) +hoistTreeDiff :: Functor m => (forall x. m x -> n x) -> TreeDiff m -> TreeDiff n +hoistTreeDiff f (TreeDiff cfr) = + TreeDiff $ Cofree.hoistCofree (\(Compose m) -> Compose (fmap f m)) cfr + -- | A summary of a 'TreeDiff', containing all names added and removed. -- Note that there isn't a clear notion of a name "changing" since conflicts might muddy the notion -- by having multiple copies of both the from and to names, so we just talk about adds and @@ -114,24 +122,26 @@ instance Semigroup NameBasedDiff where NameBasedDiff (terms0 <> terms1) (types0 <> types1) -- | Diff two Branches, returning a tree containing all of the changes -diffBranches :: forall m. (Monad m) => Branch m -> Branch m -> TreeDiff m -diffBranches from to = +diffBranches :: Branch Sqlite.Transaction -> Branch Sqlite.Transaction -> Sqlite.Transaction (TreeDiff Sqlite.Transaction) +diffBranches from to = do + fromChildren <- V2Branch.nonEmptyChildren from + toChildren <- V2Branch.nonEmptyChildren to let termDiffs = diffMap (Branch.terms from) (Branch.terms to) - typeDiffs = diffMap (Branch.types from) (Branch.types to) - defDiff = DefinitionDiffs {termDiffs, typeDiffs} - childDiff :: (Map NameSegment (m (Cofree (Compose (Map NameSegment) m) DefinitionDiffs))) - childDiff = do - Align.align (children from) (children to) + let typeDiffs = diffMap (Branch.types from) (Branch.types to) + let defDiff = DefinitionDiffs {termDiffs, typeDiffs} + let childDiff :: Map NameSegment (Sqlite.Transaction (Cofree (Compose (Map NameSegment) Sqlite.Transaction) DefinitionDiffs)) + childDiff = + Align.align fromChildren toChildren & mapMaybe \case This ca -> Just do -- TODO: For the names index we really don't need to know which exact -- names were removed, we just need to delete from the index using a -- prefix query, this would be faster than crawling to get all the deletes. removedChildBranch <- Causal.value ca - pure . unTreeDiff $ diffBranches removedChildBranch Branch.empty + unTreeDiff <$> diffBranches removedChildBranch Branch.empty That ca -> Just do newChildBranch <- Causal.value ca - pure . unTreeDiff $ diffBranches Branch.empty newChildBranch + unTreeDiff <$> diffBranches Branch.empty newChildBranch These fromC toC | Causal.valueHash fromC == Causal.valueHash toC -> -- This child didn't change. @@ -139,12 +149,13 @@ diffBranches from to = | otherwise -> Just $ do fromChildBranch <- Causal.value fromC toChildBranch <- Causal.value toC - case diffBranches fromChildBranch toChildBranch of + diffBranches fromChildBranch toChildBranch >>= \case TreeDiff (defDiffs :< Compose mchildren) -> do pure $ (defDiffs :< Compose mchildren) - in TreeDiff (defDiff :< Compose childDiff) + pure $ + TreeDiff (defDiff :< Compose childDiff) where - diffMap :: forall ref. (Ord ref) => Map NameSegment (Map ref (m MdValues)) -> Map NameSegment (Map ref (m MdValues)) -> Map NameSegment (Diff ref) + diffMap :: forall ref. (Ord ref) => Map NameSegment (Map ref (Sqlite.Transaction MdValues)) -> Map NameSegment (Map ref (Sqlite.Transaction MdValues)) -> Map NameSegment (Diff ref) diffMap l r = Align.align l r & fmap \case @@ -211,7 +222,10 @@ streamNameChanges namePrefix (TreeDiff (DefinitionDiffs {termDiffs, typeDiffs} : let name = appendName ns in (listifyNames name $ adds diff, listifyNames name $ removals diff) let nameChanges = NameChanges {termNameAdds, termNameRemovals, typeNameAdds, typeNameRemovals} - acc <- f namePrefix nameChanges + acc <- + if nameChanges == mempty + then pure mempty + else f namePrefix nameChanges childAcc <- children & ifoldMapM diff --git a/parser-typechecker/src/Unison/Codebase/SqliteCodebase/Operations.hs b/parser-typechecker/src/Unison/Codebase/SqliteCodebase/Operations.hs index ca2765fae4..2bd44ea5bb 100644 --- a/parser-typechecker/src/Unison/Codebase/SqliteCodebase/Operations.hs +++ b/parser-typechecker/src/Unison/Codebase/SqliteCodebase/Operations.hs @@ -648,7 +648,7 @@ ensureNameLookupForBranchHash getDeclType mayFromBranchHash toBranchHash = do toBranch <- Ops.expectBranchByBranchHash toBranchHash depMounts <- Projects.inferDependencyMounts toBranch <&> fmap (first (coerce @_ @PathSegments . Path.toList)) let depMountPaths = (Path.fromList . coerce) . fst <$> depMounts - let treeDiff = ignoreDepMounts depMountPaths $ BranchDiff.diffBranches fromBranch toBranch + treeDiff <- ignoreDepMounts depMountPaths <$> BranchDiff.diffBranches fromBranch toBranch let namePrefix = Nothing Ops.buildNameLookupForBranchHash mayExistingLookupBH diff --git a/unison-cli/src/Unison/Codebase/Editor/HandleInput.hs b/unison-cli/src/Unison/Codebase/Editor/HandleInput.hs index e0660830b0..5a90c49b98 100644 --- a/unison-cli/src/Unison/Codebase/Editor/HandleInput.hs +++ b/unison-cli/src/Unison/Codebase/Editor/HandleInput.hs @@ -1320,7 +1320,7 @@ loop e = do Cli.runTransaction do fromBranch <- Codebase.expectCausalBranchByCausalHash fromCH >>= V2Causal.value toBranch <- Codebase.expectCausalBranchByCausalHash toCH >>= V2Causal.value - let treeDiff = V2Branch.Diff.diffBranches fromBranch toBranch + treeDiff <- V2Branch.Diff.diffBranches fromBranch toBranch nameChanges <- V2Branch.Diff.allNameChanges Nothing treeDiff pure (DisplayDebugNameDiff nameChanges) Cli.respond output @@ -1909,7 +1909,7 @@ handleDiffNamespaceToPatch description input = do branch1 <- ExceptT (Cli.resolveAbsBranchIdV2 absBranchId1) branch2 <- ExceptT (Cli.resolveAbsBranchIdV2 absBranchId2) lift do - branchDiff <- V2Branch.Diff.nameBasedDiff (V2Branch.Diff.diffBranches branch1 branch2) + branchDiff <- V2Branch.Diff.diffBranches branch1 branch2 >>= V2Branch.Diff.nameBasedDiff termEdits <- (branchDiff ^. #terms) & Relation.domain diff --git a/unison-share-api/src/Unison/Server/Backend.hs b/unison-share-api/src/Unison/Server/Backend.hs index a0362075ce..e2a233a1d3 100644 --- a/unison-share-api/src/Unison/Server/Backend.hs +++ b/unison-share-api/src/Unison/Server/Backend.hs @@ -86,6 +86,7 @@ module Unison.Server.Backend evalDocRef, mkTermDefinition, mkTypeDefinition, + displayTerm, ) where @@ -877,6 +878,7 @@ mungeSyntaxText :: mungeSyntaxText = fmap Syntax.convertElement mkTypeDefinition :: + MonadIO m => Codebase IO Symbol Ann -> PPED.PrettyPrintEnvDecl -> Path.Path -> @@ -887,7 +889,7 @@ mkTypeDefinition :: DisplayObject (AnnotatedText (UST.Element Reference)) (AnnotatedText (UST.Element Reference)) -> - Backend IO TypeDefinition + m TypeDefinition mkTypeDefinition codebase pped namesRoot rootCausal width r docs tp = do let bn = bestNameForType @Symbol (PPED.suffixifiedPPE pped) width r tag <- diff --git a/unison-share-api/src/Unison/Server/Share.hs b/unison-share-api/src/Unison/Server/Share.hs deleted file mode 100644 index 84748d0dbd..0000000000 --- a/unison-share-api/src/Unison/Server/Share.hs +++ /dev/null @@ -1,39 +0,0 @@ --- | Helpers which are specific to the remote share server. -module Unison.Server.Share (relocateToNameRoot) where - -import Control.Lens hiding ((??)) -import Data.List.NonEmpty qualified as NonEmpty -import U.Codebase.HashTags (BranchHash) -import U.Codebase.Sqlite.NameLookups (PathSegments (..)) -import U.Codebase.Sqlite.Operations (NamesPerspective (..)) -import U.Codebase.Sqlite.Operations qualified as Ops -import Unison.Codebase.Path (Path) -import Unison.Codebase.Path qualified as Path -import Unison.Debug qualified as Debug -import Unison.HashQualified qualified as HQ -import Unison.Name (Name) -import Unison.Name qualified as Name -import Unison.NameSegment (NameSegment (..)) -import Unison.Prelude -import Unison.Sqlite qualified as Sqlite - --- | Given an arbitrary query and perspective, find the name root the query belongs in, --- then return that root and the query relocated to that root. --- --- A name root is either a project root or a dependency root. --- E.g. @.myproject.some.namespace -> .myproject@ or @.myproject.lib.base.List -> .myproject.lib.base@ -relocateToNameRoot :: Path -> HQ.HashQualified Name -> BranchHash -> Sqlite.Transaction (NamesPerspective, HQ.HashQualified Name) -relocateToNameRoot perspective query rootBh = do - -- The namespace containing the name path - let nameLocation = case HQ.toName query of - Just name -> - name - & Name.segments - & NonEmpty.init - & Path.fromList - Nothing -> Path.empty - let fullPath = perspective <> nameLocation - Debug.debugM Debug.Server "relocateToNameRoot fullPath" fullPath - namesPerspective@NamesPerspective {relativePerspective} <- Ops.namesPerspectiveForRootAndPath rootBh (PathSegments . coerce . Path.toList $ fullPath) - let reprefixName name = Name.fromReverseSegments $ (NonEmpty.head $ Name.reverseSegments name) NonEmpty.:| (reverse $ coerce relativePerspective) - pure (namesPerspective, reprefixName <$> query) diff --git a/unison-share-api/src/Unison/Server/Share/Definitions.hs b/unison-share-api/src/Unison/Server/Share/Definitions.hs deleted file mode 100644 index a56c59f1d1..0000000000 --- a/unison-share-api/src/Unison/Server/Share/Definitions.hs +++ /dev/null @@ -1,170 +0,0 @@ --- | This module contains implementations of Backend methods which are specialized for Share. --- We should likely move them to the Share repository eventually, but for now it's much easier --- to ensure they're resilient to refactors and changes in the Backend API if they live here. --- --- Perhaps we'll move them when the backing implementation switches to postgres. -module Unison.Server.Share.Definitions (definitionForHQName) where - -import Control.Lens hiding ((??)) -import Control.Monad.Except -import Data.Map qualified as Map -import Data.Set qualified as Set -import U.Codebase.Branch qualified as V2Branch -import U.Codebase.Causal qualified as V2Causal -import U.Codebase.HashTags (CausalHash (..)) -import U.Codebase.Sqlite.NameLookups (PathSegments (..), ReversedName (..)) -import U.Codebase.Sqlite.Operations (NamesPerspective (NamesPerspective)) -import U.Codebase.Sqlite.Operations qualified as Ops -import Unison.Codebase (Codebase) -import Unison.Codebase qualified as Codebase -import Unison.Codebase.Path (Path) -import Unison.Codebase.Path qualified as Path -import Unison.Codebase.Runtime qualified as Rt -import Unison.Codebase.SqliteCodebase.Conversions qualified as CV -import Unison.Codebase.SqliteCodebase.Conversions qualified as Cv -import Unison.Debug qualified as Debug -import Unison.HashQualified qualified as HQ -import Unison.LabeledDependency qualified as LD -import Unison.Name (Name) -import Unison.Name qualified as Name -import Unison.NameSegment (NameSegment (..)) -import Unison.Parser.Ann (Ann) -import Unison.Prelude -import Unison.PrettyPrintEnv qualified as PPE -import Unison.PrettyPrintEnvDecl qualified as PPED -import Unison.PrettyPrintEnvDecl.Sqlite qualified as PPESqlite -import Unison.Reference (TermReference) -import Unison.Reference qualified as Reference -import Unison.Referent qualified as Referent -import Unison.Server.Backend hiding (renderDocRefs) -import Unison.Server.Backend qualified as Backend -import Unison.Server.Doc qualified as Doc -import Unison.Server.NameSearch.Sqlite qualified as SqliteNameSearch -import Unison.Server.Share qualified as Share -import Unison.Server.Types -import Unison.Sqlite qualified as Sqlite -import Unison.Symbol (Symbol) -import Unison.Syntax.HashQualified qualified as HQ (toText) -import Unison.Util.Pretty (Width) - --- | Renders a definition for the given name or hash alongside its documentation. -definitionForHQName :: - -- | The path representing the user's current namesRoot. - -- Searches will be limited to definitions within this path, and names will be relative to - -- this path. - Path -> - -- | The root branch to use - CausalHash -> - Maybe Width -> - -- | Whether to suffixify bindings in the rendered syntax - Suffixify -> - -- | Runtime used to evaluate docs. This should be sandboxed if run on the server. - Rt.Runtime Symbol -> - Codebase IO Symbol Ann -> - -- | The name, hash, or both, of the definition to display. - HQ.HashQualified Name -> - Backend IO DefinitionDisplayResults -definitionForHQName perspective rootHash renderWidth suffixifyBindings rt codebase perspectiveQuery = do - result <- liftIO . Codebase.runTransaction codebase $ do - shallowRoot <- resolveCausalHashV2 (Just rootHash) - let rootBranchHash = V2Causal.valueHash shallowRoot - (perspective, perspectiveQuery) <- addNameIfHashOnly codebase perspective perspectiveQuery shallowRoot - (namesPerspective, locatedQuery) <- Share.relocateToNameRoot perspective perspectiveQuery rootBranchHash - pure $ Right (shallowRoot, namesPerspective, locatedQuery) - (shallowRoot, namesPerspective, query) <- either throwError pure result - let namesRoot = Path.fromList . coerce $ Ops.pathToMountedNameLookup namesPerspective - Debug.debugM Debug.Server "definitionForHQName: (namesPerspective, query)" (namesPerspective, query) - -- Bias towards both relative and absolute path to queries, - -- This allows us to still bias towards definitions outside our namesRoot but within the - -- same tree; - -- e.g. if the query is `map` and we're in `base.trunk.List`, - -- we bias towards `map` and `.base.trunk.List.map` which ensures we still prefer names in - -- `trunk` over those in other releases. - -- ppe which returns names fully qualified to the current namesRoot, not to the codebase root. - let biases = maybeToList $ HQ.toName query - let ppedBuilder deps = fmap (PPED.biasTo biases) . liftIO . Codebase.runTransaction codebase $ PPESqlite.ppedForReferences namesPerspective deps - let nameSearch = SqliteNameSearch.nameSearchForPerspective codebase namesPerspective - dr@(DefinitionResults terms types misses) <- liftIO $ Codebase.runTransaction codebase do - definitionsBySuffixes codebase nameSearch DontIncludeCycles [query] - Debug.debugM Debug.Server "definitionForHQName: found definitions" dr - let width = mayDefaultWidth renderWidth - let docResults :: Name -> Backend IO [(HashQualifiedName, UnisonHash, Doc.Doc)] - docResults name = do - Debug.debugM Debug.Server "definitionForHQName: looking up docs for name" name - docRefs <- liftIO $ docsForDefinitionName codebase nameSearch name - Debug.debugM Debug.Server "definitionForHQName: Found these docs" docRefs - renderDocRefs ppedBuilder width codebase rt docRefs - - let drDeps = definitionResultsDependencies dr - termAndTypePPED <- ppedBuilder drDeps - let fqnTermAndTypePPE = PPED.unsuffixifiedPPE termAndTypePPED - typeDefinitions <- - ifor (typesToSyntax suffixifyBindings width termAndTypePPED types) \ref tp -> do - let hqTypeName = PPE.typeNameOrHashOnly fqnTermAndTypePPE ref - docs <- maybe (pure []) docResults (HQ.toName hqTypeName) - mkTypeDefinition codebase termAndTypePPED namesRoot shallowRoot width ref docs tp - termDefinitions <- - ifor (termsToSyntax suffixifyBindings width termAndTypePPED terms) \reference trm -> do - let referent = Referent.Ref reference - let hqTermName = PPE.termNameOrHashOnly fqnTermAndTypePPE referent - docs <- maybe (pure []) docResults (HQ.toName hqTermName) - mkTermDefinition codebase termAndTypePPED namesRoot shallowRoot width reference docs trm - let renderedDisplayTerms = Map.mapKeys Reference.toText termDefinitions - renderedDisplayTypes = Map.mapKeys Reference.toText typeDefinitions - renderedMisses = fmap HQ.toText misses - pure $ - DefinitionDisplayResults - renderedDisplayTerms - renderedDisplayTypes - renderedMisses - --- | A _hopefully_ temporary solution for the following problem: --- --- When rendering definitions by-hash, we don't know which of the project's dependencies we --- may be in, so we don't know which mount to use when rendering it. --- So, first we do a breadth-first recursive search to find some name for that definition, --- then we can use that name to find the mount and render just as we would if provided a name --- up front. -addNameIfHashOnly :: Codebase m v a -> Path -> HQ.HashQualified Name -> V2Branch.CausalBranch Sqlite.Transaction -> Sqlite.Transaction (Path, HQ.HashQualified Name) -addNameIfHashOnly codebase perspective hqQuery rootCausal = case hqQuery of - HQ.HashOnly sh -> do - let rootBranchHash = V2Causal.valueHash rootCausal - let pathSegments = coerce $ Path.toList perspective - startingPerspective@NamesPerspective {pathToMountedNameLookup} <- Ops.namesPerspectiveForRootAndPath rootBranchHash pathSegments - let findTerm = do - termRefs <- lift $ termReferentsByShortHash codebase sh - termRefs - & altMap \ref -> do - MaybeT $ Ops.recursiveTermNameSearch startingPerspective (CV.referent1to2 ref) - let findType = do - typeRefs <- lift $ typeReferencesByShortHash sh - typeRefs - & altMap \ref -> do - MaybeT $ Ops.recursiveTypeNameSearch startingPerspective (Cv.reference1to2 ref) - mayReversedName <- runMaybeT $ findTerm <|> findType - Debug.debugM Debug.Server "addNameIfHashOnly: found reversed name" mayReversedName - pure $ case mayReversedName of - Nothing -> (perspective, hqQuery) - Just fqnReversedName -> - (Path.fromList . coerce $ pathToMountedNameLookup, HQ.NameOnly (Name.fromReverseSegments $ coerce fqnReversedName)) - _ -> pure (perspective, hqQuery) - -renderDocRefs :: - PPEDBuilder -> - Width -> - Codebase IO Symbol Ann -> - Rt.Runtime Symbol -> - [TermReference] -> - Backend IO [(HashQualifiedName, UnisonHash, Doc.Doc)] -renderDocRefs _ppedBuilder _width _codebase _rt [] = pure [] -renderDocRefs ppedBuilder width codebase rt docRefs = do - eDocs <- for docRefs \ref -> (ref,) <$> liftIO (Backend.evalDocRef rt codebase ref) - let docDeps = foldMap (Doc.dependencies . snd) eDocs <> Set.fromList (LD.TermReference <$> docRefs) - docsPPED <- ppedBuilder docDeps - for eDocs \(ref, eDoc) -> do - let name = bestNameForTerm @Symbol (PPED.suffixifiedPPE docsPPED) width (Referent.Ref ref) - let hash = Reference.toText ref - let renderedDoc = Doc.renderDoc docsPPED eDoc - pure (name, hash, renderedDoc) - -type PPEDBuilder = Set LD.LabeledDependency -> Backend IO PPED.PrettyPrintEnvDecl diff --git a/unison-share-api/src/Unison/Server/Share/FuzzyFind.hs b/unison-share-api/src/Unison/Server/Share/FuzzyFind.hs deleted file mode 100644 index 4c5a37bd5e..0000000000 --- a/unison-share-api/src/Unison/Server/Share/FuzzyFind.hs +++ /dev/null @@ -1,364 +0,0 @@ -{-# LANGUAGE DataKinds #-} -{-# LANGUAGE DeriveAnyClass #-} -{-# LANGUAGE DeriveGeneric #-} -{-# LANGUAGE KindSignatures #-} -{-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE StandaloneDeriving #-} -{-# LANGUAGE TypeOperators #-} -{-# LANGUAGE ViewPatterns #-} -{-# OPTIONS_GHC -Wno-orphans #-} - -module Unison.Server.Share.FuzzyFind where - -import Control.Monad.Except -import Data.Aeson -import Data.Char qualified as Char -import Data.List qualified as List -import Data.OpenApi (ToSchema) -import Data.Text qualified as Text -import Servant - ( QueryParam, - (:>), - ) -import Servant.Docs - ( DocQueryParam (..), - ParamKind (Normal), - ToParam (..), - ToSample (..), - noSamples, - ) -import Servant.OpenApi () -import U.Codebase.Causal qualified as V2Causal -import U.Codebase.HashTags (BranchHash, CausalHash) -import U.Codebase.Sqlite.NameLookups (PathSegments (..)) -import U.Codebase.Sqlite.NameLookups qualified as NameLookups -import U.Codebase.Sqlite.NamedRef qualified as S -import U.Codebase.Sqlite.Operations qualified as SqliteOps -import Unison.Codebase (Codebase) -import Unison.Codebase qualified as Codebase -import Unison.Codebase.Editor.DisplayObject -import Unison.Codebase.Path qualified as Path -import Unison.Codebase.ShortCausalHash qualified as SCH -import Unison.Codebase.SqliteCodebase.Conversions qualified as Cv -import Unison.NameSegment -import Unison.Parser.Ann (Ann) -import Unison.Prelude -import Unison.PrettyPrintEnvDecl qualified as PPED -import Unison.PrettyPrintEnvDecl.Sqlite qualified as PPED -import Unison.Server.Backend (termEntryLabeledDependencies, typeEntryLabeledDependencies) -import Unison.Server.Backend qualified as Backend -import Unison.Server.Syntax (SyntaxText) -import Unison.Server.Types - ( APIGet, - ExactName (..), - HashQualifiedName, - NamedTerm, - NamedType, - UnisonName, - mayDefaultWidth, - ) -import Unison.Symbol (Symbol) -import Unison.Util.Pretty (Width) - -type FuzzyFindAPI = - "find" - :> QueryParam "rootBranch" SCH.ShortCausalHash - :> QueryParam "relativeTo" Path.Path - :> QueryParam "limit" Int - :> QueryParam "renderWidth" Width - :> QueryParam "query" String - :> APIGet [(Alignment, FoundResult)] - -instance ToSample Alignment where - toSamples _ = noSamples - -instance ToParam (QueryParam "limit" Int) where - toParam _ = - DocQueryParam - "limit" - ["1", "10", "20"] - "The maximum number of results to return. Defaults to 10." - Normal - -instance ToParam (QueryParam "query" String) where - toParam _ = - DocQueryParam - "query" - ["foo", "ff", "td nr"] - "Space-separated subsequences to find in the name of a type or term." - Normal - -data FoundTerm = FoundTerm - { bestFoundTermName :: HashQualifiedName, - namedTerm :: NamedTerm - } - deriving (Generic, Show) - -data FoundType = FoundType - { bestFoundTypeName :: HashQualifiedName, - typeDef :: DisplayObject SyntaxText SyntaxText, - namedType :: NamedType - } - deriving (Generic, Show) - -instance ToJSON FoundType where - toJSON (FoundType {bestFoundTypeName, typeDef, namedType}) = - object - [ "bestFoundTypeName" .= bestFoundTypeName, - "typeDef" .= typeDef, - "namedType" .= namedType - ] - -deriving instance ToSchema FoundType - -instance ToJSON FoundTerm where - toJSON (FoundTerm {bestFoundTermName, namedTerm}) = - object - [ "bestFoundTermName" .= bestFoundTermName, - "namedTerm" .= namedTerm - ] - -deriving instance ToSchema FoundTerm - -data FoundResult - = FoundTermResult FoundTerm - | FoundTypeResult FoundType - deriving (Generic, Show) - -instance ToJSON FoundResult where - toJSON = \case - FoundTermResult ft -> object ["tag" .= String "FoundTermResult", "contents" .= ft] - FoundTypeResult ft -> object ["tag" .= String "FoundTypeResult", "contents" .= ft] - -deriving instance ToSchema FoundResult - -instance ToSample FoundResult where - toSamples _ = noSamples - -serveFuzzyFind :: - -- | Whether the root is a scratch root - Bool -> - -- | Whether to search in dependencies - Bool -> - Codebase IO Symbol Ann -> - CausalHash -> - Path.Path -> - Maybe Int -> - Maybe Width -> - Text -> - Backend.Backend IO [(Alignment, FoundResult)] -serveFuzzyFind inScratch searchDependencies codebase rootCausal perspective mayLimit typeWidth query = do - (includeDependencies, bh, namesPerspective, dbTermMatches, dbTypeMatches) <- liftIO . Codebase.runTransaction codebase $ do - shallowRoot <- Backend.resolveCausalHashV2 (Just rootCausal) - let bh = V2Causal.valueHash shallowRoot - namesPerspective@SqliteOps.NamesPerspective {pathToMountedNameLookup = PathSegments pathToPerspective} <- SqliteOps.namesPerspectiveForRootAndPath bh (coerce $ Path.toList perspective) - -- If were browsing at a scratch root we need to include one level of dependencies even if - -- the 'include-dependencies' flag is not set - -- since the projects are all "dependencies" of the scratch root as far as name-lookups - -- are concerned. - let isScratchRootSearch = inScratch && null pathToPerspective - -- Include dependencies if they were explicitly requested OR if we're running a search - -- from a scratch root - let includeDependencies = isScratchRootSearch || searchDependencies - (terms, types) <- SqliteOps.fuzzySearchDefinitions includeDependencies namesPerspective limit preparedQuery - pure (includeDependencies, bh, namesPerspective, terms, types) - let prepareMatch :: S.NamedRef Backend.FoundRef -> (PathSegments, Alignment, UnisonName, [Backend.FoundRef]) - prepareMatch name@(S.NamedRef {S.reversedSegments}) = - let renderedName = NameLookups.reversedNameToNamespaceText reversedSegments - segments = computeMatchSegments preparedQuery name - alignment = Alignment {score = scoreMatch name, result = MatchResult {segments}} - in (NameLookups.reversedNameToPathSegments reversedSegments, alignment, renderedName, [S.ref name]) - let preparedTerms :: [(PathSegments, Alignment, UnisonName, [Backend.FoundRef])] - preparedTerms = - dbTermMatches - <&> \match -> - match - & fmap (\(ref, ct) -> Backend.FoundTermRef $ Cv.referent2to1UsingCT (fromMaybe (error "serveFuzzyFind: CT required but not found") ct) ref) - & prepareMatch - let preparedTypes :: [(PathSegments, Alignment, UnisonName, [Backend.FoundRef])] - preparedTypes = prepareMatch . fmap (Backend.FoundTypeRef . Cv.reference2to1) <$> dbTypeMatches - let alignments :: - ( [ ( PathSegments, - Alignment, - UnisonName, - [Backend.FoundRef] - ) - ] - ) - alignments = - (preparedTerms <> preparedTypes) - & List.sortOn (\(_, Alignment {score}, _, _) -> score) - lift (join <$> traverse (loadEntry includeDependencies bh namesPerspective) alignments) - where - preparedQuery = prepareQuery (Text.unpack query) - limit = fromMaybe 10 mayLimit - loadEntry :: Bool -> BranchHash -> SqliteOps.NamesPerspective -> (PathSegments, Alignment, Text, [Backend.FoundRef]) -> IO [(Alignment, FoundResult)] - loadEntry includeDependencies bh searchPerspective (pathToMatch, a, n, refs) = do - namesPerspective <- - -- If we're including dependencies we need to ensure each match's type signature is - -- rendered using a ppe with that dependency's names. - -- So we re-compute the perspective for each match. - -- - -- If not we can use the same perspective for every match. - if includeDependencies - then Codebase.runTransaction codebase $ SqliteOps.namesPerspectiveForRootAndPath bh (coerce (Path.toList perspective) <> pathToMatch) - else pure searchPerspective - let relativeToBranch = Nothing - entries <- for refs $ - \case - Backend.FoundTermRef r -> do - Left . (r,) <$> Backend.termListEntry codebase relativeToBranch (ExactName (NameSegment n) (Cv.referent1to2 r)) - Backend.FoundTypeRef r -> - Codebase.runTransaction codebase do - Right . (r,) <$> Backend.typeListEntry codebase relativeToBranch (ExactName (NameSegment n) r) - let allLabeledDependencies = foldMap (either (termEntryLabeledDependencies . snd) (typeEntryLabeledDependencies . snd)) entries - pped <- liftIO . Codebase.runTransaction codebase $ PPED.ppedForReferences namesPerspective allLabeledDependencies - let ppe = PPED.suffixifiedPPE pped - Codebase.runTransaction codebase do - for entries \case - Left (r, termEntry) -> - pure - ( a, - FoundTermResult - . FoundTerm - (Backend.bestNameForTerm @Symbol ppe (mayDefaultWidth typeWidth) r) - $ Backend.termEntryToNamedTerm ppe typeWidth termEntry - ) - Right (r, typeEntry) -> do - let namedType = Backend.typeEntryToNamedType typeEntry - let typeName = Backend.bestNameForType @Symbol ppe (mayDefaultWidth typeWidth) r - typeHeader <- Backend.typeDeclHeader codebase ppe r - let ft = FoundType typeName typeHeader namedType - pure (a, FoundTypeResult ft) - --- Scores a matched name by the number of segments. --- Lower is better. -scoreMatch :: S.NamedRef r -> Int -scoreMatch S.NamedRef {S.reversedSegments = NameLookups.ReversedName segments} = length segments - -data Alignment = Alignment - { score :: Int, - result :: MatchResult - } - deriving stock (Generic) - deriving anyclass (ToSchema) - -data MatchResult = MatchResult - { segments :: [MatchSegment] - } - deriving stock (Generic) - deriving anyclass (ToSchema) - -data MatchSegment - = Gap Text - | Match Text - deriving stock (Show, Generic) - deriving anyclass (ToSchema) - -instance ToJSON Alignment where - toJSON (Alignment {score, result}) = - object ["score" .= score, "result" .= result] - -instance ToJSON MatchResult where - toJSON (MatchResult {segments}) = object ["segments" .= toJSON segments] - -instance ToJSON MatchSegment where - toJSON = \case - Gap s -> object ["tag" .= String "Gap", "contents" .= s] - Match s -> object ["tag" .= String "Match", "contents" .= s] - --- After finding a search results with fuzzy find we do some post processing to --- refine the result: --- * Sort: --- we sort both on the FZF score and the number of segments in the FQN --- preferring shorter FQNs over longer. This helps with things like forks --- of base. --- * Dedupe: --- we dedupe on the found refs to avoid having several rows of a --- definition with different names in the result set. --- --- >>> import qualified Data.List.NonEmpty as NonEmpty --- >>> computeMatchSegments ["foo", "baz"] (S.NamedRef (NameLookups.ReversedName ("baz" NonEmpty.:| ["bar", "foo"])) ()) --- [Match "foo",Gap ".bar.",Match "baz"] --- --- >>> computeMatchSegments ["Li", "Ma"] (S.NamedRef (NameLookups.ReversedName ("foldMap" NonEmpty.:| ["List", "data"])) ()) --- [Gap "data.",Match "Li",Gap "st.fold",Match "Ma",Gap "p"] -computeMatchSegments :: - [Text] -> - (S.NamedRef r) -> - [MatchSegment] -computeMatchSegments query (S.NamedRef {reversedSegments}) = - let nameText = NameLookups.reversedNameToNamespaceText reversedSegments - -- This will be a list of _lower-cased_ match segments, but we need to reclaim the - -- casing from the actual name. - matchSegmentShape = List.unfoldr splitIntoSegments (filter (not . Text.null) . map Text.toLower $ query, Text.toLower nameText) - in List.unfoldr reCasifySegments (matchSegmentShape, nameText) - where - -- The actual matching is case-insensitive but we want to preserve the casing of the - -- actual name, so we use the size of match segments to segment the actual name which has - -- the correct case. - reCasifySegments :: ([MatchSegment], Text) -> Maybe (MatchSegment, ([MatchSegment], Text)) - reCasifySegments = \case - ([], _) -> Nothing - (Gap gap : restShape, name) -> - let (actualGap, restName) = Text.splitAt (Text.length gap) name - in Just (Gap actualGap, (restShape, restName)) - (Match match : restShape, name) -> - let (actualMatch, restName) = Text.splitAt (Text.length match) name - in Just (Match actualMatch, (restShape, restName)) - -- Using the query, split the match into chunks of 'match' or 'gap' - splitIntoSegments :: ([Text], Text) -> Maybe (MatchSegment, ([Text], Text)) - splitIntoSegments = \case - (_, "") -> Nothing - ([], rest) -> Just (Gap rest, ([], "")) - (q : qs, name) -> - Text.breakOn q name - & \case - ("", rest) -> - case Text.stripPrefix q rest of - Nothing -> Nothing - Just remainder -> - Just (Match q, (qs, remainder)) - (gap, rest) -> - Just (Gap gap, (q : qs, rest)) - --- | Splits a query into segments, where each segment must appear in order in any matching --- names. --- --- >>> prepareQuery "foo bar baz" --- ["foo","bar","baz"] --- --- Split camel-case style words into segments. --- >>> prepareQuery "fMap" --- ["f","Map"] --- --- Collapse multiple spaces --- >>> prepareQuery "foo barBaz boom" --- ["foo","bar","Baz","boom"] --- --- Split namespaces into segments with a required dot in between. --- >>> prepareQuery "List.map" --- ["List",".","map"] --- --- Shouldn't get multiple splits for capitalized letters --- >>> prepareQuery "List.Map" --- ["List",".","Map"] -prepareQuery :: String -> [Text] -prepareQuery query = do - word <- words query - xs <- - word - & List.foldl' - ( \acc next -> case next of - c - | Char.isUpper c -> [c] : acc - | Char.isSpace c -> "" : acc - | c == '.' -> "" : "." : acc - | otherwise -> case acc of - [] -> [[c]] - (last : rest) -> (last ++ [c]) : rest - ) - [] - & reverse - & filter (not . null) - pure $ Text.pack xs diff --git a/unison-share-api/src/Unison/Server/Share/NamespaceDetails.hs b/unison-share-api/src/Unison/Server/Share/NamespaceDetails.hs deleted file mode 100644 index df4caaab00..0000000000 --- a/unison-share-api/src/Unison/Server/Share/NamespaceDetails.hs +++ /dev/null @@ -1,38 +0,0 @@ -module Unison.Server.Share.NamespaceDetails (namespaceDetails) where - -import Control.Monad.Except -import Data.Set qualified as Set -import Servant.OpenApi () -import U.Codebase.HashTags (CausalHash) -import Unison.Codebase (Codebase) -import Unison.Codebase qualified as Codebase -import Unison.Codebase.Path qualified as Path -import Unison.Codebase.Runtime qualified as Rt -import Unison.Parser.Ann (Ann) -import Unison.Server.Backend -import Unison.Server.Backend qualified as Backend -import Unison.Server.Share.RenderDoc qualified as RenderDoc -import Unison.Server.Types - ( NamespaceDetails (..), - v2CausalBranchToUnisonHash, - ) -import Unison.Symbol (Symbol) -import Unison.Util.Pretty (Width) - -namespaceDetails :: - Rt.Runtime Symbol -> - Codebase IO Symbol Ann -> - Path.Path -> - CausalHash -> - Maybe Width -> - Backend IO NamespaceDetails -namespaceDetails runtime codebase namespacePath rootCausalHash mayWidth = do - causalHashAtPath <- liftIO $ Codebase.runTransaction codebase do - causalBranch <- Backend.resolveCausalHashV2 (Just rootCausalHash) - namespaceCausal <- Codebase.getShallowCausalAtPath namespacePath (Just causalBranch) - let causalHashAtPath = v2CausalBranchToUnisonHash namespaceCausal - pure causalHashAtPath - mayReadme <- RenderDoc.findAndRenderDoc readmeNames runtime codebase namespacePath rootCausalHash mayWidth - pure $ NamespaceDetails namespacePath causalHashAtPath mayReadme - where - readmeNames = Set.fromList ["README", "Readme", "ReadMe", "readme"] diff --git a/unison-share-api/src/Unison/Server/Share/RenderDoc.hs b/unison-share-api/src/Unison/Server/Share/RenderDoc.hs deleted file mode 100644 index 5913feabab..0000000000 --- a/unison-share-api/src/Unison/Server/Share/RenderDoc.hs +++ /dev/null @@ -1,62 +0,0 @@ -{-# LANGUAGE DataKinds #-} -{-# LANGUAGE DeriveAnyClass #-} -{-# LANGUAGE DeriveGeneric #-} -{-# LANGUAGE KindSignatures #-} -{-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE StandaloneDeriving #-} -{-# LANGUAGE TypeOperators #-} -{-# OPTIONS_GHC -Wno-orphans #-} - --- | Helper for rendering docs within a given namespace -module Unison.Server.Share.RenderDoc where - -import Control.Monad.Except -import Data.Set qualified as Set -import Servant.OpenApi () -import U.Codebase.Causal qualified as V2Causal -import U.Codebase.HashTags (CausalHash) -import U.Codebase.Sqlite.NameLookups (PathSegments (..)) -import U.Codebase.Sqlite.Operations qualified as Ops -import Unison.Codebase (Codebase) -import Unison.Codebase qualified as Codebase -import Unison.Codebase.Path qualified as Path -import Unison.Codebase.Runtime qualified as Rt -import Unison.LabeledDependency qualified as LD -import Unison.NameSegment (NameSegment (..)) -import Unison.Parser.Ann (Ann) -import Unison.Prelude -import Unison.PrettyPrintEnvDecl.Sqlite qualified as PPESqlite -import Unison.Server.Backend -import Unison.Server.Backend qualified as Backend -import Unison.Server.Doc (Doc) -import Unison.Server.Doc qualified as Doc -import Unison.Symbol (Symbol) -import Unison.Util.Pretty (Width) - --- | Find, eval, and render the first doc we find with any of the provided names within the given namespace --- If no doc is found, return Nothing --- --- Requires Name Lookups, currently only usable on Share. -findAndRenderDoc :: - Set NameSegment -> - Rt.Runtime Symbol -> - Codebase IO Symbol Ann -> - Path.Path -> - CausalHash -> - Maybe Width -> - Backend IO (Maybe Doc) -findAndRenderDoc docNames runtime codebase namespacePath rootCausalHash _mayWidth = do - (shallowBranchAtNamespace, namesPerspective) <- - liftIO . (Codebase.runTransaction codebase) $ do - rootCausal <- Backend.resolveCausalHashV2 (Just rootCausalHash) - let rootBranchHash = V2Causal.valueHash rootCausal - namespaceCausal <- Codebase.getShallowCausalAtPath namespacePath (Just rootCausal) - shallowBranchAtNamespace <- V2Causal.value namespaceCausal - namesPerspective <- Ops.namesPerspectiveForRootAndPath rootBranchHash (coerce . Path.toList $ namespacePath) - pure (shallowBranchAtNamespace, namesPerspective) - let mayDocRef = Backend.findDocInBranch docNames shallowBranchAtNamespace - for mayDocRef \docRef -> do - eDoc <- liftIO $ evalDocRef runtime codebase docRef - let docDeps = Doc.dependencies eDoc <> Set.singleton (LD.TermReference docRef) - docPPE <- liftIO $ Codebase.runTransaction codebase $ PPESqlite.ppedForReferences namesPerspective docDeps - pure $ Doc.renderDoc docPPE eDoc diff --git a/unison-share-api/unison-share-api.cabal b/unison-share-api/unison-share-api.cabal index 978c21020e..5fc6fef765 100644 --- a/unison-share-api/unison-share-api.cabal +++ b/unison-share-api/unison-share-api.cabal @@ -39,11 +39,6 @@ library Unison.Server.QueryResult Unison.Server.SearchResult Unison.Server.SearchResult' - Unison.Server.Share - Unison.Server.Share.Definitions - Unison.Server.Share.FuzzyFind - Unison.Server.Share.NamespaceDetails - Unison.Server.Share.RenderDoc Unison.Server.Syntax Unison.Server.Types Unison.Sync.API