diff options
| author | Akshay Nair <phenax5@gmail.com> | 2025-10-03 20:21:31 +0530 |
|---|---|---|
| committer | Akshay Nair <phenax5@gmail.com> | 2025-10-03 21:10:34 +0530 |
| commit | b1f56e140e6166c06a5b1f0a02aa4bc0953d6eb3 (patch) | |
| tree | 0be94058cb050c1571e0d21f806433def5d3ad49 | |
| parent | 4d669ba5d5858e47b8d5723aae89b75481a2df2f (diff) | |
| download | daffm-b1f56e140e6166c06a5b1f0a02aa4bc0953d6eb3.tar.gz daffm-b1f56e140e6166c06a5b1f0a02aa4bc0953d6eb3.zip | |
Add delete command + minor refactors
| -rw-r--r-- | daffm.cabal | 1 | ||||
| -rw-r--r-- | exe/Main.hs | 2 | ||||
| -rw-r--r-- | lib/Daffm.hs | 4 | ||||
| -rw-r--r-- | lib/Daffm/Action/Cmdline.hs | 37 | ||||
| -rw-r--r-- | lib/Daffm/Action/Core.hs | 22 | ||||
| -rw-r--r-- | lib/Daffm/State.hs | 12 | ||||
| -rw-r--r-- | notes.org | 9 |
7 files changed, 56 insertions, 31 deletions
diff --git a/daffm.cabal b/daffm.cabal index c125b10..300f79b 100644 --- a/daffm.cabal +++ b/daffm.cabal @@ -18,6 +18,7 @@ common common-config RankNTypes NamedFieldPuns OverloadedStrings + LambdaCase QuasiQuotes MultiWayIf TemplateHaskell diff --git a/exe/Main.hs b/exe/Main.hs index 5e71066..6d14a28 100644 --- a/exe/Main.hs +++ b/exe/Main.hs @@ -10,5 +10,5 @@ main :: IO () main = do cwd <- getCurrentDirectory let parentDir = takeDirectory cwd - initialState <- Daffm.loadDirInAppState cwd parentDir Daffm.mkEmptyAppState + initialState <- Daffm.loadDirToState cwd parentDir Daffm.mkEmptyAppState void $ M.defaultMain Daffm.app initialState diff --git a/lib/Daffm.hs b/lib/Daffm.hs index ffae42b..afdba11 100644 --- a/lib/Daffm.hs +++ b/lib/Daffm.hs @@ -1,9 +1,9 @@ -module Daffm (app, loadDirInAppState, mkEmptyAppState) where +module Daffm (app, loadDirToState, mkEmptyAppState) where import qualified Brick.Main as M import Daffm.Attrs (appAttrMap) import Daffm.Event (appEvent) -import Daffm.State (loadDirInAppState, mkEmptyAppState) +import Daffm.State (loadDirToState, mkEmptyAppState) import Daffm.Types (AppState (..), FocusTarget) import Daffm.View (appView) diff --git a/lib/Daffm/Action/Cmdline.hs b/lib/Daffm/Action/Cmdline.hs index bf1f3f1..64977aa 100644 --- a/lib/Daffm/Action/Cmdline.hs +++ b/lib/Daffm/Action/Cmdline.hs @@ -3,16 +3,17 @@ 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 (void) +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 Data.List (dropWhileEnd) +import qualified Data.Set as Set import qualified Data.Text as Text import qualified Data.Text.Zipper as Z import qualified Data.Text.Zipper as Zipper -import System.Process (callCommand) +import System.Process (callCommand, callProcess) leaveCmdline :: AppEvent () leaveCmdline = clearCmdline >> modify (\st -> st {stateFocusTarget = FocusMain}) @@ -27,16 +28,6 @@ setCmdlineText text = clearCmdline :: AppEvent () clearCmdline = applyCmdlineEdit Z.clearZipper -cmdSubstitutions :: Text.Text -> AppEvent Text.Text -cmdSubstitutions cmd = do - (AppState {stateFiles, stateCwd}) <- get - let file = maybe "" (filePath . snd) . L.listSelectedElement $ stateFiles - -- TODO: Escaping % - let subst = - Text.replace "%" (Text.pack file) - . Text.replace "%d" (Text.pack stateCwd) - pure . subst $ cmd - runCmdline :: AppEvent () runCmdline = do cmd <- gets (trimCmd . Editor.getEditContents . stateCmdlineEditor) @@ -56,8 +47,30 @@ evaluateCommand ('!' : cmd) = do cmd' <- Text.unpack <$> cmdSubstitutions (Text.pack 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" : 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 = Text.unwords . map (escape . Text.pack) $ Set.elems stateFileSelections + -- TODO: Escaping % + let subst = + Text.replace "%" (Text.pack file) + . Text.replace "%d" (Text.pack stateCwd) + . Text.replace "%s" selections + pure . subst $ cmd + applyCmdlineEdit :: (Zipper.TextZipper String -> Zipper.TextZipper String) -> AppEvent () applyCmdlineEdit zipper = do editor <- gets stateCmdlineEditor diff --git a/lib/Daffm/Action/Core.hs b/lib/Daffm/Action/Core.hs index 9fed688..d18bda9 100644 --- a/lib/Daffm/Action/Core.hs +++ b/lib/Daffm/Action/Core.hs @@ -1,12 +1,13 @@ {-# OPTIONS_GHC -Wno-unrecognised-pragmas #-} {-# HLINT ignore "Use for_" #-} +{-# HLINT ignore "Use <=<" #-} module Daffm.Action.Core where import Brick (suspendAndResume') import qualified Brick.Widgets.List as L import Control.Monad.State (MonadIO (liftIO), MonadState, get, gets, modify, put) -import Daffm.State (loadDirInAppState, toggleFileSelection) +import Daffm.State import Daffm.Types (AppEvent, AppState (..), FileInfo (..), FileType (..)) import qualified Data.Set as Set import System.Directory (getHomeDirectory) @@ -16,32 +17,34 @@ import System.Process (callProcess) modifyM :: (MonadState s m) => (s -> m s) -> m () modifyM f = get >>= f >>= put +loadDir :: FilePath -> FilePath -> AppEvent () +loadDir dir parentDir = do + modifyM (liftIO . (>>= filterInvalidSelections) . loadDirToState dir parentDir) + reloadDir :: AppEvent () reloadDir = do AppState {stateCwd, stateParentDir} <- get - modifyM (liftIO . loadDirInAppState stateCwd stateParentDir) + loadDir stateCwd stateParentDir goBackToParentDir :: AppEvent () goBackToParentDir = do dir <- gets stateParentDir - modifyM (liftIO . loadDirInAppState dir (takeDirectory dir)) + loadDir dir (takeDirectory dir) goHome :: AppEvent () goHome = do dir <- liftIO getHomeDirectory - modifyM (liftIO . loadDirInAppState dir (takeDirectory dir)) + loadDir dir (takeDirectory dir) openSelectedFile :: AppEvent () openSelectedFile = do - fileM <- currentFile - case fileM of + currentFile >>= \case Just file -> openFile file Nothing -> pure () openFile :: FileInfo -> AppEvent () openFile (FileInfo {filePath, fileType = Directory}) = do - (AppState {stateCwd}) <- get - modifyM (liftIO . loadDirInAppState filePath stateCwd) + gets stateCwd >>= loadDir filePath openFile (FileInfo {filePath, fileType}) = do suspendAndResume' $ do putStrLn $ "Opening " <> show fileType <> ": " <> filePath @@ -53,8 +56,7 @@ currentFile = do toggleCurrentFileSelection :: AppEvent () toggleCurrentFileSelection = do - fileM <- currentFile - case fileM of + currentFile >>= \case Just file -> modify $ toggleFileSelection (filePath file) Nothing -> pure () moveCurrent 1 diff --git a/lib/Daffm/State.hs b/lib/Daffm/State.hs index ead5209..d35dccf 100644 --- a/lib/Daffm/State.hs +++ b/lib/Daffm/State.hs @@ -3,7 +3,7 @@ module Daffm.State where import qualified Brick.Widgets.Edit as Editor import qualified Brick.Widgets.List as L import Control.Applicative ((<|>)) -import Control.Monad (forM) +import Control.Monad (filterM, forM) import Daffm.Types (AppState (..), FileInfo (..), FileType (..), FocusTarget (..)) import Data.Char (toLower) import Data.List (findIndex, sortBy) @@ -13,6 +13,7 @@ import qualified Data.Set as Set import qualified Data.Text.Zipper.Generic as Zipper import qualified Data.Vector as Vec import System.Directory (listDirectory, makeAbsolute, setCurrentDirectory) +import System.PosixCompat (fileExist) import qualified System.PosixCompat as Posix mkEditor :: (Zipper.GenericTextZipper a) => a -> Editor.Editor a FocusTarget @@ -37,8 +38,8 @@ toggleSetItem val set = toggleFileSelection :: FilePath -> AppState -> AppState toggleFileSelection path st = st {stateFileSelections = toggleSetItem path $ stateFileSelections st} -loadDirInAppState :: FilePath -> FilePath -> AppState -> IO AppState -loadDirInAppState dir parentDir appState@(AppState {stateCwd, stateListPositionCache}) = do +loadDirToState :: FilePath -> FilePath -> AppState -> IO AppState +loadDirToState dir parentDir appState@(AppState {stateCwd, stateListPositionCache}) = do setCurrentDirectory dir files <- listFilesInDir dir let prevDirPosM = findIndex ((== stateCwd) . filePath) files @@ -97,3 +98,8 @@ cacheDirPosition appState@(AppState {stateListPositionCache, stateCwd, stateFile } where pos = fromMaybe 0 $ L.listSelected stateFiles + +filterInvalidSelections :: AppState -> IO AppState +filterInvalidSelections st = do + selections <- filterM fileExist . Set.elems $ stateFileSelections st + pure $ st {stateFileSelections = Set.fromList selections} @@ -6,20 +6,23 @@ - [X] Command substitution % - [X] Command substitution %d:cwd - [X] Show file permissions -- [ ] select multiple files -- [ ] Commands +- [X] select multiple files +- [X] Command substitution %s:selections - [ ] Error handling - [ ] Cli arg parsing +- [ ] Command parsing +- [ ] pipe command: Pipe selections/current to stdin of command ** Later - [ ] handle on open (for external integrations) - [ ] Cmdline history -- [ ] Command substitution %s:selections - [ ] cd into dir symlinks - [ ] 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 - [ ] cmdline tab completion +- [ ] Allow escaping % in commands |
