Skip to content

Commit

Permalink
Merge pull request #4317 from unisonweb/cp/switch-fzf
Browse files Browse the repository at this point in the history
Add fuzzy-finder for project switching
  • Loading branch information
mergify[bot] authored Sep 13, 2023
2 parents fff1a78 + 30fad44 commit 858919a
Show file tree
Hide file tree
Showing 6 changed files with 108 additions and 44 deletions.
24 changes: 23 additions & 1 deletion codebase2/codebase-sqlite/U/Codebase/Sqlite/Queries.hs
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ module U.Codebase.Sqlite.Queries
loadAllProjectBranchesBeginningWith,
loadAllProjectBranchInfo,
loadProjectAndBranchNames,
loadAllProjectBranchNamePairs,
loadProjectBranch,
insertProjectBranch,
renameProjectBranch,
Expand Down Expand Up @@ -376,7 +377,7 @@ import U.Codebase.WatchKind (WatchKind)
import U.Core.ABT qualified as ABT
import U.Util.Serialization qualified as S
import U.Util.Term qualified as TermUtil
import Unison.Core.Project (ProjectBranchName (..), ProjectName (..))
import Unison.Core.Project (ProjectAndBranch (..), ProjectBranchName (..), ProjectName (..))
import Unison.Debug qualified as Debug
import Unison.Hash (Hash)
import Unison.Hash qualified as Hash
Expand Down Expand Up @@ -3408,6 +3409,27 @@ loadAllProjectBranchesBeginningWith projectId mayPrefix =
ORDER BY project_branch.name ASC
|]

-- | Load ALL project/branch name pairs
-- Useful for autocomplete/fuzzy-finding
loadAllProjectBranchNamePairs :: Transaction [(ProjectAndBranch ProjectName ProjectBranchName, ProjectAndBranch ProjectId ProjectBranchId)]
loadAllProjectBranchNamePairs =
queryListRow
[sql|
SELECT
project.name,
project_branch.name,
project.id,
project_branch.branch_id
FROM
project
JOIN project_branch ON project.id = project_branch.project_id
ORDER BY project.name ASC, project_branch.name ASC
|]
<&> fmap \(projectName, branchName, projectId, branchId) ->
( ProjectAndBranch projectName branchName,
ProjectAndBranch projectId branchId
)

-- | Load info about all branches in a project, for display by the @branches@ command.
--
-- Each branch name maps to a possibly-empty collection of associated remote branches.
Expand Down
3 changes: 1 addition & 2 deletions unison-cli/src/Unison/Codebase/Editor/HandleInput.hs
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,6 @@ import Unison.CommandLine.FuzzySelect qualified as Fuzzy
import Unison.CommandLine.InputPatterns qualified as IP
import Unison.CommandLine.InputPatterns qualified as InputPatterns
import Unison.ConstructorReference (GConstructorReference (..))
import Unison.Core.Project (ProjectAndBranch (..))
import Unison.DataDeclaration qualified as DD
import Unison.FileParsers qualified as FileParsers
import Unison.Hash qualified as Hash
Expand Down Expand Up @@ -150,7 +149,7 @@ import Unison.PrettyPrintEnv.Names qualified as PPE
import Unison.PrettyPrintEnvDecl qualified as PPE hiding (biasTo, empty)
import Unison.PrettyPrintEnvDecl qualified as PPED
import Unison.PrettyPrintEnvDecl.Names qualified as PPED
import Unison.Project (ProjectBranchNameOrLatestRelease (..))
import Unison.Project (ProjectAndBranch (..), ProjectBranchNameOrLatestRelease (..))
import Unison.Reference (Reference, TermReference)
import Unison.Reference qualified as Reference
import Unison.Referent (Referent)
Expand Down
82 changes: 59 additions & 23 deletions unison-cli/src/Unison/Codebase/Editor/HandleInput/ProjectSwitch.hs
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,14 @@ where

