aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--daffm.cabal13
-rw-r--r--flake.nix5
-rw-r--r--justfile6
-rw-r--r--lib/Daffm/Action/Cmdline.hs62
-rw-r--r--lib/Daffm/Action/Commands.hs95
-rw-r--r--lib/Daffm/Action/Core.hs7
-rw-r--r--lib/Daffm/Event.hs68
-rw-r--r--lib/Daffm/State.hs29
-rw-r--r--lib/Daffm/Types.hs29
-rw-r--r--lib/Daffm/Utils.hs13
-rw-r--r--notes.org42
-rw-r--r--specs/Specs/FooSpec.hs75
12 files changed, 327 insertions, 117 deletions
diff --git a/daffm.cabal b/daffm.cabal
index 379e91d..8b37971 100644
--- a/daffm.cabal
+++ b/daffm.cabal
@@ -27,8 +27,7 @@ common common-config
build-depends:
array,
base,
- brick <= 2.10,
- containers,
+ brick == 2.4,
data-default <= 0.8.0.1,
directory <= 1.3.9.0,
filepath <= 1.5.4.0,
@@ -60,13 +59,15 @@ library lib-daffm
hs-source-dirs: lib
exposed-modules:
Daffm
- Daffm.View
- Daffm.Types
+ Daffm.Action.Cmdline
+ Daffm.Action.Commands
+ Daffm.Action.Core
Daffm.Attrs
Daffm.Event
Daffm.State
- Daffm.Action.Core
- Daffm.Action.Cmdline
+ Daffm.Types
+ Daffm.Utils
+ Daffm.View
test-suite specs
import: common-config, warnings
diff --git a/flake.nix b/flake.nix
index cc5d30a..3795d37 100644
--- a/flake.nix
+++ b/flake.nix
@@ -30,10 +30,7 @@
pkg-config
];
- devPackages = with pkgs; [
- just
- nodemon
- ];
+ devPackages = with pkgs; [just];
in {
haskellProjects.default = {
inherit projectRoot;
diff --git a/justfile b/justfile
index d22d7d2..9dff968 100644
--- a/justfile
+++ b/justfile
@@ -6,9 +6,3 @@ run *args:
test *args:
cabal test {{args}}
-
-testw *args:
- nodemon -e .hs -w lib -w specs --exec 'clear && just test {{args}}'
-
-build:
- nix build
diff --git a/lib/Daffm/Action/Cmdline.hs b/lib/Daffm/Action/Cmdline.hs
index 12f7a9d..34e5e41 100644
--- a/lib/Daffm/Action/Cmdline.hs
+++ b/lib/Daffm/Action/Cmdline.hs
@@ -1,18 +1,18 @@
module Daffm.Action.Cmdline where
-import Brick (suspendAndResume')
import qualified Brick.Widgets.Edit as Editor
-import qualified Brick.Widgets.List as L
-import Control.Monad (unless, void)
-import Control.Monad.State (get, gets, modify)
-import Daffm.Action.Core (reloadDir)
-import Daffm.Types (AppEvent, AppState (..), FileInfo (..), FocusTarget (..))
-import Data.Char (isSpace)
-import qualified Data.Set as Set
+import Control.Monad.State (gets, modify)
+import qualified Control.Monad.State.Strict as StateStrict
+import Daffm.Types
+import Daffm.Utils (trimStart)
import qualified Data.Text as Text
import qualified Data.Text.Zipper as Z
import qualified Data.Text.Zipper as Zipper
-import System.Process (callCommand, callProcess)
+
+getCmdlineText :: AppEvent Text.Text
+getCmdlineText = StateStrict.gets cmdtext
+ where
+ cmdtext = trimStart . Text.unlines . Editor.getEditContents . stateCmdlineEditor
leaveCmdline :: AppEvent ()
leaveCmdline = clearCmdline >> modify (\st -> st {stateFocusTarget = FocusMain})
@@ -27,50 +27,6 @@ setCmdlineText text =
clearCmdline :: AppEvent ()
clearCmdline = applyCmdlineEdit Z.clearZipper
-runCmdline :: AppEvent ()
-runCmdline = do
- cmd <- gets (trimCmd . Editor.getEditContents . stateCmdlineEditor)
- evaluateCommand cmd
- leaveCmdline
- where
- trimCmd = Text.dropWhile isSpace . Text.dropWhileEnd isSpace . Text.unlines
-
-evaluateCommand :: Text.Text -> AppEvent ()
-evaluateCommand (Text.splitAt 2 -> ("!!", cmd)) = do
- cmd' <- Text.unpack <$> cmdSubstitutions cmd
- suspendAndResume' $ do
- callCommand cmd'
- putStrLn "Press any key to continue" >> void getChar
- reloadDir
-evaluateCommand (Text.splitAt 1 -> ("!", cmd)) = do
- cmd' <- Text.unpack <$> cmdSubstitutions cmd
- suspendAndResume' $ callCommand cmd'
- reloadDir
-evaluateCommand "delete" = do
- (AppState {stateFileSelections, stateFiles}) <- get
- let files =
- if Set.null stateFileSelections
- then maybe [] ((: []) . filePath . snd) $ L.listSelectedElement stateFiles
- else Set.elems stateFileSelections
- unless (null files) $ do
- suspendAndResume' $ callProcess "rm" ("-rfi" : map Text.unpack files)
- reloadDir
-evaluateCommand _cmd = pure ()
-
-cmdSubstitutions :: Text.Text -> AppEvent Text.Text
-cmdSubstitutions cmd = do
- (AppState {stateFiles, stateCwd, stateFileSelections}) <- get
- let file = maybe "" (filePath . snd) . L.listSelectedElement $ stateFiles
- let escape = (\s -> "'" <> s <> "'") . Text.replace "'" "\\'"
- let selections = Set.elems stateFileSelections
- -- TODO: Escaping %
- let subst =
- Text.replace "%" file
- . Text.replace "%d" stateCwd
- . Text.replace "%s" (Text.unwords $ map escape selections)
- . Text.replace "%S" (Text.dropWhileEnd (== '\n') $ Text.unlines selections)
- pure . subst $ cmd
-
applyCmdlineEdit :: (Zipper.TextZipper Text.Text -> Zipper.TextZipper Text.Text) -> AppEvent ()
applyCmdlineEdit zipper = do
editor <- gets stateCmdlineEditor
diff --git a/lib/Daffm/Action/Commands.hs b/lib/Daffm/Action/Commands.hs
new file mode 100644
index 0000000..91e21b6
--- /dev/null
+++ b/lib/Daffm/Action/Commands.hs
@@ -0,0 +1,95 @@
+{-# OPTIONS_GHC -Wno-unrecognised-pragmas #-}
+
+{-# HLINT ignore "Use for_" #-}
+module Daffm.Action.Commands where
+
+import Brick (suspendAndResume')
+import qualified Brick as M
+import qualified Brick.Widgets.List as L
+import Control.Monad (void)
+import Control.Monad.State (get)
+import Daffm.Action.Cmdline
+import Daffm.Action.Core
+import Daffm.Types
+import Daffm.Utils (trimStart)
+import Data.Bifunctor (Bifunctor (second))
+import Data.Char (isSpace)
+import Data.Maybe (fromMaybe)
+import qualified Data.Set as Set
+import qualified Data.Text as Text
+import System.Process (callCommand)
+
+parseCommand :: Text.Text -> Maybe Command
+parseCommand (Text.splitAt 2 -> ("!!", cmd)) = Just $ CmdShell True cmd
+parseCommand (Text.splitAt 1 -> ("!", cmd)) = Just $ CmdShell False cmd
+parseCommand cmd = mkCmd . splitCmdArgs $ trimStart cmd
+ where
+ splitCmdArgs = second trimStart . Text.splitAt cmdEndIdx
+ cmdEndIdx = fromMaybe (Text.length cmd) $ Text.findIndex isSpace cmd
+ mkCmd = \case
+ ("q", _) -> Just CmdQuit
+ ("quit", _) -> Just CmdQuit
+ ("shell!", cmd') -> Just $ CmdShell True cmd'
+ ("shell", cmd') -> Just $ CmdShell False cmd'
+ ("back", _) -> Just CmdGoBack
+ ("open", _) -> Just CmdOpenSelection
+ ("reload", _) -> Just CmdReload
+ ("cd", dir) -> Just $ CmdChangeDir dir
+ ("noop", _) -> Just CmdNoop
+ ("cmdline-enter", _) -> Just CmdEnterCmdline
+ ("cmdline-leave", _) -> Just CmdLeaveCmdline
+ ("cmdline-set", txt) -> Just $ CmdSetCmdline txt
+ ("selection-toggle", _) -> Just CmdToggleSelection
+ ("selection-clear", _) -> Just CmdClearSelection
+ _ -> Nothing
+
+processCommand :: Command -> AppEvent ()
+processCommand (CmdShell True cmd) = do
+ cmd' <- Text.unpack <$> cmdSubstitutions cmd
+ suspendAndResume' $ do
+ callCommand cmd'
+ putStrLn "Press any key to continue" >> void getChar
+ reloadDir
+processCommand (CmdShell False cmd) = do
+ cmd' <- Text.unpack <$> cmdSubstitutions cmd
+ suspendAndResume' $ callCommand cmd'
+ reloadDir
+processCommand CmdQuit = M.halt
+processCommand (CmdSetCmdline txt) = enterCmdline >> setCmdlineText txt
+processCommand CmdEnterCmdline = enterCmdline
+processCommand CmdLeaveCmdline = leaveCmdline
+processCommand CmdOpenSelection = openSelectedFile
+processCommand (CmdChangeDir dir) = changeDir dir
+processCommand CmdReload = reloadDir
+processCommand CmdToggleSelection = toggleCurrentFileSelection
+processCommand CmdClearSelection = clearFileSelections
+processCommand CmdGoBack = goBackToParentDir
+processCommand CmdNoop = pure ()
+
+cmdSubstitutions :: Text.Text -> AppEvent Text.Text
+cmdSubstitutions cmd = do
+ (AppState {stateFiles, stateCwd, stateFileSelections}) <- get
+ let file = maybe "" (filePath . snd) . L.listSelectedElement $ stateFiles
+ let escape = (\s -> "'" <> s <> "'") . Text.replace "'" "\\'"
+ let selections = Set.elems stateFileSelections
+ let selectionsOrCurrent = if Set.null stateFileSelections then [file] else selections
+ let subst =
+ Text.replace "%" file
+ . Text.replace "%d" stateCwd
+ . Text.replace "%s" (Text.unwords $ map escape selections)
+ . Text.replace "%S" (Text.dropWhileEnd (== '\n') $ Text.unlines selections)
+ . Text.replace "%f" (Text.unwords $ map escape selectionsOrCurrent)
+ . Text.replace "%F" (Text.dropWhileEnd (== '\n') $ Text.unlines selectionsOrCurrent)
+ pure . subst $ cmd
+
+runCmdline :: AppEvent ()
+runCmdline = do
+ cmd <- getCmdlineText
+ leaveCmdline
+ evaluateCommand cmd
+
+evaluateCommand :: Text.Text -> AppEvent ()
+evaluateCommand cmdtxt =
+ case parseCommand cmdtxt of
+ Just cmd -> processCommand cmd
+ Nothing -> pure ()
diff --git a/lib/Daffm/Action/Core.hs b/lib/Daffm/Action/Core.hs
index 337801e..ce04073 100644
--- a/lib/Daffm/Action/Core.hs
+++ b/lib/Daffm/Action/Core.hs
@@ -32,10 +32,13 @@ goBackToParentDir = do
dir <- gets stateParentDir
loadDir dir (Text.pack . takeDirectory $ Text.unpack dir)
+changeDir :: FilePathText -> AppEvent ()
+changeDir dir = do
+ loadDir dir (Text.pack $ takeDirectory $ Text.unpack dir)
+
goHome :: AppEvent ()
goHome = do
- dir <- liftIO getHomeDirectory
- loadDir (Text.pack dir) (Text.pack $ takeDirectory dir)
+ liftIO getHomeDirectory >>= changeDir . Text.pack
openSelectedFile :: AppEvent ()
openSelectedFile = do
diff --git a/lib/Daffm/Event.hs b/lib/Daffm/Event.hs
index 247719b..262686b 100644
--- a/lib/Daffm/Event.hs
+++ b/lib/Daffm/Event.hs
@@ -1,42 +1,68 @@
module Daffm.Event where
-import qualified Brick.Main as M
import qualified Brick.Types as T
import qualified Brick.Widgets.Edit as Editor
import qualified Brick.Widgets.List as L
-import Control.Monad.State (gets, modify)
+import Control.Monad.State (get, gets, modify)
import Daffm.Action.Cmdline
-import Daffm.Action.Core
+import Daffm.Action.Commands
import Daffm.State (cacheDirPosition)
-import Daffm.Types (AppEvent, AppState (..), FocusTarget (..))
+import Daffm.Types (AppEvent, AppState (..), Command (..), FocusTarget (..), Key, KeyMatchResult (..), KeySequence, Keymap)
+import qualified Data.Map as Map
+import Data.Maybe (fromMaybe)
import qualified Graphics.Vty as V
appEvent :: T.BrickEvent FocusTarget e -> AppEvent ()
appEvent brickevent@(T.VtyEvent event) = do
focusTarget <- gets stateFocusTarget
case (focusTarget, event) of
- (FocusMain, V.EvKey (V.KChar 'l') []) -> openSelectedFile
- (FocusMain, V.EvKey (V.KChar 'h') []) -> goBackToParentDir
- (FocusMain, V.EvKey V.KEnter []) -> openSelectedFile
- (FocusMain, V.EvKey V.KBS []) -> goBackToParentDir
- (FocusMain, V.EvKey (V.KChar '~') []) -> goHome
- (FocusMain, V.EvKey (V.KChar ':') []) -> enterCmdline
- (FocusMain, V.EvKey (V.KChar '!') []) -> setCmdlineText "!" >> enterCmdline
- (FocusMain, V.EvKey (V.KChar 'q') []) -> M.halt
- (FocusMain, V.EvKey (V.KChar 'r') [V.MCtrl]) -> reloadDir
- (FocusMain, V.EvKey (V.KChar 'v') []) -> toggleCurrentFileSelection
- (FocusMain, V.EvKey (V.KChar 'C') []) -> clearFileSelections
- -- Just for testing
- (FocusMain, V.EvKey (V.KChar 'p') [V.MCtrl]) -> evaluateCommand "!!chafa -f kitty %"
(FocusCmdline, V.EvKey V.KEsc []) -> leaveCmdline
(FocusCmdline, V.EvKey V.KEnter []) -> runCmdline
- (FocusMain, _) -> do
- files <- gets stateFiles
- newFiles <- T.nestEventM' files (L.handleListEventVi L.handleListEvent event)
- modify (\appState -> appState {stateFiles = newFiles})
(FocusCmdline, _) -> do
editor <- gets stateCmdlineEditor
newEditor <- T.nestEventM' editor (Editor.handleEditorEvent brickevent)
modify (\appState -> appState {stateCmdlineEditor = newEditor})
+ (FocusMain, V.EvKey key []) -> do
+ appendToKeySequence key
+ processKeySequence >>= \case
+ MatchFailure -> do
+ files <- gets stateFiles
+ newFiles <- T.nestEventM' files (L.handleListEventVi L.handleListEvent event)
+ modify (\appState -> appState {stateFiles = newFiles})
+ _ -> pure ()
+ (FocusMain, _) -> do
+ files <- gets stateFiles
+ newFiles <- T.nestEventM' files (L.handleListEventVi L.handleListEvent event)
+ modify (\appState -> appState {stateFiles = newFiles})
modify cacheDirPosition
appEvent _ = pure ()
+
+matchKeySequence :: Keymap -> KeySequence -> KeyMatchResult
+matchKeySequence keymaps keys
+ | Map.member keys keymaps =
+ MatchSuccess . fromMaybe CmdNoop $ Map.lookup keys keymaps
+ | otherwise = partial keymaps keys
+ where
+ partial _ [] = MatchFailure
+ partial (Map.null -> True) _ = MatchFailure
+ partial keymaps' keys' = if hasMatch then MatchPartial else MatchFailure
+ where
+ hasMatch = any (startsWith keys' . fst) (Map.toList keymaps')
+ startsWith ls1 ls2 = ls1 == take (length ls1) ls2
+
+processKeySequence :: AppEvent KeyMatchResult
+processKeySequence = do
+ (AppState {stateKeyMap, stateKeySequence}) <- get
+ let match = matchKeySequence stateKeyMap stateKeySequence
+ case match of
+ MatchSuccess cmd -> do
+ processCommand cmd
+ modify (\st -> st {stateKeySequence = []})
+ MatchPartial -> pure ()
+ MatchFailure -> do
+ modify (\st -> st {stateKeySequence = []})
+ pure match
+
+appendToKeySequence :: Key -> AppEvent ()
+appendToKeySequence key =
+ modify (\st -> st {stateKeySequence = stateKeySequence st <> [key]})
diff --git a/lib/Daffm/State.hs b/lib/Daffm/State.hs
index beeb646..f45a154 100644
--- a/lib/Daffm/State.hs
+++ b/lib/Daffm/State.hs
@@ -4,7 +4,7 @@ import qualified Brick.Widgets.Edit as Editor
import qualified Brick.Widgets.List as L
import Control.Applicative ((<|>))
import Control.Monad (filterM, forM)
-import Daffm.Types (AppState (..), FileInfo (..), FilePathText, FileType (..), FocusTarget (..))
+import Daffm.Types (AppState (..), Command (..), FileInfo (..), FilePathText, FileType (..), FocusTarget (..))
import Data.List (findIndex, sortBy)
import qualified Data.Map.Strict as Map
import Data.Maybe (fromMaybe)
@@ -12,6 +12,7 @@ import qualified Data.Set as Set
import qualified Data.Text as Text
import qualified Data.Text.Zipper.Generic as Zipper
import qualified Data.Vector as Vec
+import qualified Graphics.Vty as K
import System.Directory (listDirectory, makeAbsolute, setCurrentDirectory)
import System.PosixCompat (fileExist)
import qualified System.PosixCompat as Posix
@@ -28,8 +29,32 @@ mkEmptyAppState =
stateListPositionCache = Map.empty,
stateFileSelections = Set.empty,
stateCwd = "",
- stateParentDir = ""
+ stateParentDir = "",
+ stateKeyMap = defaultKeymaps,
+ stateKeySequence = []
}
+ where
+ defaultKeymaps =
+ Map.fromList
+ [ ([K.KChar 'q'], CmdQuit),
+ ([K.KChar 'r', K.KChar 'r'], CmdReload),
+ ([K.KChar '!'], CmdSetCmdline "!"),
+ ([K.KChar ':'], CmdEnterCmdline),
+ ([K.KChar 'l'], CmdOpenSelection),
+ ([K.KChar 'h'], CmdGoBack),
+ ([K.KEnter], CmdOpenSelection),
+ ([K.KBS], CmdGoBack),
+ ([K.KChar 'v'], CmdToggleSelection),
+ ([K.KChar '\t'], CmdToggleSelection),
+ ([K.KChar 'C'], CmdClearSelection),
+ ([K.KChar '~'], CmdChangeDir "/home/imsohexy"),
+ ([K.KChar 'g', K.KChar 'h'], CmdChangeDir "/home/imsohexy"),
+ ([K.KChar 'g', K.KChar 'd', K.KChar 'c'], CmdChangeDir "/home/imsohexy/Documents"),
+ ([K.KChar 'g', K.KChar 'd', K.KChar 'l'], CmdChangeDir "/home/imsohexy/Downloads"),
+ ([K.KChar 'g', K.KChar 'p'], CmdChangeDir "/home/imsohexy/Pictures"),
+ -- Just for testing
+ ([K.KChar 'p'], CmdShell True "chafa -f kitty %")
+ ]
toggleSetItem :: (Ord a) => a -> Set.Set a -> Set.Set a
toggleSetItem val set =
diff --git a/lib/Daffm/Types.hs b/lib/Daffm/Types.hs
index ffff4d0..953e3a8 100644
--- a/lib/Daffm/Types.hs
+++ b/lib/Daffm/Types.hs
@@ -6,6 +6,7 @@ import qualified Brick.Widgets.List as L
import qualified Data.Map as Map
import qualified Data.Set as Set
import qualified Data.Text as Text
+import qualified Graphics.Vty as V
import System.Posix.Types (FileMode, FileOffset)
data FileType
@@ -39,10 +40,36 @@ data AppState = AppState
stateFocusTarget :: FocusTarget,
stateCwd :: FilePathText,
stateListPositionCache :: Map.Map Text.Text Int,
- stateParentDir :: FilePathText
+ stateParentDir :: FilePathText,
+ stateKeySequence :: KeySequence,
+ stateKeyMap :: Keymap
}
deriving (Show)
type AppEvent = EventM FocusTarget AppState
type CmdlineEditor = Editor.Editor Text.Text FocusTarget
+
+data KeyMatchResult = MatchSuccess Command | MatchPartial | MatchFailure
+ deriving (Show, Eq)
+
+data Command
+ = CmdShell Bool Text.Text
+ | CmdQuit
+ | CmdSetCmdline Text.Text
+ | CmdEnterCmdline
+ | CmdLeaveCmdline
+ | CmdOpenSelection
+ | CmdChangeDir Text.Text
+ | CmdReload
+ | CmdToggleSelection
+ | CmdClearSelection
+ | CmdGoBack
+ | CmdNoop
+ deriving (Show, Eq)
+
+type Key = V.Key
+
+type Keymap = Map.Map [Key] Command
+
+type KeySequence = [Key]
diff --git a/lib/Daffm/Utils.hs b/lib/Daffm/Utils.hs
new file mode 100644
index 0000000..af4e0b2
--- /dev/null
+++ b/lib/Daffm/Utils.hs
@@ -0,0 +1,13 @@
+module Daffm.Utils where
+
+import Data.Char (isSpace)
+import qualified Data.Text as Text
+
+trimStart :: Text.Text -> Text.Text
+trimStart = Text.dropWhile isSpace
+
+trimEnd :: Text.Text -> Text.Text
+trimEnd = Text.dropWhileEnd isSpace
+
+trim :: Text.Text -> Text.Text
+trim = trimStart . trimEnd
diff --git a/notes.org b/notes.org
index 660be4b..c4aebb1 100644
--- a/notes.org
+++ b/notes.org
@@ -1,28 +1,34 @@
-** Current
-- [X] Preserve cursor position per dir while navigating
-- [X] Cmdline must be single line
-- [X] Reload
-- [X] Run shell command
-- [X] Command substitution %
-- [X] Command substitution %d:cwd
-- [X] Show file permissions
+** Essentials
- [X] select multiple files
+- [X] Support multikey bindings
+- [X] Show file permissions
+- [X] Run shell command
+- [X] Reload
+- [X] Preserve cursor position per dir while navigating
+- [X] Command: shell & shell! (alias for !, !!)
- [X] Command substitution %s:selections
+- [X] Command substitution %d:cwd
+- [X] Command substitution %
+- [X] Command parsing
+- [X] Cmdline must be single line
+- [X] Internal commands?
- [ ] Error handling
-- [ ] Command parsing
-- [ ] pipe command: Pipe selections/current to stdin of command
-- [ ] Cli arg parsing
-** Later
-- [ ] handle on open (for external integrations)
- [ ] Cmdline history
+- [ ] parsing key sequence
+- [ ] configuration file (toml?)
+- [ ] Cli arg parsing (dir arg)
+** Right after
- [ ] cd into dir symlinks
+- [ ] handle on open (for external integrations)
- [ ] copy/paste across instances
- user-land solution (write selections to file and read from second instance)
- socket
-- [ ] Internal commands
-- [ ] configuration file (toml?)
-- [ ] support multikey bindings?
-- [ ] bind command: define keybindings
-- [ ] watch for changes
+- [ ] Command: pipe (think about this) (pipe selection file names/file contents)
+- [ ] Allow escaping % in commands
+- [ ] Rethink % substituions
+** Later
- [ ] cmdline tab completion
- [ ] Allow escaping % in commands
+- [ ] watch for changes
+- [ ] bind command: define keybindings
+- [ ] some caching for file list?
diff --git a/specs/Specs/FooSpec.hs b/specs/Specs/FooSpec.hs
index 295ef79..4c5f640 100644
--- a/specs/Specs/FooSpec.hs
+++ b/specs/Specs/FooSpec.hs
@@ -1,10 +1,77 @@
module Specs.FooSpec where
+import Daffm.Action.Commands (parseCommand)
+import Daffm.Event (matchKeySequence)
+import Daffm.Types
+import qualified Data.Map as Map
+import qualified Graphics.Vty as K
import Test.Hspec
test :: SpecWith ()
test = do
- describe "stuff" $ do
- context "when things" $ do
- it "does stuf" $ do
- 1 `shouldBe` 1
+ describe "matchKeySequence" $ do
+ context "when key sequence is empty" $ do
+ it "returns MatchFailure" $ do
+ let keymap = Map.fromList [([K.KChar 'a', K.KChar 'b', K.KChar 'c'], CmdNoop)]
+ matchKeySequence keymap [] `shouldBe` MatchFailure
+
+ context "when keymap is empty" $ do
+ it "returns MatchFailure" $ do
+ matchKeySequence Map.empty [K.KChar 'a'] `shouldBe` MatchFailure
+
+ context "when key sequence does not match any of the keymaps" $ do
+ it "returns MatchPartial" $ do
+ let keymap =
+ Map.fromList
+ [ ([K.KChar 'a', K.KChar 'b', K.KChar 'c'], CmdShell True "1"),
+ ([K.KChar 'a', K.KChar 'd', K.KChar 'e'], CmdShell True "2")
+ ]
+ let keys = [K.KChar 'a', K.KChar 'x']
+ matchKeySequence keymap keys `shouldBe` MatchFailure
+
+ context "when key sequence partially matches one of the keymaps" $ do
+ it "returns MatchPartial" $ do
+ let keymap =
+ Map.fromList
+ [ ([K.KChar 'a', K.KChar 'b', K.KChar 'c'], CmdShell True "1"),
+ ([K.KChar 'a', K.KChar 'd', K.KChar 'e'], CmdShell True "2")
+ ]
+ let keys = [K.KChar 'a', K.KChar 'b']
+ matchKeySequence keymap keys `shouldBe` MatchPartial
+
+ context "when key sequence matches with one of the keymaps" $ do
+ it "returns MatchPartial" $ do
+ let keymap =
+ Map.fromList
+ [ ([K.KChar 'a', K.KChar 'b', K.KChar 'c'], CmdShell True "1"),
+ ([K.KChar 'a', K.KChar 'd', K.KChar 'e'], CmdShell True "2")
+ ]
+ let keys = [K.KChar 'a', K.KChar 'd', K.KChar 'e']
+ matchKeySequence keymap keys `shouldBe` MatchSuccess (CmdShell True "2")
+
+ describe "parseCommand" $ do
+ context "when given an invalid command" $ do
+ it "returns Nothing" $ do
+ parseCommand "aklsdjijm" `shouldBe` Nothing
+ context "when empty command" $ do
+ it "returns Nothing" $ do
+ parseCommand "" `shouldBe` Nothing
+
+ context "when command prefixed with !" $ do
+ it "parses as shell command without wait for key" $ do
+ parseCommand "!ls -la /" `shouldBe` Just (CmdShell False "ls -la /")
+
+ context "when command prefixed with !!" $ do
+ it "parses as shell command with wait for key" $ do
+ parseCommand "!!ls -la /" `shouldBe` Just (CmdShell True "ls -la /")
+
+ context "when given quit" $ do
+ it "parses correctly" $ do
+ parseCommand "quit invalid args" `shouldBe` Just CmdQuit
+ parseCommand "quit" `shouldBe` Just CmdQuit
+
+ context "when given set-cmdline" $ do
+ it "parses correctly" $ do
+ parseCommand "cmdline-set hello" `shouldBe` Just (CmdSetCmdline "hello")
+ parseCommand "cmdline-set" `shouldBe` Just (CmdSetCmdline "")
+ parseCommand "cmdline-set somespaces " `shouldBe` Just (CmdSetCmdline "somespaces ")