Skip to content

Commit

Permalink
Merge pull request #5258 from unisonweb/cp/global-search
Browse files Browse the repository at this point in the history
  • Loading branch information
aryairani authored Aug 1, 2024
2 parents 4acebf0 + 8c9c3ba commit 8a3e2ef
Show file tree
Hide file tree
Showing 19 changed files with 303 additions and 244 deletions.
160 changes: 86 additions & 74 deletions unison-cli/src/Unison/Codebase/Editor/HandleInput.hs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ import Unison.Codebase.Editor.HandleInput.DeleteProject (handleDeleteProject)
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.Global qualified as Global
import Unison.Codebase.Editor.HandleInput.InstallLib (handleInstallLib)
import Unison.Codebase.Editor.HandleInput.LSPDebug qualified as LSPDebug
import Unison.Codebase.Editor.HandleInput.Load (EvalMode (Sandboxed), evalUnisonFile, handleLoad, loadUnisonFile)
Expand Down Expand Up @@ -497,23 +498,27 @@ loop e = do
fixupOutput = HQ'.toHQ . Path.nameFromHQSplit
NamesI global query -> do
hqLength <- Cli.runTransaction Codebase.hashLength
(names, pped) <-
if global
then do
error "TODO: Implement names.global."
else do
names <- Cli.currentNames
let searchNames names = do
pped <- Cli.prettyPrintEnvDeclFromNames names
pure (names, pped)