import Control.Lens ((^.))
import Data.These (These (..))
import U.Codebase.Sqlite.Project as SqliteProject
import U.Codebase.Sqlite.ProjectBranch as SqliteProjectBranch
import U.Codebase.Sqlite.Queries qualified as Queries
import Unison.Cli.Monad (Cli)
import Unison.Cli.Monad qualified as Cli
import Unison.Cli.ProjectUtils qualified as ProjectUtils
import Unison.Codebase.Editor.Output qualified as Output
import Unison.CommandLine.FuzzySelect qualified as Fuzzy
import Unison.Prelude
import Unison.Project (ProjectAndBranch (..), ProjectAndBranchNames (..), ProjectBranchName, ProjectName)
import Witch (unsafeFrom)
Expand All @@ -24,29 +27,36 @@ import Witch (unsafeFrom)
-- 1. Switch to project "foo", since there isn't a branch "foo" in the current project
-- 2. Switch to branch "foo", since there isn't a project "foo"
-- 3. Complain, because there's both a project "foo" and a branch "foo" in the current project
projectSwitch :: ProjectAndBranchNames -> Cli ()
projectSwitch = \case
ProjectAndBranchNames'Ambiguous projectName branchName ->
ProjectUtils.getCurrentProjectBranch >>= \case
Nothing -> switchToProjectAndBranchByTheseNames (This projectName)
Just (ProjectAndBranch currentProject _currentBranch, _restPath) -> do
let currentProjectName = currentProject ^. #name
(projectExists, branchExists) <-
Cli.runTransaction do
(,)
<$> Queries.projectExistsByName projectName
<*> Queries.projectBranchExistsByName (currentProject ^. #projectId) branchName
case (projectExists, branchExists) of
(False, False) -> Cli.respond (Output.LocalProjectNorProjectBranchExist projectName branchName)
(False, True) -> switchToProjectAndBranchByTheseNames (These currentProjectName branchName)
(True, False) -> switchToProjectAndBranchByTheseNames (This projectName)
(True, True) ->
Cli.respondNumbered $
Output.AmbiguousSwitch
projectName
(ProjectAndBranch currentProjectName branchName)
ProjectAndBranchNames'Unambiguous projectAndBranchNames0 ->
switchToProjectAndBranchByTheseNames projectAndBranchNames0
projectSwitch :: Maybe ProjectAndBranchNames -> Cli ()
projectSwitch mayNames = do
projectNames <- case mayNames of
Nothing -> do
fuzzySelectProjectBranch >>= \case
Nothing -> Cli.returnEarlyWithoutOutput
Just (ProjectAndBranch p b) -> pure (ProjectAndBranchNames'Unambiguous $ These p b)
Just names -> pure names
case projectNames of
ProjectAndBranchNames'Ambiguous projectName branchName ->
ProjectUtils.getCurrentProjectBranch >>= \case
Nothing -> switchToProjectAndBranchByTheseNames (This projectName)
Just (ProjectAndBranch currentProject _currentBranch, _restPath) -> do
let currentProjectName = currentProject ^. #name
(projectExists, branchExists) <-
Cli.runTransaction do
(,)
<$> Queries.projectExistsByName projectName
<*> Queries.projectBranchExistsByName (currentProject ^. #projectId) branchName
case (projectExists, branchExists) of
(False, False) -> Cli.respond (Output.LocalProjectNorProjectBranchExist projectName branchName)
(False, True) -> switchToProjectAndBranchByTheseNames (These currentProjectName branchName)
(True, False) -> switchToProjectAndBranchByTheseNames (This projectName)
(True, True) ->
Cli.respondNumbered $
Output.AmbiguousSwitch
projectName
(ProjectAndBranch currentProjectName branchName)
ProjectAndBranchNames'Unambiguous projectAndBranchNames0 ->
switchToProjectAndBranchByTheseNames projectAndBranchNames0

switchToProjectAndBranchByTheseNames :: These ProjectName ProjectBranchName -> Cli ()
switchToProjectAndBranchByTheseNames projectAndBranchNames0 = do
Expand Down Expand Up @@ -76,3 +86,29 @@ switchToProjectAndBranchByTheseNames projectAndBranchNames0 = do
setMostRecentBranch branch = do
Queries.setMostRecentBranch (branch ^. #projectId) (branch ^. #branchId)
pure branch

-- | Select a project branch from a list of all project branches
fuzzySelectProjectBranch :: Cli (Maybe (ProjectAndBranch ProjectName ProjectBranchName))
fuzzySelectProjectBranch = do
mayCurrentPB <-
ProjectUtils.getCurrentProjectBranch
<&> \case
Nothing -> Nothing
Just (ProjectAndBranch p b, _path) -> Just (ProjectAndBranch (SqliteProject.projectId p) (SqliteProjectBranch.branchId b))
allBranches <-
Cli.runTransaction Queries.loadAllProjectBranchNamePairs
<&> \xs ->
xs
& filter (\(_names, ids) -> Just ids /= mayCurrentPB)
-- Put branches in our current project near the cursor for easy access.
& sortOn (\(_names, ProjectAndBranch {project = projectId}) -> Just projectId /= (project <$> mayCurrentPB))
result <-
liftIO $
Fuzzy.fuzzySelect
Fuzzy.defaultOptions {Fuzzy.allowMultiSelect = False}
(\(names, _) -> into @Text names)
allBranches
case result of
Just ((names, _ids) : _) ->
pure . Just $ names
_ -> pure Nothing
2 changes: 1 addition & 1 deletion unison-cli/src/Unison/Codebase/Editor/Input.hs
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ data Input
| DiffNamespaceToPatchI DiffNamespaceToPatchInput
| ProjectCreateI Bool {- try downloading base? -} (Maybe ProjectName)
| ProjectRenameI ProjectName
| ProjectSwitchI ProjectAndBranchNames
| ProjectSwitchI (Maybe ProjectAndBranchNames {- Nothing triggers fuzzy-finder -})
| ProjectsI
| BranchI BranchSourceI (ProjectAndBranch (Maybe ProjectName) ProjectBranchName)
| BranchRenameI ProjectBranchName
Expand Down
2 changes: 1 addition & 1 deletion unison-cli/src/Unison/Codebase/TranscriptParser.hs
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,7 @@ run verbosity dir stanzas codebase runtime sbRuntime config ucmVersion baseURL =
pure
if curPath == ProjectUtils.projectBranchPath projectAndBranchIds
then Nothing
else Just (ProjectSwitchI (ProjectAndBranchNames'Unambiguous (These projectName branchName)))
else Just (ProjectSwitchI (Just $ ProjectAndBranchNames'Unambiguous (These projectName branchName)))
case maybeSwitchCommand of
Just switchCommand -> do
atomically $ Q.undequeue cmdQueue (Just p)
Expand Down
39 changes: 23 additions & 16 deletions unison-cli/src/Unison/CommandLine/InputPatterns.hs
Original file line number Diff line number Diff line change
Expand Up @@ -686,20 +686,26 @@ deleteGen :: Maybe String -> String -> ([Path.HQSplit'] -> DeleteTarget) -> Inpu
deleteGen suffix target mkTarget =
let cmd = maybe "delete" ("delete." <>) suffix
info =
P.wrapColumn2 [
(P.sep
" "
[ backtick (P.sep " " [P.string cmd, "foo"]),
"removes the",
P.string target,
"name `foo` from the namespace."
], ""),
(P.sep
" "
[ backtick (P.sep " " [P.string cmd, "foo bar"]),
"removes the",
P.string target,
"name `foo` and `bar` from the namespace." ], "")]
P.wrapColumn2
[ ( P.sep
" "
[ backtick (P.sep " " [P.string cmd, "foo"]),
"removes the",
P.string target,
"name `foo` from the namespace."
],
""
),
( P.sep
" "
[ backtick (P.sep " " [P.string cmd, "foo bar"]),
"removes the",
P.string target,
"name `foo` and `bar` from the namespace."
],
""
)
]
warn =
P.sep
" "
Expand Down Expand Up @@ -2612,18 +2618,19 @@ projectSwitch =
{ patternName = "switch",
aliases = [],
visibility = I.Visible,
argTypes = [(Required, projectAndBranchNamesArg False)],
argTypes = [(Optional, projectAndBranchNamesArg False)],
help =
P.wrapColumn2
[ ("`switch foo/bar`", "switches to the branch `bar` in the project `foo`"),
("`switch foo/`", "switches to the last branch you visited in the project `foo`"),
("`switch /bar`", "switches to the branch `bar` in the current project")
],
parse = \case
[] -> Right (Input.ProjectSwitchI Nothing)
[name] ->
case tryInto @ProjectAndBranchNames (Text.pack name) of
Left _ -> Left (showPatternHelp projectSwitch)
Right projectAndBranch -> Right (Input.ProjectSwitchI projectAndBranch)
Right projectAndBranch -> Right (Input.ProjectSwitchI $ Just projectAndBranch)
_ -> Left (showPatternHelp projectSwitch)
}

Expand Down

0 comments on commit 858919a

Please sign in to comment.