feat: Yank to clipboard

closes #16
This commit is contained in:
Utku Demir 2021-03-18 14:34:10 +13:00
parent c494717450
commit 3bb55ba770
No known key found for this signature in database
GPG Key ID: F3F8629C3E0BF60B
4 changed files with 92 additions and 10 deletions

View File

@ -1,5 +1,9 @@
# Changelog
## Unreleased
* feat: Ability to yank selected store path to clipboard (shortcut: 'y')
## 0.1.6 - 2021-03-12
* feat: Support non standard Nix store locations

View File

@ -41,6 +41,7 @@ common common-options
StorePath
App
InvertedIndex
Clipboard
Paths_nix_tree
autogen-modules: Paths_nix_tree
mixins: base hiding (Prelude)

View File

@ -5,6 +5,7 @@ import qualified Brick.BChan as B
import qualified Brick.Widgets.Border as B
import qualified Brick.Widgets.Center as B
import qualified Brick.Widgets.List as B
import qualified Clipboard
import qualified Data.List.NonEmpty as NE
import qualified Data.Map as Map
import qualified Data.Sequence as S
@ -28,8 +29,10 @@ data Widgets
| WidgetWhyDependsViewport
deriving (Show, Eq, Ord)
data Notice = Notice Text Text
data Modal s
= ModalHelp
= ModalNotice Notice
| ModalWhyDepends (B.GenericList Widgets Seq (NonEmpty (Path s)))
| ModalSearch Text Text (B.GenericList Widgets Seq (Path s))
@ -177,9 +180,9 @@ app =
{ B.appDraw = \env@AppEnv {aeOpenModal} ->
[ case aeOpenModal of
Nothing -> B.emptyWidget
Just ModalHelp -> renderHelpModal
Just (ModalWhyDepends l) -> renderWhyDependsModal l
Just (ModalSearch l r xs) -> renderSearchModal l r xs,
Just (ModalSearch l r xs) -> renderSearchModal l r xs
Just (ModalNotice notice) -> renderNotice notice,
renderMainScreen env
],
B.appChooseCursor = \_ -> const Nothing,
@ -190,12 +193,17 @@ app =
| k `elem` [V.KChar 'q', V.KEsc] ->
B.halt s
(B.VtyEvent (V.EvKey (V.KChar '?') []), Nothing) ->
B.continue s {aeOpenModal = Just ModalHelp}
B.continue s {aeOpenModal = Just (ModalNotice helpNotice)}
(B.VtyEvent (V.EvKey (V.KChar 'w') []), Nothing) -> do
B.hScrollToBeginning (B.viewportScroll WidgetWhyDependsViewport)
B.continue $ showWhyDepends s
(B.VtyEvent (V.EvKey (V.KChar '/') []), Nothing) ->
B.continue $ showAndUpdateSearch "" "" s
(B.VtyEvent (V.EvKey (V.KChar 'y') []), Nothing) -> do
liftIO (yankToClipboard $ spName (selectedPath s))
>>= \case
Right () -> B.continue s
Left n -> B.continue s {aeOpenModal = Just (ModalNotice n)}
(B.VtyEvent (V.EvKey (V.KChar 's') []), Nothing) ->
B.continue $
s
@ -283,8 +291,8 @@ app =
selectPath
(shortestPathTo (aeActualStoreEnv s) (spName path))
closed
-- help modal
(B.VtyEvent (V.EvKey k []), Just ModalHelp)
-- notices
(B.VtyEvent (V.EvKey k []), Just (ModalNotice _))
| k `elem` [V.KChar 'q', V.KEsc] ->
B.continue s {aeOpenModal = Nothing}
-- handle our events
@ -314,6 +322,20 @@ app =
]
)
yankToClipboard :: StoreName s -> IO (Either Notice ())
yankToClipboard p =
Clipboard.copy (toS $ storeNameToPath p)
<&> \case
Right () -> Right ()
Left errs ->
Left $
Notice
"Error"
( T.intercalate "\n" $
"Cannot copy to clipboard: " :
map (" " <>) errs
)
renderMainScreen :: AppEnv s -> B.Widget Widgets
renderMainScreen env@AppEnv {aePrevPane, aeCurrPane, aeNextPane} =
(B.joinBorders . B.border)
@ -373,15 +395,19 @@ helpText =
T.intercalate
"\n"
[ "hjkl/Arrow Keys : Navigate",
"q/Esc: : Quit / close modal",
"w : Open why-depends mode",
"/ : Open search mode",
"s : Change sort order",
"? : Show help"
"y : Yank selected path to clipboard",
"? : Show help",
"q/Esc: : Quit / close modal"
]
renderHelpModal :: B.Widget a
renderHelpModal = renderModal "Help" (B.txt helpText)
helpNotice :: Notice
helpNotice = Notice "Help" helpText
renderNotice :: Notice -> B.Widget a
renderNotice (Notice title txt) = renderModal title (B.txt txt)
renderWhyDependsModal ::
B.GenericList Widgets Seq (NonEmpty (Path s)) ->

51
src/Clipboard.hs Normal file
View File

@ -0,0 +1,51 @@
module Clipboard
( copy,
)
where
import qualified Data.ByteString.Lazy as BL
import qualified System.Process.Typed as P
cmds :: [(Text, [Text])]
cmds =
[ ("xsel", ["-i", "-b"]),
("xclip", ["-selection", "clipboard"]),
("wl-copy", []),
("pbcopy", [])
]
runCmd :: Text -> (Text, [Text]) -> IO (Either Text ())
runCmd txt (cmd, args) =
P.proc (toS cmd) (map toS args)
& P.setStdin (P.byteStringInput $ toUtf8Lazy txt)
& P.readProcess
& try
<&> \case
(Right (ExitSuccess, _, _)) -> Right ()
(Right (ExitFailure e, out, err)) ->
Left $
"Running " <> show (cmd, args) <> " "
<> "failed with exit code "
<> show e
<> ", "
<> "stdout: "
<> decodeUtf8 (BL.toStrict out)
<> ", "
<> "stderr: "
<> decodeUtf8 (BL.toStrict err)
<> "."
(Left (ex :: SomeException)) ->
Left $
"Running " <> show (cmd, args) <> " "
<> "failed with exception: "
<> show ex
<> "."
copy :: Text -> IO (Either [Text] ())
copy txt = go cmds []
where
go [] errs = return $ Left errs
go (x : xs) errs =
runCmd txt x >>= \case
Right () -> return $ Right ()
Left err -> go xs (err : errs)