Skip to content

Commit

Permalink
Add Zsh completions for the assistant (digital-asset#3980)
Browse files Browse the repository at this point in the history
* Add Zsh completions for the assistant

This is slightly more annoying for users since at least on Linux there
doesn’t seem to be a user-writable directory that is in `$fpath` by
default. I think on Zsh we could probably write them to
/usr/local/share/zsh/site-functions but I’d rather avoid platform
specific logic here. I would expect that Zsh users are usually
somewhat comfortable with modifying the config and this also matches
other installation instructions, e.g.,
https://github.com/zsh-users/zsh-completions#manual-installation.

On the plus side, the completions look significantly nicer in Zsh
since they include the description of commands.

CHANGELOG_BEGIN
- [DAML Assistant] Zsh completions for the DAML Assistant are now
  installed as part of ``daml install``. To activate them you need to
  add ``~/.daml/zsh`` to your ``$fpath``, e.g., by adding
  ``fpath=(~/.daml/zsh $fpath)`` to the beginning of your ``~/.zshrc``
  before you call ``compinit``.
CHANGELOG_END

* Fix tests

* Update daml-assistant/src/DA/Daml/Assistant/Install/Completion.hs

Co-Authored-By: associahedron <231829+associahedron@users.noreply.github.com>

Co-authored-by: associahedron <231829+associahedron@users.noreply.github.com>
  • Loading branch information
cocreature and associahedron authored Jan 8, 2020
1 parent 7bce235 commit d1449e7
Show file tree
Hide file tree
Showing 7 changed files with 77 additions and 16 deletions.
1 change: 1 addition & 0 deletions daml-assistant/exe/DA/Daml/Assistant.hs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ autoInstall env@Env{..} = do
, iForce = ForceInstall False
, iSetPath = SetPath True
, iBashCompletions = BashCompletions Auto
, iZshCompletions = ZshCompletions Auto
}
installEnv = InstallEnv
{ options = options
Expand Down
1 change: 1 addition & 0 deletions daml-assistant/src/DA/Daml/Assistant/Command.hs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ installParser = InstallOptions
<*> iflag QuietInstall "quiet" (short 'q') "Don't display installation messages"
<*> fmap SetPath (flagYesNoAuto "set-path" True "Adjust PATH automatically. This option only has an effect on Windows." idm)
<*> fmap BashCompletions (flagYesNoAuto' "bash-completions" "Install bash completions for DAML assistant. Default is yes for linux and mac, no for windows." idm)
<*> fmap ZshCompletions (flagYesNoAuto' "zsh-completions" "Install Zsh completions for DAML assistant. Default is yes for linux and mac, no for windows." idm)
where
iflag p name opts desc = fmap p (switch (long name <> help desc <> opts))

Expand Down
3 changes: 2 additions & 1 deletion daml-assistant/src/DA/Daml/Assistant/Install.hs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import DA.Daml.Assistant.Types
import DA.Daml.Assistant.Util
import qualified DA.Daml.Assistant.Install.Github as Github
import DA.Daml.Assistant.Install.Path
import DA.Daml.Assistant.Install.BashCompletion
import DA.Daml.Assistant.Install.Completion
import DA.Daml.Project.Consts
import DA.Daml.Project.Config
import DA.Daml.Project.Util
Expand Down Expand Up @@ -192,6 +192,7 @@ activateDaml env@InstallEnv{..} targetPath = do

updatePath options (\s -> unlessQuiet env (output s)) damlBinaryTargetDir
installBashCompletions options damlPath (\s -> unlessQuiet env (output s))
installZshCompletions options damlPath (\s -> unlessQuiet env (output s))

data WalkCallbacks = WalkCallbacks
{ walkOnFile :: FilePath -> IO ()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
-- Copyright (c) 2020 The DAML Authors. All rights reserved.
-- SPDX-License-Identifier: Apache-2.0

-- | Installation of bash completions. The completion script is
-- | Installation of bash and Zsh completions. The Zsh completion script is
-- installed in @~/.daml/bash_completion.sh@ and a hook is added
-- in @~/.bash_completion@ to invoke it if available.
module DA.Daml.Assistant.Install.BashCompletion
-- For Zsh we install the script to @~/.daml/zsh/_daml@ and ask users to add
-- @~/.daml/zsh@ to @$fpath@.
module DA.Daml.Assistant.Install.Completion
( installBashCompletions
, installZshCompletions
) where

import DA.Daml.Assistant.Types

import qualified Data.ByteString.Lazy as BSL
import Control.Exception.Safe (tryIO, catchIO, displayException)
import Control.Monad.Extra (unless, andM, whenM)
import System.Directory (getHomeDirectory, getAppUserDataDirectory, doesFileExist, removePathForcibly)
import System.FilePath ((</>))
import System.Directory (getHomeDirectory, getAppUserDataDirectory, doesFileExist, removePathForcibly, createDirectoryIfMissing)
import System.FilePath ((</>), takeDirectory)
import System.Info.Extra (isWindows)
import System.Process.Typed (proc, readProcessStdout_)
import System.IO.Extra (readFileUTF8, writeFileUTF8)
Expand All @@ -25,6 +28,12 @@ installBashCompletions options damlPath output =
whenM (shouldInstallBashCompletions options damlPath) $
doInstallBashCompletions damlPath output

-- | Install zsh completion script if we should.
installZshCompletions :: InstallOptions -> DamlPath -> (String -> IO ()) -> IO ()
installZshCompletions options damlPath output =
whenM (shouldInstallZshCompletions options damlPath) $
doInstallZshCompletions damlPath output

-- | Should we install bash completions? By default, yes, but only if the
-- we're not on Windows and the completion script hasn't yet been generated.
shouldInstallBashCompletions :: InstallOptions -> DamlPath -> IO Bool
Expand All @@ -34,38 +43,72 @@ shouldInstallBashCompletions options damlPath =
BashCompletions No -> pure False
BashCompletions Auto -> andM
[ pure (not isWindows)
, not <$> doesFileExist (completionScriptPath damlPath)
, not <$> doesFileExist (bashCompletionScriptPath damlPath)
, isDefaultDamlPath damlPath
]

-- | Should we install Zsh completions? By default, yes, but only if the
-- we're not on Windows and the completion script hasn't yet been generated.
shouldInstallZshCompletions :: InstallOptions -> DamlPath -> IO Bool
shouldInstallZshCompletions options damlPath =
case iZshCompletions options of
ZshCompletions Yes -> pure True
ZshCompletions No -> pure False
ZshCompletions Auto -> andM
[ pure (not isWindows)
, not <$> doesFileExist (zshCompletionScriptPath damlPath)
, isDefaultDamlPath damlPath
]

-- | Generate the bash completion script, and add a hook.
doInstallBashCompletions :: DamlPath -> (String -> IO ()) -> IO ()
doInstallBashCompletions damlPath output = do
let scriptPath = completionScriptPath damlPath
script <- getCompletionScript damlPath
let scriptPath = bashCompletionScriptPath damlPath
script <- getBashCompletionScript damlPath
BSL.writeFile scriptPath script
unitE <- tryIO $ addCompletionHook scriptPath
unitE <- tryIO $ addBashCompletionHook scriptPath
case unitE of
Left e -> do
output ("Bash completions not installed: " <> displayException e)
catchIO (removePathForcibly scriptPath) (const $ pure ())
Right () -> output "Bash completions installed for DAML assistant."

-- | Generate the Zsh completion script.
doInstallZshCompletions :: DamlPath -> (String -> IO ()) -> IO ()
doInstallZshCompletions damlPath output = do
let scriptPath = zshCompletionScriptPath damlPath
script <- getZshCompletionScript damlPath
createDirectoryIfMissing True (takeDirectory scriptPath)
BSL.writeFile scriptPath script
output $ unlines
[ "Zsh completions installed for DAML assistant."
, "To use them, add '~/.daml/zsh' to your $fpath, e.g. by adding the following"
, "to the beginning of '~/.zshrc' before you call 'compinit':"
, "fpath=(~/.daml/zsh $fpath)"
]

-- | Read the bash completion script from optparse-applicative's
-- built-in @--bash-completion-script@ routine. Please read
-- https://github.com/pcapriotti/optparse-applicative/wiki/Bash-Completion
-- for more details. Note that the bash completion script doesn't
-- in general contain any daml-assistant specific information, it's only
-- specific to the path, so we don't need to regenerate it every version.
getCompletionScript :: DamlPath -> IO BSL.ByteString
getCompletionScript damlPath = do
getBashCompletionScript :: DamlPath -> IO BSL.ByteString
getBashCompletionScript damlPath = do
let assistant = assistantPath damlPath
readProcessStdout_ (proc assistant ["--bash-completion-script", assistant])

-- | Read the Zsh completion script from optparse-applicative’s
-- builtin @--zsh-completion-script@ routine.
getZshCompletionScript :: DamlPath -> IO BSL.ByteString
getZshCompletionScript damlPath = do
let assistant = assistantPath damlPath
readProcessStdout_ (proc assistant ["--zsh-completion-script", assistant])

-- | Add a completion hook in ~/.bash_completion
-- Does nothing if the hook is already there
addCompletionHook :: FilePath -> IO ()
addCompletionHook scriptPath = do
addBashCompletionHook :: FilePath -> IO ()
addBashCompletionHook scriptPath = do
let newHook = makeHook scriptPath
hookPath <- getHookPath
hooks <- readHooks hookPath
Expand Down Expand Up @@ -108,5 +151,8 @@ writeHooks (HookPath p) = writeFileUTF8 p . unlines . map unHook
assistantPath :: DamlPath -> FilePath
assistantPath (DamlPath p) = p </> "bin" </> "daml"

completionScriptPath :: DamlPath -> FilePath
completionScriptPath (DamlPath p) = p </> "bash_completions.sh"
bashCompletionScriptPath :: DamlPath -> FilePath
bashCompletionScriptPath (DamlPath p) = p </> "bash_completions.sh"

zshCompletionScriptPath :: DamlPath -> FilePath
zshCompletionScriptPath (DamlPath p) = p </> "zsh" </> "_daml"
2 changes: 2 additions & 0 deletions daml-assistant/src/DA/Daml/Assistant/Types.hs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ data InstallOptions = InstallOptions
, iQuiet :: QuietInstall -- ^ don't print messages
, iSetPath :: SetPath -- ^ set the user's PATH (on Windows)
, iBashCompletions :: BashCompletions -- ^ install bash completions for the daml assistant
, iZshCompletions :: ZshCompletions -- ^ install Zsh completions for the daml assistant
} deriving (Eq, Show)

-- | An install URL is a fully qualified HTTP[S] URL to an SDK release tarball. For example:
Expand All @@ -104,3 +105,4 @@ newtype ActivateInstall = ActivateInstall { unActivateInstall :: Bool } deriving
newtype SetPath = SetPath Bool deriving (Eq, Show)
newtype InstallAssistant = InstallAssistant { unwrapInstallAssistant :: YesNoAuto } deriving (Eq, Show)
newtype BashCompletions = BashCompletions { unwrapBashCompletions :: YesNoAuto } deriving (Eq, Show)
newtype ZshCompletions = ZshCompletions { unwrapZshCompletions :: YesNoAuto } deriving (Eq, Show)
4 changes: 4 additions & 0 deletions daml-assistant/test/DA/Daml/Assistant/Tests.hs
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,7 @@ testInstall = Tasty.testGroup "DA.Daml.Assistant.Install"
, iForce = ForceInstall False
, iSetPath = SetPath False
, iBashCompletions = BashCompletions No
, iZshCompletions = ZshCompletions No
}

setCurrentDirectory base
Expand Down Expand Up @@ -413,6 +414,7 @@ testInstallUnix = Tasty.testGroup "unix-specific tests"
, iForce = ForceInstall False
, iSetPath = SetPath False
, iBashCompletions = BashCompletions No
, iZshCompletions = ZshCompletions No
}

setCurrentDirectory base
Expand Down Expand Up @@ -442,6 +444,7 @@ testInstallUnix = Tasty.testGroup "unix-specific tests"
, iForce = ForceInstall False
, iSetPath = SetPath False
, iBashCompletions = BashCompletions No
, iZshCompletions = ZshCompletions No
}