let unsuffixifiedPPE = PPED.unsuffixifiedPPE pped
terms = Names.lookupHQTerm Names.IncludeSuffixes query names
types = Names.lookupHQType Names.IncludeSuffixes query names
terms' :: [(Referent, [HQ'.HashQualified Name])]
terms' = map (\r -> (r, PPE.allTermNames unsuffixifiedPPE r)) (Set.toList terms)
types' :: [(Reference, [HQ'.HashQualified Name])]
types' = map (\r -> (r, PPE.allTypeNames unsuffixifiedPPE r)) (Set.toList types)
Cli.respond $ ListNames global hqLength types' terms'
let unsuffixifiedPPE = PPED.unsuffixifiedPPE pped
terms = Names.lookupHQTerm Names.IncludeSuffixes query names
types = Names.lookupHQType Names.IncludeSuffixes query names
terms' :: [(Referent, [HQ'.HashQualified Name])]
terms' = map (\r -> (r, PPE.allTermNames unsuffixifiedPPE r)) (Set.toList terms)
types' :: [(Reference, [HQ'.HashQualified Name])]
types' = map (\r -> (r, PPE.allTypeNames unsuffixifiedPPE r)) (Set.toList types)
pure (terms', types')
if global
then do
Global.forAllProjectBranches \(projBranchNames, _ids) branch -> do
let names = Branch.toNames . Branch.head $ branch
(terms, types) <- searchNames names
when (not (null terms) || not (null types)) do
Cli.respond $ GlobalListNames projBranchNames hqLength types terms
else do
names <- Cli.currentNames
(terms, types) <- searchNames names
Cli.respond $ ListNames hqLength types terms
DocsI srcs -> do
for_ srcs docsI
CreateAuthorI authorNameSegment authorFullName -> do
Expand Down Expand Up @@ -1089,80 +1094,87 @@ handleFindI ::
Cli ()
handleFindI isVerbose fscope ws input = do
Cli.Env {codebase} <- ask
(pped, names, searchRoot, branch0) <- case fscope of
case fscope of
FindLocal p -> do
searchRoot <- Cli.resolvePath' p
branch0 <- Cli.getBranch0FromProjectPath searchRoot
let names = Branch.toNames (Branch.withoutLib branch0)
-- Don't exclude anything from the pretty printer, since the type signatures we print for
-- results may contain things in lib.
pped <- Cli.currentPrettyPrintEnvDecl
pure (pped, names, Just p, branch0)
let suffixifiedPPE = PPED.suffixifiedPPE pped
results <- searchBranch0 codebase branch0 names
if (null results)
then do
Cli.respond FindNoLocalMatches
-- We've already searched everything else, so now we search JUST the
-- names in lib.
let mayOnlyLibBranch = branch0 & Branch.children %%~ \cs -> Map.singleton NameSegment.libSegment <$> Map.lookup NameSegment.libSegment cs
case mayOnlyLibBranch of
Nothing -> respondResults codebase suffixifiedPPE (Just p) []
Just onlyLibBranch -> do
let onlyLibNames = Branch.toNames onlyLibBranch
results <- searchBranch0 codebase branch0 onlyLibNames
respondResults codebase suffixifiedPPE (Just p) results
else respondResults codebase suffixifiedPPE (Just p) results
FindLocalAndDeps p -> do
searchRoot <- Cli.resolvePath' p
branch0 <- Cli.getBranch0FromProjectPath searchRoot
let names = Branch.toNames (Branch.withoutTransitiveLibs branch0)
-- Don't exclude anything from the pretty printer, since the type signatures we print for
-- results may contain things in lib.
pped <- Cli.currentPrettyPrintEnvDecl
pure (pped, names, Just p, branch0)
let suffixifiedPPE = PPED.suffixifiedPPE pped
results <- searchBranch0 codebase branch0 names
respondResults codebase suffixifiedPPE (Just p) results
FindGlobal -> do
-- TODO: Rewrite to be properly global again
projectRootNames <- Names.makeAbsolute . Branch.toNames <$> Cli.getCurrentProjectRoot0
pped <- Cli.prettyPrintEnvDeclFromNames projectRootNames
currentBranch0 <- Cli.getCurrentBranch0
pure (pped, projectRootNames, Nothing, currentBranch0)
let suffixifiedPPE = PPED.suffixifiedPPE pped
let getResults :: Names -> Cli [SearchResult]
getResults names =
case ws of
[] -> pure (List.sortBy SR.compareByName (SR.fromNames names))
-- type query
":" : ws -> do
typ <- parseSearchType (show input) (unwords ws)
let keepNamed = Set.intersection (Branch.deepReferents branch0)
(noExactTypeMatches, matches) <- do
Cli.runTransaction do
matches <- keepNamed <$> Codebase.termsOfType codebase typ
if null matches
then (True,) . keepNamed <$> Codebase.termsMentioningType codebase typ
else pure (False, matches)
when noExactTypeMatches (Cli.respond NoExactTypeMatches)
pure $
-- in verbose mode, aliases are shown, so we collapse all
-- aliases to a single search result; in non-verbose mode,
-- a separate result may be shown for each alias
(if isVerbose then uniqueBy SR.toReferent else id) $
searchResultsFor names (Set.toList matches) []
Global.forAllProjectBranches \(projAndBranchNames, _ids) branch -> do
let branch0 = Branch.head branch
let projectRootNames = Names.makeAbsolute . Branch.toNames $ branch0
pped <- Cli.prettyPrintEnvDeclFromNames projectRootNames
results <- searchBranch0 codebase branch0 projectRootNames
when (not $ null results) do
Cli.setNumberedArgs $ fmap (SA.SearchResult Nothing) results
results' <- Cli.runTransaction (Backend.loadSearchResults codebase results)
Cli.respond $ GlobalFindBranchResults projAndBranchNames (PPED.suffixifiedPPE pped) isVerbose results'
where
searchBranch0 :: Codebase.Codebase m Symbol Ann -> Branch0 IO -> Names -> Cli [SearchResult]
searchBranch0 codebase branch0 names =
case ws of
[] -> pure (List.sortBy SR.compareByName (SR.fromNames names))
-- type query
":" : ws -> do
typ <- parseSearchType (show input) (unwords ws)
let keepNamed = Set.intersection (Branch.deepReferents branch0)
(noExactTypeMatches, matches) <- do
Cli.runTransaction do
matches <- keepNamed <$> Codebase.termsOfType codebase typ
if null matches
then (True,) . keepNamed <$> Codebase.termsMentioningType codebase typ
else pure (False, matches)
when noExactTypeMatches (Cli.respond NoExactTypeMatches)
pure $
-- in verbose mode, aliases are shown, so we collapse all
-- aliases to a single search result; in non-verbose mode,
-- a separate result may be shown for each alias
(if isVerbose then uniqueBy SR.toReferent else id) $
searchResultsFor names (Set.toList matches) []

