Skip to content

Commit

Permalink
Merge pull request #5362 from unisonweb/topic/text-search
Browse files Browse the repository at this point in the history
  • Loading branch information
aryairani committed Sep 24, 2024
2 parents c057942 + eafb5b3 commit 3a21189
Show file tree
Hide file tree
Showing 9 changed files with 402 additions and 6 deletions.
4 changes: 3 additions & 1 deletion unison-cli/src/Unison/Codebase/Editor/HandleInput.hs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ import Unison.Codebase.Editor.HandleInput.DebugSynhashTerm (handleDebugSynhashTe
import Unison.Codebase.Editor.HandleInput.DeleteBranch (handleDeleteBranch)
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.FindAndReplace (handleStructuredFindI, handleStructuredFindReplaceI, handleTextFindI)
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)
Expand Down Expand Up @@ -621,6 +621,7 @@ loop e = do
FindI isVerbose fscope ws -> handleFindI isVerbose fscope ws input
StructuredFindI _fscope ws -> handleStructuredFindI ws
StructuredFindReplaceI ws -> handleStructuredFindReplaceI ws
TextFindI allowLib ws -> handleTextFindI allowLib ws
LoadI maybePath -> handleLoad maybePath
ClearI -> Cli.respond ClearScreen
AddI requestedNames -> do
Expand Down Expand Up @@ -1045,6 +1046,7 @@ inputDescription input =
ShowDefinitionI {} -> wat
StructuredFindI {} -> wat
StructuredFindReplaceI {} -> wat
TextFindI {} -> wat
ShowRootReflogI {} -> pure "deprecated.root-reflog"
ShowGlobalReflogI {} -> pure "reflog.global"
ShowProjectReflogI mayProjName -> do
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
module Unison.Codebase.Editor.HandleInput.FindAndReplace
( handleStructuredFindReplaceI,
handleStructuredFindI,
handleTextFindI
)
where

Expand Down Expand Up @@ -28,6 +29,7 @@ import Unison.Names (Names)
import Unison.Names qualified as Names
import Unison.NamesWithHistory qualified as Names
import Unison.Parser.Ann (Ann (..))
import Unison.Pattern qualified as Pattern
import Unison.Prelude
import Unison.PrettyPrintEnv qualified as PPE
import Unison.PrettyPrintEnv.Names qualified as PPE
Expand Down Expand Up @@ -91,6 +93,47 @@ handleStructuredFindI rule = do
Cli.setNumberedArgs $ map SA.HashQualified results
Cli.respond (ListStructuredFind results)

