From 6c3f7442b92f2fbf2308d93ece448cf3dd759a58 Mon Sep 17 00:00:00 2001 From: Akshay Nair Date: Fri, 7 Nov 2025 11:49:27 +0530 Subject: Add custom commands --- example-config.toml | 82 ++++++++++++++++++++++++++++++++++++++++++++ lib/Daffm/Action/Commands.hs | 62 ++++++++++++++++++--------------- lib/Daffm/Action/Keymap.hs | 2 +- lib/Daffm/Configuration.hs | 12 +++++-- lib/Daffm/State.hs | 1 + lib/Daffm/Types.hs | 8 +++-- notes.org | 3 +- 7 files changed, 137 insertions(+), 33 deletions(-) create mode 100644 example-config.toml diff --git a/example-config.toml b/example-config.toml new file mode 100644 index 0000000..ccadfd8 --- /dev/null +++ b/example-config.toml @@ -0,0 +1,82 @@ +opener = """ +echo "%F" | while IFS= read file; do + case "$(file --mime-type "$file" -bL)" in + image/*) setsid -f sxiv "$file" >/dev/null 2>&1 ;; + video/*|image/gif) setsid -f mpv "$file" >/dev/null 2>&1 ;; + application/pdf) setsid -f zathura "$file" >/dev/null 2>&1 ;; + *) $EDITOR "$file" ;; + esac +done; +""" + +[keymap] +md = "cmdline-set !mkdir -p " +mf = "cmdline-set !touch " +cl = ["cmdline-set !ln -s %f ", "selection-clear"] +rn = ["!!clear; echo '%F' | vidir -v -", "selection-clear"] +dd = "!clear; echo '%F'; rm -rfIv %f" +sdd = "!clear; echo '%F'; sudo rm -rfIv %f" +cc = ["!!clear; echo 'Duplicated %'; cp -r % %.dup", "selection-clear"] +cp = ["!!cp -irv %s -t %d", "selection-clear"] +mv = ["!!mv -iv %s -t %d", "selection-clear"] + +gdl = "cd ~/Downloads" +gdc = "cd ~/Documents" +gp = "cd ~/Pictures" +gsc = "cd ~/Pictures/screenshots" +gdp = "cd ~/dump" +gmnt = "cd /run/media/imsohexy" +gdv = "cd ~/dev/projects" + +# Clipboard +yy = """shell +relpath=$(realpath -s --relative-to="${DAFFM_PATH_RELATIVE_TO:-$PWD}" %) +echo -n "$relpath" | xclip -selection clipboard +""" +YY = "!echo -n % | xclip -selection clipboard" +yf = """shell +xclip -selection clipboard -t $(file --mime-type % -bL) -i % +""" + +# Mark directories +"m1" = """eval echo "map 1 cd %d" """ +"m2" = """eval echo "map 2 cd %d" """ +"m3" = """eval echo "map 3 cd %d" """ +"m4" = """eval echo "map 4 cd %d" """ + +# Alt openers +"du" = "!!dua ." +"\\xz" = "!!unzip %f" +"\\x7" = "!!7z x %f" +"\\xt" = "!!tar xzf %f" +"\\xr" = "!!unrar x %f" +"\\wi" = "!!wine %" +"p" = ["!clear", "preview"] + +[commands] +preview = """!! +file="%args" +if [ -z "$file" ]; then file=%; fi +case "$file" in + *.bmp|*.jpg|*.jpeg|*.png|*.xpm|*.webp|*.gif) chafa -f kitty $file ;; + *.wav|*.mp3|*.flac|*.m4a|*.wma|*.ac3|*.og[agx]|*.opus|*.flac) + mediainfo $file + ;; + *.avi|*.mp4|*.ogv|*.mkv|*.mpg|*.mpeg|*.m2v|*.mov|*.webm|*.mts|*.m4v) + mediainfo $file + ;; + *.pdf) pdftotext $file - | less ;; + *.docx) docx2txt $file - | less ;; + *.md|*.org) glow -s dark -p $file ;; + *.tgz|*.tar.gz) tar tzf $file ;; + *.tar.bz2|*.tbz2) tar tjf $file ;; + *.tar.bz2|*.tbz2) tar tjf $file ;; + *.tar.txz|*.txz) xz --list $file ;; + *.tar) tar tf $file ;; + *.zip|*.jar|*.war|*.ear|*.oxt) unzip -l $file ;; + *.rar) unrar l $file ;; + *.7z) 7z l $file ;; + *.o) nm $file | less ;; + *) bat --color always $file || cat $file ;; +esac +""" diff --git a/lib/Daffm/Action/Commands.hs b/lib/Daffm/Action/Commands.hs index c77a9fc..5538eec 100644 --- a/lib/Daffm/Action/Commands.hs +++ b/lib/Daffm/Action/Commands.hs @@ -62,10 +62,10 @@ parseCommand cmd = mkCmd . splitCmdArgs $ trimStart cmd ("move", Text.stripPrefix "+" -> Just inc) -> Just . CmdMove . MoveDown . read $ Text.unpack inc ("move", Text.stripPrefix "-" -> Just inc) -> Just . CmdMove . MoveUp . read $ Text.unpack inc ("move", readMaybe . Text.unpack -> Just pos) -> Just . CmdMove . MoveTo $ pos - _ -> Nothing + (cmd', args) -> Just $ CmdCustom cmd' args -readCommandLines' :: Text.Text -> IO [Text.Text] -readCommandLines' cmd = do +readCommandLines :: Text.Text -> IO [Text.Text] +readCommandLines cmd = do Proc.withCreateProcess (Proc.shell $ Text.unpack cmd) { Proc.delegate_ctlc = True, @@ -77,47 +77,55 @@ readCommandLines' cmd = do _ <- Proc.waitForProcess p Text.lines <$> maybe (pure "") Text.hGetContents stdout -processCommand :: Command -> AppEvent () -processCommand (CmdShell waitForKey cmd) = do - cmdSubstitutions cmd >>= suspendAndRunShellCommand waitForKey +argSubst :: Text.Text -> Text.Text -> Text.Text +argSubst = Text.replace "%args" + +processCommand :: Command -> CustomArgs -> AppEvent () +processCommand (CmdShell waitForKey cmd) args = do + cmdSubstitutions (argSubst args cmd) >>= suspendAndRunShellCommand waitForKey reloadDir -processCommand (CmdCommandShell cmd) = do - stdout <- cmdSubstitutions cmd >>= liftIO . readCommandLines' +processCommand (CmdCommandShell cmd) args = do + stdout <- cmdSubstitutions (argSubst args cmd) >>= liftIO . readCommandLines forM_ stdout runIfCmd reloadDir where runIfCmd (Text.stripPrefix "" -> Just cmd') = - processCommand $ fromMaybe CmdNoop (parseCommand cmd') + processCommand (fromMaybe CmdNoop (parseCommand cmd')) args runIfCmd _ = pure () -processCommand CmdQuit = M.halt -processCommand (CmdSetCmdline txt) = enterCmdline >> cmdSubstitutions txt >>= setCmdlineText -processCommand CmdEnterCmdline = enterCmdline -processCommand CmdLeaveCmdline = leaveCmdline -processCommand CmdOpenSelection = openSelectedFile -processCommand (CmdChangeDir dir) = cmdSubstitutions dir >>= changeDir -processCommand CmdReload = reloadDir -processCommand CmdToggleSelection = toggleCurrentFileSelection -processCommand CmdClearSelection = clearFileSelections -processCommand CmdGoBack = goBackToParentDir -processCommand (CmdChain chain) = forM_ chain processCommand -processCommand (CmdSearch term) = setSearchTerm term >> applySearch >> nextSearchMatch -processCommand (CmdSearchNext change) = updateSearchIndex (+ change) >> nextSearchMatch -processCommand (CmdKeymapSet keys command) = +processCommand CmdQuit _args = M.halt +processCommand (CmdSetCmdline txt) args = enterCmdline >> cmdSubstitutions (argSubst args txt) >>= setCmdlineText +processCommand CmdEnterCmdline _args = enterCmdline +processCommand CmdLeaveCmdline _args = leaveCmdline +processCommand CmdOpenSelection _args = openSelectedFile +processCommand (CmdChangeDir dir) args = cmdSubstitutions (argSubst args dir) >>= changeDir +processCommand CmdReload _args = reloadDir +processCommand CmdToggleSelection _args = toggleCurrentFileSelection +processCommand CmdClearSelection _args = clearFileSelections +processCommand CmdGoBack _ = goBackToParentDir +processCommand (CmdChain chain) args = forM_ chain (`processCommand` args) +processCommand (CmdSearch term) _ = setSearchTerm term >> applySearch >> nextSearchMatch +processCommand (CmdSearchNext change) _ = updateSearchIndex (+ change) >> nextSearchMatch +processCommand (CmdKeymapSet keys command) _ = modify $ \st -> st {stateKeyMap = Map.insert keys command $ stateKeyMap st} -processCommand (CmdMove move) = moveCursor $ toUpdater move +processCommand (CmdMove move) _ = moveCursor $ toUpdater move where toUpdater MoveToEnd = L.listMoveToEnd toUpdater (MoveTo pos) = L.listMoveTo pos - toUpdater (MoveUp inc) = L.listMoveBy $ - inc + toUpdater (MoveUp inc) = L.listMoveBy $ -inc toUpdater (MoveDown inc) = L.listMoveBy inc moveCursor :: (L.List FocusTarget FileInfo -> L.List FocusTarget FileInfo) -> AppEvent () moveCursor updater = do files <- gets $ updater . stateFiles modify $ \st -> st {stateFiles = files} -processCommand CmdNoop = pure () +processCommand (CmdCustom cmd args) _ = do + myCmd <- gets $ Map.lookup cmd . stateCustomCommands + case myCmd of + Just command -> processCommand command args + Nothing -> pure () +processCommand CmdNoop _ = pure () evaluateCommand :: Text.Text -> AppEvent () evaluateCommand cmdtxt = case parseCommand cmdtxt of - Just cmd -> processCommand cmd + Just cmd -> processCommand cmd "" Nothing -> pure () diff --git a/lib/Daffm/Action/Keymap.hs b/lib/Daffm/Action/Keymap.hs index 4463446..c3079c9 100644 --- a/lib/Daffm/Action/Keymap.hs +++ b/lib/Daffm/Action/Keymap.hs @@ -11,7 +11,7 @@ processKeySequence = do let match = matchKeySequence stateKeyMap stateKeySequence case match of MatchSuccess cmd -> do - processCommand cmd + processCommand cmd "" modify (\st -> st {stateKeySequence = []}) MatchPartial -> pure () MatchFailure -> do diff --git a/lib/Daffm/Configuration.hs b/lib/Daffm/Configuration.hs index 3b61d6e..ee8f88e 100644 --- a/lib/Daffm/Configuration.hs +++ b/lib/Daffm/Configuration.hs @@ -26,7 +26,7 @@ defaultConfiguration = Configuration { configKeymap = defaultKeymaps, configOpener = Nothing, - configTheme = Map.empty, + configCommands = Map.empty, configExtend = Nothing } @@ -102,7 +102,7 @@ configurationCodec = <$> (keymapCodec "keymap" .= configKeymap) <*> (openerCodec "opener" .= configOpener) <*> (extendCodec "extend" .= configExtend) - <*> pure Map.empty .= configTheme + <*> (commandsCodec "commands" .= configCommands) where openerCodec = Toml.dioptional . Toml.text extendCodec = Toml.dioptional . Toml.text @@ -118,3 +118,11 @@ keymapCodec = Toml.dimap (const Map.empty) toKeymap . keymapRawCodec commandCodec k = cmdCodec k <|> cmdChainCodec k cmdCodec = Toml.dimap (const "") toCmd . Toml.text cmdChainCodec = Toml.dimap (const []) (CmdChain . map toCmd) . Toml.arrayOf Toml._Text + +commandsCodec :: Toml.Key -> Toml.TomlCodec (Map.Map Text.Text Command) +commandsCodec = Toml.tableMap Toml._KeyText commandCodec + where + toCmd = fromMaybe CmdNoop . parseCommand + 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 6d7f6ea..040d244 100644 --- a/lib/Daffm/State.hs +++ b/lib/Daffm/State.hs @@ -39,6 +39,7 @@ mkEmptyAppState config = stateKeySequence = [], stateSearchTerm = Nothing, stateSearchMatches = Vec.empty, + stateCustomCommands = configCommands config, stateSearchIndex = 0 } diff --git a/lib/Daffm/Types.hs b/lib/Daffm/Types.hs index d9dd977..15c284b 100644 --- a/lib/Daffm/Types.hs +++ b/lib/Daffm/Types.hs @@ -40,6 +40,7 @@ data FocusTarget = FocusCmdline | FocusMain deriving (Show, Eq, Ord) data AppState = AppState { stateCmdlineEditor :: CmdlineEditor, stateCwd :: FilePathText, + stateCustomCommands :: Map.Map Text.Text Command, stateFileSelections :: Set.Set FilePathText, stateFiles :: L.List FocusTarget FileInfo, stateFocusTarget :: FocusTarget, @@ -53,6 +54,8 @@ data AppState = AppState } deriving (Show) +type CustomArgs = Text.Text + type AppEvent = EventM FocusTarget AppState type CmdlineEditor = Editor.Editor Text.Text FocusTarget @@ -81,6 +84,7 @@ data Command | CmdSearchNext Int | CmdKeymapSet [Key] Command | CmdMove MoveInc + | CmdCustom Text.Text CustomArgs | CmdNoop deriving (Show, Eq) @@ -94,7 +98,7 @@ data Configuration = Configuration { configKeymap :: !Keymap, configOpener :: !(Maybe Text.Text), configExtend :: !(Maybe Text.Text), - configTheme :: !(Map.Map Text.Text Text.Text) + configCommands :: !(Map.Map Text.Text Command) } deriving (Show) @@ -103,7 +107,7 @@ instance Semigroup Configuration where a { configKeymap = configKeymap a <> configKeymap b, configOpener = configOpener a <|> configOpener b, - configTheme = configTheme a <> configTheme b + configCommands = configCommands a <> configCommands b } data Args = Args diff --git a/notes.org b/notes.org index b9f2fb5..d1ff03e 100644 --- a/notes.org +++ b/notes.org @@ -1,9 +1,10 @@ - [X] commands: move cursor: move +1, move -1, move 5, move $, move 0 - [X] rename command-shell to evaluate - [X] Allow recursive config extends (@A -> @B -> @C) -- [ ] custom commands +- [X] custom commands - [X] fix problems with substitutions (maybe switch to env vars?) - [ ] allow escaping % in commands +- [ ] escape % in file paths while substituting - [ ] allow updating selection list (:selection-add, :selection-delete) - [ ] cmdline autocompletion - [ ] handle permissions errors -- cgit v1.3.1