From 8cc1763237f9f12d25cf00735ce63c5655877899 Mon Sep 17 00:00:00 2001 From: Akshay Nair Date: Sun, 5 Oct 2025 20:21:13 +0530 Subject: Add CmdChain to run commands sequentially in keys --- lib/Daffm/Action/Commands.hs | 6 +++--- lib/Daffm/Action/Core.hs | 4 ++-- lib/Daffm/Configuration.hs | 12 ++++++++---- lib/Daffm/State.hs | 21 ++++++++++++++------- lib/Daffm/Types.hs | 13 +++++++------ notes.org | 8 ++++++-- 6 files changed, 40 insertions(+), 24 deletions(-) diff --git a/lib/Daffm/Action/Commands.hs b/lib/Daffm/Action/Commands.hs index a9b706f..a5116eb 100644 --- a/lib/Daffm/Action/Commands.hs +++ b/lib/Daffm/Action/Commands.hs @@ -4,13 +4,13 @@ module Daffm.Action.Commands where import qualified Brick as M +import Control.Monad (forM_) 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.Text as Text runCmdline :: AppEvent () @@ -24,8 +24,7 @@ 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 + splitCmdArgs = second trimStart . Text.break isSpace mkCmd = \case ("q", _) -> Just CmdQuit ("quit", _) -> Just CmdQuit @@ -57,6 +56,7 @@ processCommand CmdReload = reloadDir processCommand CmdToggleSelection = toggleCurrentFileSelection processCommand CmdClearSelection = clearFileSelections processCommand CmdGoBack = goBackToParentDir +processCommand (CmdChain chain) = forM_ chain processCommand processCommand CmdNoop = pure () evaluateCommand :: Text.Text -> AppEvent () diff --git a/lib/Daffm/Action/Core.hs b/lib/Daffm/Action/Core.hs index f80e41a..a8dad32 100644 --- a/lib/Daffm/Action/Core.hs +++ b/lib/Daffm/Action/Core.hs @@ -65,8 +65,8 @@ cmdSubstitutions cmd = do 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 "%" (escape file) + . Text.replace "%d" (escape 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) diff --git a/lib/Daffm/Configuration.hs b/lib/Daffm/Configuration.hs index 494d2a4..d3691cd 100644 --- a/lib/Daffm/Configuration.hs +++ b/lib/Daffm/Configuration.hs @@ -1,12 +1,13 @@ module Daffm.Configuration where +import Control.Applicative ((<|>)) import Control.Arrow (ArrowChoice (left)) import Control.Exception (throwIO) import qualified Control.Exception as IO import Daffm.Action.Commands (parseCommand) import Daffm.Keymap (parseKeySequence) import Daffm.Types -import Data.Bifunctor (Bifunctor (bimap)) +import Data.Bifunctor (Bifunctor (first)) import qualified Data.Map as Map import Data.Maybe (fromMaybe) import qualified Data.Text as Text @@ -25,7 +26,7 @@ getDefaultConfigFilePath "" = do pure $ joinPath [dir, "config.toml"] getDefaultConfigFilePath name = do dir <- getConfigDir - pure $ joinPath [dir, "config" <> name <> ".toml"] + pure $ joinPath [dir, "config." <> name <> ".toml"] resolveConfigPath :: Maybe String -> IO FilePath resolveConfigPath Nothing = getDefaultConfigFilePath "" @@ -59,8 +60,11 @@ configurationCodec = keymapCodec :: Toml.Key -> Toml.TomlCodec Keymap keymapCodec = Toml.dimap (const Map.empty) toKeymap . keymapRawCodec where - keymapRawCodec = Toml.tableMap Toml._KeyText Toml.text - toKeymap = Map.fromList . map (bimap toKeys toCmd) . Map.toList + keymapRawCodec = Toml.tableMap Toml._KeyText commandCodec + toKeymap = Map.fromList . map (first toKeys) . Map.toList toKeys = fromMaybe [] . parseKeySequence . stripQuotes toCmd = fromMaybe CmdNoop . parseCommand stripQuotes txt = fromMaybe txt (Text.stripPrefix "\"" txt >>= Text.stripSuffix "\"") + commandCodec k = cmdCodec k <|> cmdChainCodec k + cmdCodec = Toml.dimap (const "") toCmd . Toml.text + cmdChainCodec = Toml.dimap (const []) (CmdChain . map toCmd) . Toml.arrayOf Toml._Text diff --git a/lib/Daffm/State.hs b/lib/Daffm/State.hs index fd5818d..355acab 100644 --- a/lib/Daffm/State.hs +++ b/lib/Daffm/State.hs @@ -5,6 +5,7 @@ import qualified Brick.Widgets.List as L import Control.Applicative ((<|>)) import Control.Monad (filterM, forM) import Daffm.Types +import Daffm.Utils (trim) import Data.List (findIndex, sortBy) import qualified Data.Map.Strict as Map import Data.Maybe (fromMaybe) @@ -22,7 +23,7 @@ defaultKeymaps :: Keymap defaultKeymaps = Map.fromList [ ([K.KChar 'q'], CmdQuit), - ([K.KChar 'r'], CmdReload), + ([K.KChar 'r', K.KChar 'r'], CmdReload), ([K.KChar '!'], CmdSetCmdline "!"), ([K.KChar ':'], CmdEnterCmdline), ([K.KChar 'l'], CmdOpenSelection), @@ -51,7 +52,7 @@ mkEmptyAppState config = stateListPositionHistory = Map.empty, stateFileSelections = Set.empty, stateCwd = "", - stateKeyMap = defaultKeymaps <> configKeymap config, + stateKeyMap = configKeymap config <> defaultKeymaps, stateOpenerScript = configOpener config, stateKeySequence = [] } @@ -64,16 +65,22 @@ toggleFileSelection :: FilePathText -> AppState -> AppState toggleFileSelection path st = st {stateFileSelections = toggleSetItem path $ stateFileSelections st} normalizePath :: FilePathText -> IO FilePathText -normalizePath (Text.null -> True) = normalizePath "~" +normalizePath (Text.null -> True) = Text.pack <$> getHomeDirectory normalizePath "~" = Text.pack <$> getHomeDirectory -normalizePath (Text.splitAt 2 -> ("~/", rest)) = do - home <- normalizePath "~" - pure . Text.pack . joinPath $ map Text.unpack [home, rest] +normalizePath (Text.stripPrefix "~/" -> (Just rest)) = do + home <- getHomeDirectory + pure . Text.pack . joinPath $ [home, Text.unpack rest] normalizePath dir = pure dir +stripQuotes :: Text.Text -> Text.Text +stripQuotes txt = fromMaybe txt (double <|> single) + where + double = Text.stripPrefix "\"" txt >>= Text.stripSuffix "\"" + single = Text.stripPrefix "'" txt >>= Text.stripSuffix "'" + loadDirToState :: FilePathText -> AppState -> IO AppState loadDirToState dir' appState@(AppState {stateCwd, stateListPositionHistory}) = do - dir <- normalizePath dir' + dir <- normalizePath . stripQuotes $ trim dir' doesDirectoryExist (Text.unpack dir) >>= \case True -> do setCurrentDirectory $ Text.unpack dir diff --git a/lib/Daffm/Types.hs b/lib/Daffm/Types.hs index d3dcc70..197ab63 100644 --- a/lib/Daffm/Types.hs +++ b/lib/Daffm/Types.hs @@ -34,15 +34,15 @@ data FileInfo = FileInfo data FocusTarget = FocusCmdline | FocusMain deriving (Show, Eq, Ord) data AppState = AppState - { stateFiles :: L.List FocusTarget FileInfo, - stateCmdlineEditor :: CmdlineEditor, + { stateCmdlineEditor :: CmdlineEditor, + stateCwd :: FilePathText, stateFileSelections :: Set.Set FilePathText, + stateFiles :: L.List FocusTarget FileInfo, stateFocusTarget :: FocusTarget, - stateCwd :: FilePathText, - stateListPositionHistory :: Map.Map Text.Text Int, + stateKeyMap :: Keymap, stateKeySequence :: KeySequence, - stateOpenerScript :: Maybe Text.Text, - stateKeyMap :: Keymap + stateListPositionHistory :: Map.Map Text.Text Int, + stateOpenerScript :: Maybe Text.Text } deriving (Show) @@ -65,6 +65,7 @@ data Command | CmdToggleSelection | CmdClearSelection | CmdGoBack + | CmdChain [Command] | CmdNoop deriving (Show, Eq) diff --git a/notes.org b/notes.org index 88db27c..3bfd14c 100644 --- a/notes.org +++ b/notes.org @@ -20,13 +20,17 @@ - [X] Opener configuration - [X] Cli arg parsing (dir arg) - [X] Cli arg parsing (-c flag for custom config path) +- [X] handle on open (for external integrations) (opener in custom config) +- [X] chain multiple commands +- [ ] Store last directory path +- [ ] Store selections path - [ ] Command: search, search-next, search-prev ** Right after -- [ ] copy/paste across instances +- [ ] copy/move/paste selections across instances - user-land solution (write selections to file and read from second instance) - socket +- [ ] think about nvim integration (server + send back) - [ ] Fix keymap evaluation: If v is bound, it doesnt evaluate vd since v was found -- [X] handle on open (for external integrations) (opener in custom config) - [ ] Cmdline history - [ ] Command: pipe (think about this) (pipe selection file names/file contents) - [ ] Allow escaping % in commands -- cgit v1.3.1