diff options
| -rw-r--r-- | lib/Daffm/Action/Commands.hs | 7 | ||||
| -rw-r--r-- | lib/Daffm/Action/Core.hs | 49 | ||||
| -rw-r--r-- | lib/Daffm/Attrs.hs | 6 | ||||
| -rw-r--r-- | lib/Daffm/State.hs | 12 | ||||
| -rw-r--r-- | lib/Daffm/Types.hs | 11 | ||||
| -rw-r--r-- | lib/Daffm/View.hs | 14 | ||||
| -rw-r--r-- | notes.org | 7 |
7 files changed, 88 insertions, 18 deletions
diff --git a/lib/Daffm/Action/Commands.hs b/lib/Daffm/Action/Commands.hs index a9cf691..35262df 100644 --- a/lib/Daffm/Action/Commands.hs +++ b/lib/Daffm/Action/Commands.hs @@ -9,7 +9,7 @@ import Control.Monad.IO.Class (MonadIO (liftIO)) import Daffm.Action.Cmdline import Daffm.Action.Core import Daffm.Types -import Daffm.Utils (trimStart) +import Daffm.Utils (trim, trimStart) import Data.Bifunctor (Bifunctor (second)) import Data.Char (isSpace) import Data.Maybe (fromMaybe) @@ -45,6 +45,9 @@ parseCommand cmd = mkCmd . splitCmdArgs $ trimStart cmd ("cmdline-set", txt) -> Just $ CmdSetCmdline txt ("selection-toggle", _) -> Just CmdToggleSelection ("selection-clear", _) -> Just CmdClearSelection + ("search", term) -> Just $ CmdSearch $ trim term + ("search-next", _) -> Just $ CmdSearchNext 1 + ("search-prev", _) -> Just $ CmdSearchNext (-1) _ -> Nothing readCommandLines' :: Text.Text -> IO [Text.Text] @@ -83,6 +86,8 @@ 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 CmdNoop = pure () evaluateCommand :: Text.Text -> AppEvent () diff --git a/lib/Daffm/Action/Core.hs b/lib/Daffm/Action/Core.hs index a8dad32..a726c77 100644 --- a/lib/Daffm/Action/Core.hs +++ b/lib/Daffm/Action/Core.hs @@ -13,6 +13,7 @@ import Daffm.Types (AppEvent, AppState (..), FileInfo (..), FilePathText, FileTy import Data.Maybe (fromMaybe) import qualified Data.Set as Set import qualified Data.Text as Text +import qualified Data.Vector as Vec import System.Directory (getHomeDirectory) import qualified System.Exit as Proc import System.FilePath (takeDirectory) @@ -24,6 +25,7 @@ modifyM f = get >>= f >>= put loadDir :: FilePathText -> AppEvent () loadDir dir = do modifyM (liftIO . (>>= filterInvalidSelections) . loadDirToState dir) + applySearch reloadDir :: AppEvent () reloadDir = do @@ -93,9 +95,7 @@ currentFile = do toggleCurrentFileSelection :: AppEvent () toggleCurrentFileSelection = do - currentFile >>= \case - Just file -> modify $ toggleFileSelection (filePath file) - Nothing -> pure () + currentFile >>= maybe (pure ()) (modify . toggleFileSelection . filePath) moveCurrent 1 clearFileSelections :: AppEvent () @@ -106,3 +106,46 @@ moveCurrent :: Int -> AppEvent () moveCurrent count = do files <- gets stateFiles modify $ \s -> s {stateFiles = L.listMoveBy count files} + +setSearchTerm :: Text.Text -> AppEvent () +setSearchTerm "" = modify (\st -> st {stateSearchTerm = Nothing, stateSearchIndex = 0}) +setSearchTerm term = modify (\st -> st {stateSearchTerm = Just term, stateSearchIndex = 0}) + +applySearch :: AppEvent () +applySearch = get >>= apply + where + apply :: AppState -> AppEvent () + apply (AppState {stateSearchTerm = Nothing}) = + modify + (\st -> st {stateSearchMatches = Vec.empty, stateSearchIndex = 0}) + apply (AppState {stateSearchTerm = Just term, stateFiles}) = do + let search (_, FileInfo {fileName}) = Text.toLower term `Text.isInfixOf` Text.toLower fileName + let matches = Vec.map fst . Vec.filter search . Vec.indexed $ L.listElements stateFiles + modify + ( \st -> + st + { stateSearchMatches = matches, + stateSearchIndex = wrapSearchIndex st (stateSearchIndex st) + } + ) + +nextSearchMatch :: AppEvent () +nextSearchMatch = do + st@(AppState {stateSearchMatches, stateFiles, stateSearchIndex}) <- get + let nextFiles = + if Vec.null stateSearchMatches + then stateFiles + else L.listMoveTo (stateSearchMatches Vec.! wrapSearchIndex st stateSearchIndex) stateFiles + modify (\st' -> st' {stateFiles = nextFiles}) + +wrapSearchIndex :: AppState -> Int -> Int +wrapSearchIndex (AppState {stateSearchMatches}) nextIndex = + let matchCount = length stateSearchMatches + in if + | nextIndex < 0 -> matchCount - 1 + | nextIndex >= matchCount && matchCount /= 0 -> nextIndex `mod` matchCount + | otherwise -> nextIndex + +updateSearchIndex :: (Int -> Int) -> AppEvent () +updateSearchIndex upd = + modify (\st -> st {stateSearchIndex = wrapSearchIndex st $ upd $ stateSearchIndex st}) diff --git a/lib/Daffm/Attrs.hs b/lib/Daffm/Attrs.hs index 77f9ee6..d7b7097 100644 --- a/lib/Daffm/Attrs.hs +++ b/lib/Daffm/Attrs.hs @@ -18,6 +18,9 @@ directoryAttr = listAttr <> A.attrName "directory" directorySelectedAttr :: A.AttrName directorySelectedAttr = listSelectedAttr <> directoryAttr +searchMarchAttr :: A.AttrName +searchMarchAttr = listAttr <> A.attrName "match-indicator" + appAttrMap :: A.AttrMap appAttrMap = A.attrMap @@ -27,5 +30,6 @@ appAttrMap = (directoryAttr, fg V.brightCyan), (directorySelectedAttr, fg V.brightCyan), (fileAttr, fg V.white), - (fileSelectedAttr, fg V.white) + (fileSelectedAttr, fg V.white), + (searchMarchAttr, fg V.magenta) ] diff --git a/lib/Daffm/State.hs b/lib/Daffm/State.hs index 210cd8d..1429897 100644 --- a/lib/Daffm/State.hs +++ b/lib/Daffm/State.hs @@ -32,12 +32,16 @@ mkEmptyAppState config = stateCwd = "", stateKeyMap = configKeymap config, stateOpenerScript = configOpener config, - stateKeySequence = [] + stateKeySequence = [], + stateSearchTerm = Nothing, + stateSearchMatches = Vec.empty, + stateSearchIndex = 0 } toggleSetItem :: (Ord a) => a -> Set.Set a -> Set.Set a -toggleSetItem val set = - if Set.member val set then Set.delete val set else Set.insert val set +toggleSetItem val set + | Set.member val set = Set.delete val set + | otherwise = Set.insert val set toggleFileSelection :: FilePathText -> AppState -> AppState toggleFileSelection path st = st {stateFileSelections = toggleSetItem path $ stateFileSelections st} @@ -83,7 +87,7 @@ loadDirToState dir' appState@(AppState {stateCwd, stateListPositionHistory}) = d let targetFilePosM = targetFilePathM >>= \f -> findIndex ((== f) . filePath) files let pos = fromMaybe 0 (targetFilePosM <|> cachedPosM <|> prevDirPosM) let list = L.listMoveTo pos $ L.list FocusMain (Vec.fromList files) 1 - pure $ appState {stateFiles = list, stateCwd = dir} + pure $ appState {stateFiles = list, stateCwd = dir, stateSearchIndex = 0, stateSearchMatches = Vec.empty} False -> pure appState fileTypeFromStatus :: Posix.FileStatus -> FileType diff --git a/lib/Daffm/Types.hs b/lib/Daffm/Types.hs index eb9d9a5..dc6ca36 100644 --- a/lib/Daffm/Types.hs +++ b/lib/Daffm/Types.hs @@ -7,6 +7,7 @@ import Control.Applicative ((<|>)) import qualified Data.Map as Map import qualified Data.Set as Set import qualified Data.Text as Text +import qualified Data.Vector as Vec import qualified Graphics.Vty as K import qualified Graphics.Vty as V import System.Posix.Types (FileMode, FileOffset) @@ -44,7 +45,10 @@ data AppState = AppState stateKeyMap :: Keymap, stateKeySequence :: KeySequence, stateListPositionHistory :: Map.Map Text.Text Int, - stateOpenerScript :: Maybe Text.Text + stateOpenerScript :: Maybe Text.Text, + stateSearchTerm :: Maybe Text.Text, + stateSearchMatches :: Vec.Vector Int, + stateSearchIndex :: Int } deriving (Show) @@ -69,6 +73,8 @@ data Command | CmdClearSelection | CmdGoBack | CmdChain [Command] + | CmdSearch Text.Text + | CmdSearchNext Int | CmdNoop deriving (Show, Eq) @@ -109,6 +115,9 @@ defaultKeymaps = [ ([K.KChar 'q'], CmdQuit), ([K.KChar 'r', K.KChar 'r'], CmdReload), ([K.KChar '!'], CmdSetCmdline "!"), + ([K.KChar '/'], CmdSetCmdline "search "), + ([K.KChar 'n'], CmdSearchNext 1), + ([K.KChar 'N'], CmdSearchNext (-1)), ([K.KChar ':'], CmdEnterCmdline), ([K.KChar 'l'], CmdOpenSelection), ([K.KChar 'h'], CmdGoBack), diff --git a/lib/Daffm/View.hs b/lib/Daffm/View.hs index 387fffc..081cd38 100644 --- a/lib/Daffm/View.hs +++ b/lib/Daffm/View.hs @@ -4,7 +4,7 @@ import Brick.Types (Widget) import Brick.Widgets.Core (Padding (Max, Pad), emptyWidget, hBox, hLimit, padLeft, padRight, str, txt, vBox, vLimit, withAttr, (<+>)) import Brick.Widgets.Edit (renderEditor) import qualified Brick.Widgets.List as L -import Daffm.Attrs (directoryAttr, directorySelectedAttr, fileAttr, fileSelectedAttr) +import Daffm.Attrs (directoryAttr, directorySelectedAttr, fileAttr, fileSelectedAttr, searchMarchAttr) import Daffm.Keymap (showKeySequence) import Daffm.Types (AppState (..), FileInfo (..), FileType (..), FocusTarget (..)) import Data.Int (Int64) @@ -21,7 +21,7 @@ appView appState@(AppState {stateFiles}) = [ui] ui = vBox [vLimit 1 header, box, vLimit 1 cmdline] header = headerView appState cmdline = cmdlineView appState - box = L.renderList (fileItemView appState) True stateFiles + box = L.renderListWithIndex (fileItemView appState) True stateFiles hFixed :: Int -> Widget n -> Widget n hFixed w = hLimit w . padRight Max @@ -29,20 +29,24 @@ hFixed w = hLimit w . padRight Max headerView :: AppState -> Widget n headerView (AppState {stateCwd}) = txt stateCwd -fileItemView :: AppState -> Bool -> FileInfo -> Widget FocusTarget -fileItemView appState sel fileInfo@(FileInfo {filePath, fileSize, fileType, fileMode}) = +fileItemView :: AppState -> Int -> Bool -> FileInfo -> Widget FocusTarget +fileItemView appState index sel fileInfo@(FileInfo {filePath, fileSize, fileType, fileMode}) = hBox [ hFixed 2 fileSelectionView, hFixed 10 $ fileModeView fileMode, hFixed 6 $ fileTypeView fileType, hFixed 7 $ fileSizeView fileSize, - fileNameView sel fileInfo + fileNameView sel fileInfo, + searchMatchIndicatorView ] where fileSizeView = txt . prettyFileSize . fromIntegral fileTypeView = txt . showFileType fileModeView = txt . showFileMode fileSelectionView = txt $ if Set.member filePath $ stateFileSelections appState then ">" else " " + searchMatchIndicatorView + | index `Vec.elem` stateSearchMatches appState = padLeft (Pad 1) $ withAttr searchMarchAttr $ txt "*" + | otherwise = emptyWidget showFileType :: FileType -> Text.Text showFileType Directory = "dir" @@ -26,17 +26,18 @@ - [X] Command: command-shell special variant of shell that evaluates output as commands - [X] Given file name, open dir of file with cursor on file - [X] Config extend (extend = true will extend config.toml) -- [ ] Finish nvim integration -- [ ] Command: search, search-next, search-prev +- [X] Finish nvim integration +- [X] Command: search, search-next, search-prev - [ ] Store last directory path (for auto cd in shell) - [ ] Store selections path (for copy/move/paste across instances) +- [ ] Command: bind +- [ ] cd into dir symlinks ** Right after - [ ] cmdline tab completion - [ ] Allow escaping % in commands - [ ] theme from config - [ ] Cmdline history - [ ] Rethink % substituions -- [ ] cd into dir symlinks ** Later - [ ] Fix keymap evaluation? (If vd is bound, it doesnt evaluate a v binding) - [ ] Allow escaping % in commands |