handleTextFindI :: Bool -> [String] -> Cli ()
handleTextFindI allowLib tokens = do
Cli.Env {codebase} <- ask
currentBranch <- Cli.getCurrentBranch0
hqLength <- Cli.runTransaction Codebase.hashLength
let names = Branch.toNames currentBranch
let ppe = PPED.makePPED (PPE.hqNamer hqLength names) (PPE.suffixifyByHash names)
let fqppe = PPED.unsuffixifiedPPE ppe
results :: [(HQ.HashQualified Name, Referent)] <- pure $ do
r <- Set.toList (Relation.ran $ Names.terms names)
Just hq <- [PPE.terms fqppe r]
fullName <- [HQ'.toName hq]
guard (allowLib || not (Name.beginsWithSegment fullName NameSegment.libSegment))
Referent.Ref _ <- pure r
Just shortName <- [PPE.terms (PPED.suffixifiedPPE ppe) r]
pure (HQ'.toHQ shortName, r)
let ok (hq, Referent.Ref (Reference.DerivedId r)) = do
oe <- Cli.runTransaction (Codebase.getTerm codebase r)
pure $ (hq, maybe False containsTokens oe)
ok (hq, _) = pure (hq, False)
results0 <- traverse ok results
let results = Alphabetical.sortAlphabetically [hq | (hq, True) <- results0]
Cli.setNumberedArgs $ map SA.HashQualified results
Cli.respond (ListTextFind allowLib results)
where
tokensTxt = Text.pack <$> tokens
containsTokens tm =
hasAll . join $ ABT.find txts tm
where
hasAll txts = all (\tok -> any (\haystack -> Text.isInfixOf tok haystack) txts) tokensTxt
txts (Term.Text' haystack) = ABT.Found [haystack]
txts (Term.Nat' haystack) = ABT.Found [Text.pack (show haystack)]
txts (Term.Int' haystack) = ABT.Found [Text.pack (show haystack)]
txts (Term.Float' haystack) = ABT.Found [Text.pack (show haystack)]
txts (Term.Char' haystack) = ABT.Found [Text.pack [haystack]]
txts (Term.Match' _ cases) = ABT.Found r
where r = join $ Pattern.foldMap' txtPattern . Term.matchPattern <$> cases
txts _ = ABT.Continue
txtPattern (Pattern.Text _ txt) = [txt]
txtPattern _ = []

lookupRewrite ::
(HQ.HashQualified Name -> Output) ->
([Symbol] -> Term Symbol Ann -> Term Symbol Ann) ->
Expand Down
1 change: 1 addition & 0 deletions unison-cli/src/Unison/Codebase/Editor/Input.hs
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ data Input
| FindShallowI Path'
| StructuredFindI FindScope (HQ.HashQualified Name) -- sfind findScope query
| StructuredFindReplaceI (HQ.HashQualified Name) -- sfind.replace rewriteQuery
| TextFindI Bool [String] -- TextFindI allowLib tokens
| -- Show provided definitions.
ShowDefinitionI OutputLocation ShowDefinitionScope (NonEmpty (HQ.HashQualified Name))
| ShowRootReflogI {- Deprecated -}
Expand Down
2 changes: 2 additions & 0 deletions unison-cli/src/Unison/Codebase/Editor/Output.hs
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,7 @@ data Output
| ListOfDefinitions FindScope PPE.PrettyPrintEnv ListDetailed [SearchResult' Symbol Ann]
| ListShallow (IO PPE.PrettyPrintEnv) [ShallowListEntry Symbol Ann]
| ListStructuredFind [HQ.HashQualified Name]
| ListTextFind Bool [HQ.HashQualified Name] -- whether lib was included in the search
| GlobalFindBranchResults (ProjectAndBranch ProjectName ProjectBranchName) PPE.PrettyPrintEnv ListDetailed [SearchResult' Symbol Ann]
| -- ListStructuredFind patternMatchingUsages termBodyUsages
-- show the result of add/update
Expand Down Expand Up @@ -552,6 +553,7 @@ isFailure o = case o of
ListOfDefinitions _ _ _ ds -> null ds
GlobalFindBranchResults _ _ _ _ -> False
ListStructuredFind tms -> null tms
ListTextFind _ tms -> null tms
SlurpOutput _ _ sr -> not $ SR.isOk sr
ParseErrors {} -> True
TypeErrors {} -> True
Expand Down
44 changes: 44 additions & 0 deletions unison-cli/src/Unison/CommandLine/InputPatterns.hs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ module Unison.CommandLine.InputPatterns
saveExecuteResult,
sfind,
sfindReplace,
textfind,
test,
testAll,
todo,
Expand Down Expand Up @@ -147,6 +148,7 @@ import Data.Map qualified as Map
import Data.Maybe (fromJust)
import Data.Set qualified as Set
import Data.Text qualified as Text
import Data.Char (isSpace)
import Data.These (These (..))
import Network.URI qualified as URI
import System.Console.Haskeline.Completion (Completion (Completion))
Expand Down Expand Up @@ -1080,6 +1082,46 @@ undo =
"`undo` reverts the most recent change to the codebase."
(const $ pure Input.UndoI)

textfind :: Bool -> InputPattern
textfind allowLib =
InputPattern cmdName aliases I.Visible [("token", OnePlus, 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 ])
msg =
P.lines
[ P.wrap $
makeExample (textfind allowLib) ["token1", "\"99\"", "token2"]
<> " finds terms with literals (text or numeric) containing"
<> "`token1`, `99`, and `token2`.",
"",
P.wrap $ "Numeric literals must be quoted (ex: \"42\")" <>
"but single words need not be quoted.",
"",
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
Expand Down Expand Up @@ -3442,6 +3484,8 @@ validInputs =
findVerboseAll,
sfind,
sfindReplace,
textfind False,
textfind True,
forkLocal,
help,
helpTopics,
Expand Down
18 changes: 13 additions & 5 deletions unison-cli/src/Unison/CommandLine/OutputMessages.hs
Original file line number Diff line number Diff line change
Expand Up @@ -1448,7 +1448,13 @@ notifyUser dir = \case
ListDependencies ppe lds types terms ->
pure $ listDependentsOrDependencies ppe "Dependencies" "dependencies" lds types terms
ListStructuredFind terms ->
pure $ listStructuredFind terms
pure $ listFind False Nothing terms
ListTextFind True terms ->
pure $ listFind True Nothing terms
ListTextFind False terms ->
pure $ listFind False (Just tip) terms
where
tip = (IP.makeExample (IP.textfind True) [] <> " will search `lib` as well.")
DumpUnisonFileHashes hqLength datas effects terms ->
pure . P.syntaxToColor . P.lines $
( effects <&> \(n, r) ->
Expand Down Expand Up @@ -3586,17 +3592,19 @@ endangeredDependentsTable ppeDecl m =
& fmap (\(n, dep) -> numArg n <> prettyLabeled fqnEnv dep)
& P.lines

listStructuredFind :: [HQ.HashQualified Name] -> Pretty
listStructuredFind [] = "😶 I couldn't find any matches."
listStructuredFind tms =
listFind :: Bool -> Maybe Pretty -> [HQ.HashQualified Name] -> Pretty
listFind _ Nothing [] = "😶 I couldn't find any matches."
listFind _ (Just onMissing) [] = P.lines ["😶 I couldn't find any matches.", "", tip onMissing]
listFind allowLib _ tms =
P.callout "🔎" . P.lines $
[ "These definitions from the current namespace (excluding `lib`) have matches:",
[ "These definitions from the current namespace " <> parenthetical <> "have matches:",
"",
P.indentN 2 $ P.numberedList (pnames tms),
"",
tip (msg (length tms))
]
where
parenthetical = if allowLib then "" else "(excluding `lib`) "
pnames hqs = P.syntaxToColor . prettyHashQualified <$> hqs
msg 1 = "Try " <> IP.makeExample IP.edit ["1"] <> " to bring this into your scratch file."
msg n =
Expand Down
18 changes: 18 additions & 0 deletions unison-src/transcripts/help.output.md
Original file line number Diff line number Diff line change
Expand Up @@ -778,6 +778,24 @@ scratch/main> help
test.all
`test.all` runs unit tests for the current branch (including the `lib` namespace).
text.find (or grep)
`text.find token1 "99" token2` finds terms with literals (text
or numeric) containing `token1`, `99`, and `token2`.
Numeric literals must be quoted (ex: "42") but single words
need not be quoted.
Use `text.find.all` to include search of `lib`.
text.find.all (or grep.all)
`text.find.all token1 "99" token2` finds terms with literals
(text or numeric) containing `token1`, `99`, and `token2`.
Numeric literals must be quoted (ex: "42") but single words
need not be quoted.
Use `text.find` to exclude `lib` from search.
todo
`todo` lists the current namespace's outstanding issues,
including conflicted names, dependencies with missing names,
Expand Down
70 changes: 70 additions & 0 deletions unison-src/transcripts/textfind.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@

# The `text.find` command

```ucm:hide
scratch/main> builtins.merge lib.builtin
```

The `text.find` (or `grep`) command can be used to search for text or numeric literals appearing anywhere in your project. Just supply one or more tokens to search for. Unlike regular grep over the text of your code, this ignores local variables and function names that happen to match your search tokens (use `dependents` or `find` for that purpose). It's only searching for text or numeric literals that match.

```ucm
scratch/main> help grep
```

```ucm
scratch/main> help text.find.all
```

Here's an example:

```unison
foo =
_ = "an interesting constant"
1
bar = match "well hi there" with
"ooga" -> 99
"booga" -> 23
_ -> 0
baz = ["an", "quaffle", "tres"]
qux =
quaffle = 99
quaffle + 1
lib.foo = [Any 46, Any "hi", Any "zoink"]
lib.bar = 3
```

```ucm:hide
scratch/main> add
```

```ucm
scratch/main> grep hi
scratch/main> view 1
scratch/main> grep "hi"
scratch/main> text.find.all hi
scratch/main> view 1-5
scratch/main> grep oog
scratch/main> view 1
```

```ucm
scratch/main> grep quaffle
scratch/main> view 1-5
scratch/main> text.find "interesting const"
scratch/main> view 1-5
scratch/main> text.find "99" "23"
scratch/main> view 1
```

Now some failed searches:

```ucm:error
scratch/main> grep lsdkfjlskdjfsd
```

Notice it gives the tip about `text.find.all`. But not here:

```ucm:error
scratch/main> grep.all lsdkfjlskdjfsd
```
Loading

0 comments on commit 3a21189

Please sign in to comment.