setCurrentDirectory base
Expand Down Expand Up @@ -471,6 +474,7 @@ testInstallUnix = Tasty.testGroup "unix-specific tests"
, iForce = ForceInstall False
, iSetPath = SetPath False
, iBashCompletions = BashCompletions No
, iZshCompletions = ZshCompletions No
}

setCurrentDirectory base
Expand Down
8 changes: 7 additions & 1 deletion docs/source/tools/assistant.rst
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,13 @@ Rarely, you might need to install an SDK release from a downloaded SDK release t
Terminal Command Completion
***************************

The ``daml`` assistant comes with support for ``bash`` completions. These will be installed automatically on Linux and Mac when you install or upgrade the DAML assistant. If you use the ``bash`` shell, and your ``bash`` supports completions, you can use the TAB key to complete many ``daml`` commands, such as ``daml install`` and ``daml version``.
The ``daml`` assistant comes with support for ``bash`` and ``zsh`` completions. These will be installed automatically on Linux and Mac when you install or upgrade the DAML assistant.

If you use the ``bash`` shell, and your ``bash`` supports completions, you can use the TAB key to complete many ``daml`` commands, such as ``daml install`` and ``daml version``.

For ``Zsh`` you first need to add ``~/.daml/zsh`` to your ``$fpath``,
e.g., by adding the following to the beginning of your ``~/.zshrc``
before you call ``compinit``: ``fpath=(~/.daml/zsh $fpath)``

You can override whether bash completions are installed for ``daml`` by
passing ``--bash-completions=yes`` or ``--bash-completions=no`` to ``daml install``.

0 comments on commit d1449e7

Please sign in to comment.