-- name query
qs -> do
let anythingBeforeHash :: Megaparsec.Parsec (Lexer.Token Text) [Char] Text
anythingBeforeHash = Text.pack <$> Megaparsec.takeWhileP Nothing (/= '#')
let srs =
searchBranchScored
names
Find.simpleFuzzyScore
(mapMaybe (HQ.parseTextWith anythingBeforeHash . Text.pack) qs)
pure $ uniqueBy SR.toReferent srs
let respondResults results = do
Cli.setNumberedArgs $ fmap (SA.SearchResult searchRoot) results
results' <- Cli.runTransaction (Backend.loadSearchResults codebase results)
Cli.respond $ ListOfDefinitions fscope suffixifiedPPE isVerbose results'
results <- getResults names
case (results, fscope) of
([], FindLocal {}) -> do
Cli.respond FindNoLocalMatches
-- We've already searched everything else, so now we search JUST the
-- names in lib.
let mayOnlyLibBranch = branch0 & Branch.children %%~ \cs -> Map.singleton NameSegment.libSegment <$> Map.lookup NameSegment.libSegment cs
case mayOnlyLibBranch of
Nothing -> respondResults []
Just onlyLibBranch -> do
let onlyLibNames = Branch.toNames onlyLibBranch
results <- getResults onlyLibNames
respondResults results
_ -> respondResults results
-- name query
qs -> do
let anythingBeforeHash :: Megaparsec.Parsec (Lexer.Token Text) [Char] Text
anythingBeforeHash = Text.pack <$> Megaparsec.takeWhileP Nothing (/= '#')
let srs =
searchBranchScored
names
Find.simpleFuzzyScore
(mapMaybe (HQ.parseTextWith anythingBeforeHash . Text.pack) qs)
pure $ uniqueBy SR.toReferent srs
respondResults :: Codebase.Codebase m Symbol Ann -> PPE.PrettyPrintEnv -> Maybe Path' -> [SearchResult] -> Cli ()
respondResults codebase ppe searchRoot results = do
Cli.setNumberedArgs $ fmap (SA.SearchResult searchRoot) results
results' <- Cli.runTransaction (Backend.loadSearchResults codebase results)
Cli.respond $ ListOfDefinitions fscope ppe isVerbose results'

handleDependencies :: HQ.HashQualified Name -> Cli ()
handleDependencies hq = do
Expand Down
22 changes: 22 additions & 0 deletions unison-cli/src/Unison/Codebase/Editor/HandleInput/Global.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
module Unison.Codebase.Editor.HandleInput.Global (forAllProjectBranches) where

import Control.Monad.Reader
import U.Codebase.Sqlite.DbId (ProjectBranchId, ProjectId)
import U.Codebase.Sqlite.Queries qualified as Q
import Unison.Cli.Monad (Cli)
import Unison.Cli.Monad qualified as Cli
import Unison.Codebase qualified as Codebase
import Unison.Codebase.Branch (Branch)
import Unison.Core.Project
import Unison.Prelude
import Unison.Util.Monoid (foldMapM)

