aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--daffm.cabal5
-rw-r--r--lib/Daffm.hs128
-rw-r--r--lib/Daffm/Event.hs62
-rw-r--r--lib/Daffm/State.hs85
-rw-r--r--lib/Daffm/Types.hs4
-rw-r--r--lib/Daffm/View.hs11
-rw-r--r--notes.org6
7 files changed, 168 insertions, 133 deletions
diff --git a/daffm.cabal b/daffm.cabal
index a9cce5f..75a49fe 100644
--- a/daffm.cabal
+++ b/daffm.cabal
@@ -25,7 +25,7 @@ common common-config
build-depends:
array,
base,
- brick <= 2.9,
+ brick <= 2.10,
containers,
data-default <= 0.8.0.1,
directory <= 1.3.9.0,
@@ -33,6 +33,7 @@ common common-config
process <= 1.6.26.1,
unix-compat <= 0.7.4.1,
mtl == 2.3.1,
+ containers <= 0.8,
temporary,
text,
vector,
@@ -59,6 +60,8 @@ library lib-daffm
Daffm.View
Daffm.Types
Daffm.Attrs
+ Daffm.Event
+ Daffm.State
test-suite specs
import: common-config, warnings
diff --git a/lib/Daffm.hs b/lib/Daffm.hs
index a7a3705..ffae42b 100644
--- a/lib/Daffm.hs
+++ b/lib/Daffm.hs
@@ -1,72 +1,11 @@
-module Daffm where
+module Daffm (app, loadDirInAppState, mkEmptyAppState) where
-import Brick (suspendAndResume')
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 (forM)
-import Control.Monad.State (MonadIO (liftIO), MonadState, get, gets, modify, put)
import Daffm.Attrs (appAttrMap)
-import Daffm.Types (AppState (..), FileInfo (..), FileType (..), FocusTarget (FocusCmdline, FocusMain))
+import Daffm.Event (appEvent)
+import Daffm.State (loadDirInAppState, mkEmptyAppState)
+import Daffm.Types (AppState (..), FocusTarget)
import Daffm.View (appView)
-import Data.Char (toLower)
-import Data.List (sortBy)
-import Data.Maybe (fromMaybe)
-import Data.Vector ((!?))
-import qualified Data.Vector as Vec
-import qualified Graphics.Vty as V
-import System.Directory (listDirectory, makeAbsolute, setCurrentDirectory)
-import System.FilePath (takeDirectory)
-import qualified System.PosixCompat as Posix
-import System.Process (callProcess)
-
-type AppEvent = T.EventM FocusTarget AppState
-
-modifyM :: (MonadState s m) => (s -> m s) -> m ()
-modifyM f = get >>= f >>= put
-
-openSelectedFile :: AppEvent ()
-openSelectedFile = do
- AppState {stateFiles, stateCwd} <- get
- let indexM = L.listSelected stateFiles
- let files = L.listElements stateFiles
- case indexM >>= (files !?) of
- Just (FileInfo {filePath, fileType = Directory}) ->
- modifyM (liftIO . loadDirInAppState filePath stateCwd)
- Just (FileInfo {filePath, fileType}) -> do
- suspendAndResume' $ do
- callProcess "nvim" [filePath]
- putStrLn $ "Opening " <> show fileType <> ": " <> filePath
- pure ()
- Nothing -> pure ()
- pure ()
-
-goBackToParentDir :: AppEvent ()
-goBackToParentDir = do
- dir <- gets stateParentDir
- modifyM (liftIO . loadDirInAppState dir (takeDirectory dir))
-
-appEvent :: T.BrickEvent FocusTarget e -> AppEvent ()
-appEvent brickevent@(T.VtyEvent event) = do
- focusTarget <- gets stateFocusTarget
- case (focusTarget, event) of
- (FocusCmdline, V.EvKey V.KEsc []) -> modify (\st -> st {stateFocusTarget = FocusMain})
- (FocusMain, V.EvKey (V.KChar ':') []) -> modify (\st -> st {stateFocusTarget = FocusCmdline})
- (FocusMain, V.EvKey (V.KChar 'q') []) -> M.halt
- (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, _) -> 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})
-appEvent _ = pure ()
app :: M.App AppState e FocusTarget
app =
@@ -77,62 +16,3 @@ app =
M.appStartEvent = pure (),
M.appAttrMap = const appAttrMap
}
-
-fileTypeFromStatus :: Posix.FileStatus -> Maybe FileType
-fileTypeFromStatus s =
- if
- | Posix.isBlockDevice s -> Just BlockDevice
- | Posix.isCharacterDevice s -> Just CharacterDevice
- | Posix.isNamedPipe s -> Just NamedPipe
- | Posix.isRegularFile s -> Just RegularFile
- | Posix.isDirectory s -> Just Directory
- | Posix.isSocket s -> Just UnixSocket
- | Posix.isSymbolicLink s -> Just SymbolicLink
- | otherwise -> Nothing
-
-getFileInfo :: FilePath -> IO FileInfo
-getFileInfo name = do
- path <- makeAbsolute name
- stat <- Posix.getSymbolicLinkStatus path
- pure $
- FileInfo
- { filePath = path,
- fileName = name,
- fileSize = Posix.fileSize stat,
- fileType = fromMaybe RegularFile $ fileTypeFromStatus stat
- }
-
-fileSorter :: FileInfo -> FileInfo -> Ordering
-fileSorter (FileInfo {fileType = Directory, fileName = fa}) (FileInfo {fileType = Directory, fileName = fb}) =
- compare (toLower <$> fa) (toLower <$> fb)
-fileSorter (FileInfo {fileType = Directory}) _ = LT
-fileSorter _ (FileInfo {fileType = Directory}) = GT
-fileSorter (FileInfo {fileName = fa}) (FileInfo {fileName = fb}) =
- compare (toLower <$> fa) (toLower <$> fb)
-
-listFilesInDir :: FilePath -> IO [FileInfo]
-listFilesInDir dir = do
- files <- listDirectory dir
- sortBy fileSorter <$> forM files getFileInfo
-
-loadDirInAppState :: FilePath -> FilePath -> AppState -> IO AppState
-loadDirInAppState dir parentDir appState = do
- setCurrentDirectory dir
- files <- listFilesInDir dir
- pure $
- appState
- { stateFiles = L.list FocusMain (Vec.fromList files) 1,
- stateCwd = dir,
- stateParentDir = parentDir
- }
-
-mkEmptyAppState :: AppState
-mkEmptyAppState =
- AppState
- { stateFiles = L.list FocusMain (Vec.fromList []) 1,
- stateCmdlineEditor = Editor.editor FocusCmdline Nothing "",
- stateFocusTarget = FocusMain,
- -- stateFocusRing = focusRing [FocusMain, FocusCmdline],
- stateCwd = "",
- stateParentDir = ""
- }
diff --git a/lib/Daffm/Event.hs b/lib/Daffm/Event.hs
new file mode 100644
index 0000000..f14ebd8
--- /dev/null
+++ b/lib/Daffm/Event.hs
@@ -0,0 +1,62 @@
+module Daffm.Event where
+
+import Brick (suspendAndResume')
+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 (MonadIO (liftIO), MonadState, get, gets, modify, put)
+import Daffm.State (cacheDirPosition, loadDirInAppState)
+import Daffm.Types (AppState (..), FileInfo (..), FileType (..), FocusTarget (FocusCmdline, FocusMain))
+import Data.Vector ((!?))
+import qualified Graphics.Vty as V
+import System.FilePath (takeDirectory)
+import System.Process (callProcess)
+
+type AppEvent = T.EventM FocusTarget AppState
+
+modifyM :: (MonadState s m) => (s -> m s) -> m ()
+modifyM f = get >>= f >>= put
+
+appEvent :: T.BrickEvent FocusTarget e -> AppEvent ()
+appEvent brickevent@(T.VtyEvent event) = do
+ focusTarget <- gets stateFocusTarget
+ case (focusTarget, event) of
+ (FocusCmdline, V.EvKey V.KEsc []) -> modify (\st -> st {stateFocusTarget = FocusMain})
+ (FocusMain, V.EvKey (V.KChar ':') []) -> modify (\st -> st {stateFocusTarget = FocusCmdline})
+ (FocusMain, V.EvKey (V.KChar 'q') []) -> M.halt
+ (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, _) -> 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})
+ modify cacheDirPosition
+appEvent _ = pure ()
+
+openSelectedFile :: AppEvent ()
+openSelectedFile = do
+ AppState {stateFiles, stateCwd} <- get
+ let indexM = L.listSelected stateFiles
+ let files = L.listElements stateFiles
+ case indexM >>= (files !?) of
+ Just (FileInfo {filePath, fileType = Directory}) ->
+ modifyM (liftIO . loadDirInAppState filePath stateCwd)
+ Just (FileInfo {filePath, fileType}) -> do
+ suspendAndResume' $ do
+ putStrLn $ "Opening " <> show fileType <> ": " <> filePath
+ callProcess "nvim" [filePath]
+ pure ()
+ Nothing -> pure ()
+ pure ()
+
+goBackToParentDir :: AppEvent ()
+goBackToParentDir = do
+ dir <- gets stateParentDir
+ modifyM (liftIO . loadDirInAppState dir (takeDirectory dir))
diff --git a/lib/Daffm/State.hs b/lib/Daffm/State.hs
new file mode 100644
index 0000000..cfa9aeb
--- /dev/null
+++ b/lib/Daffm/State.hs
@@ -0,0 +1,85 @@
+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 Daffm.Types (AppState (..), FileInfo (..), FileType (..), FocusTarget (..))
+import Data.Char (toLower)
+import Data.List (findIndex, sortBy)
+import qualified Data.Map.Strict as Map
+import Data.Maybe (fromMaybe)
+import qualified Data.Vector as Vec
+import System.Directory (listDirectory, makeAbsolute, setCurrentDirectory)
+import qualified System.PosixCompat as Posix
+
+mkEmptyAppState :: AppState
+mkEmptyAppState =
+ AppState
+ { stateFiles = L.list FocusMain (Vec.fromList []) 1,
+ stateCmdlineEditor = Editor.editor FocusCmdline Nothing "",
+ stateFocusTarget = FocusMain,
+ stateListPositionCache = Map.empty,
+ stateCwd = "",
+ stateParentDir = ""
+ }
+
+loadDirInAppState :: FilePath -> FilePath -> AppState -> IO AppState
+loadDirInAppState dir parentDir appState@(AppState {stateCwd, stateListPositionCache}) = do
+ setCurrentDirectory dir
+ files <- listFilesInDir dir
+ let prevDirPosM = findIndex ((== stateCwd) . filePath) files
+ let cachedPosM = Map.lookup dir stateListPositionCache
+ let pos = fromMaybe 0 (cachedPosM <|> prevDirPosM)
+ let list = L.listMoveTo pos $ L.list FocusMain (Vec.fromList files) 1
+ pure $
+ appState
+ { stateFiles = list,
+ stateCwd = dir,
+ stateParentDir = parentDir
+ }
+
+fileTypeFromStatus :: Posix.FileStatus -> FileType
+fileTypeFromStatus s =
+ if
+ | Posix.isBlockDevice s -> BlockDevice
+ | Posix.isCharacterDevice s -> CharacterDevice
+ | Posix.isNamedPipe s -> NamedPipe
+ | Posix.isRegularFile s -> RegularFile
+ | Posix.isDirectory s -> Directory
+ | Posix.isSocket s -> UnixSocket
+ | Posix.isSymbolicLink s -> SymbolicLink
+ | otherwise -> UnknownFileType
+
+getFileInfo :: FilePath -> IO FileInfo
+getFileInfo name = do
+ path <- makeAbsolute name
+ stat <- Posix.getSymbolicLinkStatus path
+ pure $
+ FileInfo
+ { filePath = path,
+ fileName = name,
+ fileSize = Posix.fileSize stat,
+ fileType = fileTypeFromStatus stat
+ }
+
+fileSorter :: FileInfo -> FileInfo -> Ordering
+fileSorter (FileInfo {fileType = Directory, fileName = fa}) (FileInfo {fileType = Directory, fileName = fb}) =
+ compare (toLower <$> fa) (toLower <$> fb)
+fileSorter (FileInfo {fileType = Directory}) _ = LT
+fileSorter _ (FileInfo {fileType = Directory}) = GT
+fileSorter (FileInfo {fileName = fa}) (FileInfo {fileName = fb}) =
+ compare (toLower <$> fa) (toLower <$> fb)
+
+listFilesInDir :: FilePath -> IO [FileInfo]
+listFilesInDir dir = do
+ files <- listDirectory dir
+ sortBy fileSorter <$> forM files getFileInfo
+
+cacheDirPosition :: AppState -> AppState
+cacheDirPosition appState@(AppState {stateListPositionCache, stateCwd, stateFiles}) =
+ appState
+ { stateListPositionCache = Map.insert stateCwd pos stateListPositionCache
+ }
+ where
+ pos = fromMaybe 0 $ L.listSelected stateFiles
diff --git a/lib/Daffm/Types.hs b/lib/Daffm/Types.hs
index 5462851..aaf6083 100644
--- a/lib/Daffm/Types.hs
+++ b/lib/Daffm/Types.hs
@@ -2,6 +2,7 @@ module Daffm.Types where
import qualified Brick.Widgets.Edit as Editor
import qualified Brick.Widgets.List as L
+import qualified Data.Map as Map
import System.Posix.Types (FileOffset)
data FileType
@@ -12,6 +13,7 @@ data FileType
| Directory
| SymbolicLink
| UnixSocket
+ | UnknownFileType
deriving (Show)
data FileInfo = FileInfo
@@ -28,8 +30,8 @@ data AppState = AppState
{ stateFiles :: L.List FocusTarget FileInfo,
stateCmdlineEditor :: Editor.Editor String FocusTarget,
stateFocusTarget :: FocusTarget,
- -- stateFocusRing :: FocusRing FocusTarget,
stateCwd :: FilePath,
+ stateListPositionCache :: Map.Map String Int,
stateParentDir :: FilePath
}
deriving (Show)
diff --git a/lib/Daffm/View.hs b/lib/Daffm/View.hs
index db26a7d..2bfb7dc 100644
--- a/lib/Daffm/View.hs
+++ b/lib/Daffm/View.hs
@@ -1,7 +1,7 @@
module Daffm.View where
import Brick.Types (Widget)
-import Brick.Widgets.Core (Padding (Max, Pad), TextWidth (textWidth), hBox, hLimit, padLeft, padRight, str, vBox, vLimit, withAttr, (<+>))
+import Brick.Widgets.Core (Padding (Max, Pad), hBox, hLimit, padLeft, padRight, str, vBox, vLimit, withAttr, (<+>))
import Brick.Widgets.Edit (renderEditor)
import qualified Brick.Widgets.List as L
import Daffm.Attrs (directoryAttr, directorySelectedAttr, fileAttr, fileSelectedAttr)
@@ -20,14 +20,14 @@ appView appState@(AppState {stateFiles, stateCwd}) = [ui]
box :: Widget FocusTarget
box = L.renderList fileItemView True stateFiles
-fixedColumnsStr :: Int -> Widget n -> Widget n
-fixedColumnsStr w s = hLimit w $ padRight Max s
+hFixed :: Int -> Widget n -> Widget n
+hFixed w = hLimit w . padRight Max
fileItemView :: Bool -> FileInfo -> Widget FocusTarget
fileItemView sel fileInfo@(FileInfo {fileSize, fileType}) =
hBox
- [ fixedColumnsStr 5 (fileTypeView fileType),
- fixedColumnsStr 7 (fileSizeView fileSize),
+ [ hFixed 5 (fileTypeView fileType),
+ hFixed 7 (fileSizeView fileSize),
fileNameView sel fileInfo
]
where
@@ -40,6 +40,7 @@ fileItemView sel fileInfo@(FileInfo {fileSize, fileType}) =
showFileType CharacterDevice = "cdev"
showFileType BlockDevice = "bdev"
showFileType RegularFile = "file"
+ showFileType UnknownFileType = "?"
fileNameView :: Bool -> FileInfo -> Widget FocusTarget
fileNameView True (FileInfo {fileName, fileType = Directory}) = withAttr directorySelectedAttr $ str $ fileName <> "/"
diff --git a/notes.org b/notes.org
index d7beb46..d7f41a0 100644
--- a/notes.org
+++ b/notes.org
@@ -1,16 +1,18 @@
** Current
-- [ ] Show file permissions
-- [ ] Preserve cursor position per dir while navigating
+- [X] Preserve cursor position per dir while navigating
- [ ] Cmdline must be single line
- [ ] Commands
- [ ] Run shell command
- [ ] Command substitutions (%:filehighlighted %d:cwd %s:selections)
+- [ ] Show file permissions
** Later
- [ ] handle on open (for external integrations)
- [ ] Cmdline history
- [ ] bind command: define keybindings
+- [ ] select multiple files
- [ ] copy/paste across instances
- user-land solution (write selections to file and read from second instance)
- socket
- [ ] support multikey bindings?
- [ ] configuration file (toml?)
+- [ ] watch for changes