-- | Map over ALL project branches in the codebase.
-- This is a _very_ big hammer, that you should basically never use, except for things like debugging or migrations.
forAllProjectBranches :: (Monoid r) => ((ProjectAndBranch ProjectName ProjectBranchName, ProjectAndBranch ProjectId ProjectBranchId) -> Branch IO -> Cli r) -> Cli r
forAllProjectBranches f = do
Cli.Env {codebase} <- ask
projectBranches <- Cli.runTransaction Q.loadAllProjectBranchNamePairs
projectBranches & foldMapM \(names, ids@(ProjectAndBranch projId branchId)) -> do
b <- liftIO $ Codebase.expectProjectBranchRoot codebase projId branchId
f (names, ids) b
4 changes: 2 additions & 2 deletions unison-cli/src/Unison/Codebase/Editor/Input.hs
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,8 @@ data Input
| PushRemoteBranchI PushRemoteBranchInput
| ResetI (BranchId2 {- namespace to reset it to -}) (Maybe UnresolvedProjectBranch {- ProjectBranch to reset -})
-- todo: Q: Does it make sense to publish to not-the-root of a Github repo?
-- Does it make sense to fork from not-the-root of a Github repo?
| -- used in Welcome module to give directions to user
| -- Does it make sense to fork from not-the-root of a Github repo?
-- used in Welcome module to give directions to user
CreateMessage (P.Pretty P.ColorText)
| -- Change directory.
SwitchBranchI Path'
Expand Down
11 changes: 9 additions & 2 deletions unison-cli/src/Unison/Codebase/Editor/Output.hs
Original file line number Diff line number Diff line change
Expand Up @@ -261,14 +261,19 @@ data Output
| MovedOverExistingBranch Path'
| DeletedEverything
| ListNames
IsGlobal
Int -- hq length to print References
[(Reference, [HQ'.HashQualified Name])] -- type match, type names
[(Referent, [HQ'.HashQualified Name])] -- term match, term names
| GlobalListNames
(ProjectAndBranch ProjectName ProjectBranchName)
Int -- hq length to print References
[(Reference, [HQ'.HashQualified Name])] -- type match, type names
[(Referent, [HQ'.HashQualified Name])] -- term match, term names
-- list of all the definitions within this branch
| ListOfDefinitions FindScope PPE.PrettyPrintEnv ListDetailed [SearchResult' Symbol Ann]
| ListShallow (IO PPE.PrettyPrintEnv) [ShallowListEntry Symbol Ann]
| ListStructuredFind [HQ.HashQualified Name]
| GlobalFindBranchResults (ProjectAndBranch ProjectName ProjectBranchName) PPE.PrettyPrintEnv ListDetailed [SearchResult' Symbol Ann]
| -- ListStructuredFind patternMatchingUsages termBodyUsages
-- show the result of add/update
SlurpOutput Input PPE.PrettyPrintEnv SlurpResult
Expand Down Expand Up @@ -543,8 +548,10 @@ isFailure o = case o of
MoveRootBranchConfirmation -> False
MovedOverExistingBranch {} -> False
DeletedEverything -> False
ListNames _ _ tys tms -> null tms && null tys
ListNames _ tys tms -> null tms && null tys
GlobalListNames {} -> False
ListOfDefinitions _ _ _ ds -> null ds
GlobalFindBranchResults _ _ _ _ -> False
ListStructuredFind tms -> null tms
SlurpOutput _ _ sr -> not $ SR.isOk sr
ParseErrors {} -> True
Expand Down
15 changes: 9 additions & 6 deletions unison-cli/src/Unison/CommandLine/InputPatterns.hs
Original file line number Diff line number Diff line change
Expand Up @@ -1149,7 +1149,7 @@ findAll :: InputPattern
findAll = find' "find.all" (Input.FindLocalAndDeps Path.relativeEmpty')

findGlobal :: InputPattern
findGlobal = find' "find.global" Input.FindGlobal
findGlobal = find' "debug.find.global" Input.FindGlobal

findIn, findInAll :: InputPattern
findIn = findIn' "find-in" Input.FindLocal
Expand Down Expand Up @@ -1197,8 +1197,8 @@ findHelp =
"lists all definitions with a name similar to 'foo' or 'bar' in the "
<> "specified subnamespace (including one level of its 'lib')."
),
( "find.global foo",
"lists all definitions with a name similar to 'foo' in any namespace"
( "debug.find.global foo",
"Iteratively searches all projects and branches and lists all definitions with a name similar to 'foo'. Note that this is a very slow operation."
)
]
)
Expand Down Expand Up @@ -2611,12 +2611,15 @@ names isGlobal =
[]
I.Visible
[("name or hash", Required, definitionQueryArg)]
(P.wrap $ makeExample (names isGlobal) ["foo"] <> " shows the hash and all known names for `foo`.")
(P.wrap $ makeExample (names isGlobal) ["foo"] <> description)
$ \case
[thing] -> Input.NamesI isGlobal <$> handleHashQualifiedNameArg thing
args -> wrongArgsLength "exactly one argument" args
where
cmdName = if isGlobal then "names.global" else "names"
description
| isGlobal = "Iteratively search across all projects and branches for names matching `foo`. Note that this is expected to be quite slow and is primarily for debugging issues with your codebase."
| otherwise = "List all known names for `foo` in the current branch."
cmdName = if isGlobal then "debug.names.global" else "names"

dependents, dependencies :: InputPattern
dependents =
Expand Down Expand Up @@ -3456,7 +3459,7 @@ validInputs =
mergeInputPattern,
mergeCommitInputPattern,
names False, -- names
names True, -- names.global
names True, -- debug.names.global
namespaceDependencies,
previewAdd,
previewUpdate,
Expand Down
Loading

0 comments on commit 8a3e2ef

Please sign in to comment.