From b05be850349dbb813d2af6f3ee7a2fc3bf98b8ef Mon Sep 17 00:00:00 2001 From: Akshay Nair Date: Fri, 3 Oct 2025 21:57:20 +0530 Subject: Refactor to Text --- daffm.cabal | 1 + exe/Main.hs | 5 ++-- lib/Daffm/Action/Cmdline.hs | 27 ++++++++++---------- lib/Daffm/Action/Core.hs | 13 +++++----- lib/Daffm/State.hs | 28 ++++++++++---------- lib/Daffm/Types.hs | 17 ++++++++----- lib/Daffm/View.hs | 62 +++++++++++++++++++++++---------------------- 7 files changed, 80 insertions(+), 73 deletions(-) diff --git a/daffm.cabal b/daffm.cabal index 300f79b..379e91d 100644 --- a/daffm.cabal +++ b/daffm.cabal @@ -18,6 +18,7 @@ common common-config RankNTypes NamedFieldPuns OverloadedStrings + ViewPatterns LambdaCase QuasiQuotes MultiWayIf diff --git a/exe/Main.hs b/exe/Main.hs index 6d14a28..79a49f9 100644 --- a/exe/Main.hs +++ b/exe/Main.hs @@ -3,12 +3,13 @@ module Main where import qualified Brick.Main as M import Control.Monad (void) import qualified Daffm +import qualified Data.Text as Text import System.Directory (getCurrentDirectory) import System.FilePath (takeDirectory) main :: IO () main = do cwd <- getCurrentDirectory - let parentDir = takeDirectory cwd - initialState <- Daffm.loadDirToState cwd parentDir Daffm.mkEmptyAppState + let parentDir = Text.pack $ takeDirectory cwd + initialState <- Daffm.loadDirToState (Text.pack cwd) parentDir Daffm.mkEmptyAppState void $ M.defaultMain Daffm.app initialState diff --git a/lib/Daffm/Action/Cmdline.hs b/lib/Daffm/Action/Cmdline.hs index e7376c3..12f7a9d 100644 --- a/lib/Daffm/Action/Cmdline.hs +++ b/lib/Daffm/Action/Cmdline.hs @@ -8,7 +8,6 @@ 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 @@ -21,9 +20,9 @@ leaveCmdline = clearCmdline >> modify (\st -> st {stateFocusTarget = FocusMain}) enterCmdline :: AppEvent () enterCmdline = modify (\st -> st {stateFocusTarget = FocusCmdline}) -setCmdlineText :: String -> AppEvent () +setCmdlineText :: Text.Text -> AppEvent () setCmdlineText text = - applyCmdlineEdit (const $ Z.stringZipper [text] (Just 1)) + applyCmdlineEdit (const $ Z.textZipper [text] (Just 1)) clearCmdline :: AppEvent () clearCmdline = applyCmdlineEdit Z.clearZipper @@ -34,17 +33,17 @@ runCmdline = do evaluateCommand cmd leaveCmdline where - trimCmd = dropWhile isSpace . dropWhileEnd isSpace . unlines + trimCmd = Text.dropWhile isSpace . Text.dropWhileEnd isSpace . Text.unlines -evaluateCommand :: String -> AppEvent () -evaluateCommand ('!' : '!' : cmd) = do - cmd' <- Text.unpack <$> cmdSubstitutions (Text.pack cmd) +evaluateCommand :: Text.Text -> AppEvent () +evaluateCommand (Text.splitAt 2 -> ("!!", cmd)) = do + cmd' <- Text.unpack <$> cmdSubstitutions cmd suspendAndResume' $ do callCommand cmd' putStrLn "Press any key to continue" >> void getChar reloadDir -evaluateCommand ('!' : cmd) = do - cmd' <- Text.unpack <$> cmdSubstitutions (Text.pack cmd) +evaluateCommand (Text.splitAt 1 -> ("!", cmd)) = do + cmd' <- Text.unpack <$> cmdSubstitutions cmd suspendAndResume' $ callCommand cmd' reloadDir evaluateCommand "delete" = do @@ -54,7 +53,7 @@ evaluateCommand "delete" = do then maybe [] ((: []) . filePath . snd) $ L.listSelectedElement stateFiles else Set.elems stateFileSelections unless (null files) $ do - suspendAndResume' $ callProcess "rm" ("-rfi" : files) + suspendAndResume' $ callProcess "rm" ("-rfi" : map Text.unpack files) reloadDir evaluateCommand _cmd = pure () @@ -63,16 +62,16 @@ cmdSubstitutions cmd = do (AppState {stateFiles, stateCwd, stateFileSelections}) <- get let file = maybe "" (filePath . snd) . L.listSelectedElement $ stateFiles let escape = (\s -> "'" <> s <> "'") . Text.replace "'" "\\'" - let selections = map Text.pack $ Set.elems stateFileSelections + let selections = Set.elems stateFileSelections -- TODO: Escaping % let subst = - Text.replace "%" (Text.pack file) - . Text.replace "%d" (Text.pack stateCwd) + Text.replace "%" file + . Text.replace "%d" stateCwd . Text.replace "%s" (Text.unwords $ map escape selections) . Text.replace "%S" (Text.dropWhileEnd (== '\n') $ Text.unlines selections) pure . subst $ cmd -applyCmdlineEdit :: (Zipper.TextZipper String -> Zipper.TextZipper String) -> AppEvent () +applyCmdlineEdit :: (Zipper.TextZipper Text.Text -> Zipper.TextZipper Text.Text) -> AppEvent () applyCmdlineEdit zipper = do editor <- gets stateCmdlineEditor let editor' = Editor.applyEdit zipper editor diff --git a/lib/Daffm/Action/Core.hs b/lib/Daffm/Action/Core.hs index d18bda9..337801e 100644 --- a/lib/Daffm/Action/Core.hs +++ b/lib/Daffm/Action/Core.hs @@ -8,8 +8,9 @@ import Brick (suspendAndResume') import qualified Brick.Widgets.List as L import Control.Monad.State (MonadIO (liftIO), MonadState, get, gets, modify, put) import Daffm.State -import Daffm.Types (AppEvent, AppState (..), FileInfo (..), FileType (..)) +import Daffm.Types (AppEvent, AppState (..), FileInfo (..), FilePathText, FileType (..)) import qualified Data.Set as Set +import qualified Data.Text as Text import System.Directory (getHomeDirectory) import System.FilePath (takeDirectory) import System.Process (callProcess) @@ -17,7 +18,7 @@ import System.Process (callProcess) modifyM :: (MonadState s m) => (s -> m s) -> m () modifyM f = get >>= f >>= put -loadDir :: FilePath -> FilePath -> AppEvent () +loadDir :: FilePathText -> FilePathText -> AppEvent () loadDir dir parentDir = do modifyM (liftIO . (>>= filterInvalidSelections) . loadDirToState dir parentDir) @@ -29,12 +30,12 @@ reloadDir = do goBackToParentDir :: AppEvent () goBackToParentDir = do dir <- gets stateParentDir - loadDir dir (takeDirectory dir) + loadDir dir (Text.pack . takeDirectory $ Text.unpack dir) goHome :: AppEvent () goHome = do dir <- liftIO getHomeDirectory - loadDir dir (takeDirectory dir) + loadDir (Text.pack dir) (Text.pack $ takeDirectory dir) openSelectedFile :: AppEvent () openSelectedFile = do @@ -47,8 +48,8 @@ openFile (FileInfo {filePath, fileType = Directory}) = do gets stateCwd >>= loadDir filePath openFile (FileInfo {filePath, fileType}) = do suspendAndResume' $ do - putStrLn $ "Opening " <> show fileType <> ": " <> filePath - callProcess "nvim" [filePath] + putStrLn $ "Opening " <> show fileType <> ": " <> Text.unpack filePath + callProcess "nvim" [Text.unpack filePath] currentFile :: AppEvent (Maybe FileInfo) currentFile = do diff --git a/lib/Daffm/State.hs b/lib/Daffm/State.hs index d35dccf..beeb646 100644 --- a/lib/Daffm/State.hs +++ b/lib/Daffm/State.hs @@ -4,12 +4,12 @@ import qualified Brick.Widgets.Edit as Editor import qualified Brick.Widgets.List as L import Control.Applicative ((<|>)) import Control.Monad (filterM, forM) -import Daffm.Types (AppState (..), FileInfo (..), FileType (..), FocusTarget (..)) -import Data.Char (toLower) +import Daffm.Types (AppState (..), FileInfo (..), FilePathText, FileType (..), FocusTarget (..)) import Data.List (findIndex, sortBy) import qualified Data.Map.Strict as Map import Data.Maybe (fromMaybe) import qualified Data.Set as Set +import qualified Data.Text as Text import qualified Data.Text.Zipper.Generic as Zipper import qualified Data.Vector as Vec import System.Directory (listDirectory, makeAbsolute, setCurrentDirectory) @@ -35,12 +35,12 @@ toggleSetItem :: (Ord a) => a -> Set.Set a -> Set.Set a toggleSetItem val set = if val `Set.member` set then Set.delete val set else Set.insert val set -toggleFileSelection :: FilePath -> AppState -> AppState +toggleFileSelection :: FilePathText -> AppState -> AppState toggleFileSelection path st = st {stateFileSelections = toggleSetItem path $ stateFileSelections st} -loadDirToState :: FilePath -> FilePath -> AppState -> IO AppState +loadDirToState :: FilePathText -> FilePathText -> AppState -> IO AppState loadDirToState dir parentDir appState@(AppState {stateCwd, stateListPositionCache}) = do - setCurrentDirectory dir + setCurrentDirectory $ Text.unpack dir files <- listFilesInDir dir let prevDirPosM = findIndex ((== stateCwd) . filePath) files let cachedPosM = Map.lookup dir stateListPositionCache @@ -65,13 +65,13 @@ fileTypeFromStatus s = | Posix.isSymbolicLink s -> SymbolicLink | otherwise -> UnknownFileType -getFileInfo :: FilePath -> IO FileInfo +getFileInfo :: FilePathText -> IO FileInfo getFileInfo name = do - path <- makeAbsolute name + path <- makeAbsolute $ Text.unpack name stat <- Posix.getSymbolicLinkStatus path pure $ FileInfo - { filePath = path, + { filePath = Text.pack path, fileName = name, fileSize = Posix.fileSize stat, fileMode = Posix.fileMode stat, @@ -80,16 +80,16 @@ getFileInfo name = do fileSorter :: FileInfo -> FileInfo -> Ordering fileSorter (FileInfo {fileType = Directory, fileName = fa}) (FileInfo {fileType = Directory, fileName = fb}) = - compare (toLower <$> fa) (toLower <$> fb) + compare (Text.toLower fa) (Text.toLower fb) fileSorter (FileInfo {fileType = Directory}) _ = LT fileSorter _ (FileInfo {fileType = Directory}) = GT fileSorter (FileInfo {fileName = fa}) (FileInfo {fileName = fb}) = - compare (toLower <$> fa) (toLower <$> fb) + compare (Text.toLower fa) (Text.toLower fb) -listFilesInDir :: FilePath -> IO [FileInfo] +listFilesInDir :: FilePathText -> IO [FileInfo] listFilesInDir dir = do - files <- listDirectory dir - sortBy fileSorter <$> forM files getFileInfo + files <- listDirectory (Text.unpack dir) + sortBy fileSorter <$> forM files (getFileInfo . Text.pack) cacheDirPosition :: AppState -> AppState cacheDirPosition appState@(AppState {stateListPositionCache, stateCwd, stateFiles}) = @@ -101,5 +101,5 @@ cacheDirPosition appState@(AppState {stateListPositionCache, stateCwd, stateFile filterInvalidSelections :: AppState -> IO AppState filterInvalidSelections st = do - selections <- filterM fileExist . Set.elems $ stateFileSelections st + selections <- filterM (fileExist . Text.unpack) . Set.elems $ stateFileSelections st pure $ st {stateFileSelections = Set.fromList selections} diff --git a/lib/Daffm/Types.hs b/lib/Daffm/Types.hs index 882a9de..ffff4d0 100644 --- a/lib/Daffm/Types.hs +++ b/lib/Daffm/Types.hs @@ -5,6 +5,7 @@ import qualified Brick.Widgets.Edit as Editor import qualified Brick.Widgets.List as L import qualified Data.Map as Map import qualified Data.Set as Set +import qualified Data.Text as Text import System.Posix.Types (FileMode, FileOffset) data FileType @@ -18,9 +19,11 @@ data FileType | UnknownFileType deriving (Show) +type FilePathText = Text.Text + data FileInfo = FileInfo - { fileName :: String, - filePath :: FilePath, + { fileName :: Text.Text, + filePath :: FilePathText, fileSize :: FileOffset, fileMode :: FileMode, fileType :: FileType @@ -32,14 +35,14 @@ data FocusTarget = FocusCmdline | FocusMain deriving (Show, Eq, Ord) data AppState = AppState { stateFiles :: L.List FocusTarget FileInfo, stateCmdlineEditor :: CmdlineEditor, - stateFileSelections :: Set.Set FilePath, + stateFileSelections :: Set.Set FilePathText, stateFocusTarget :: FocusTarget, - stateCwd :: FilePath, - stateListPositionCache :: Map.Map String Int, - stateParentDir :: FilePath + stateCwd :: FilePathText, + stateListPositionCache :: Map.Map Text.Text Int, + stateParentDir :: FilePathText } deriving (Show) type AppEvent = EventM FocusTarget AppState -type CmdlineEditor = Editor.Editor String FocusTarget +type CmdlineEditor = Editor.Editor Text.Text FocusTarget diff --git a/lib/Daffm/View.hs b/lib/Daffm/View.hs index 9e0294d..d3845ba 100644 --- a/lib/Daffm/View.hs +++ b/lib/Daffm/View.hs @@ -1,13 +1,14 @@ module Daffm.View where import Brick.Types (Widget) -import Brick.Widgets.Core (Padding (Max, Pad), hBox, hLimit, padLeft, padRight, str, vBox, vLimit, withAttr, (<+>)) +import Brick.Widgets.Core (Padding (Max, Pad), hBox, hLimit, padLeft, padRight, 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.Types (AppState (..), FileInfo (..), FileType (..), FocusTarget (..)) import Data.Int (Int64) import qualified Data.Set as Set +import qualified Data.Text as Text import qualified Data.Vector as Vec import System.Posix.Types (FileMode) import qualified System.PosixCompat as Posix @@ -25,7 +26,7 @@ hFixed :: Int -> Widget n -> Widget n hFixed w = hLimit w . padRight Max headerView :: AppState -> Widget n -headerView (AppState {stateCwd}) = str stateCwd +headerView (AppState {stateCwd}) = txt stateCwd fileItemView :: AppState -> Bool -> FileInfo -> Widget FocusTarget fileItemView appState sel fileInfo@(FileInfo {filePath, fileSize, fileType, fileMode}) = @@ -37,12 +38,12 @@ fileItemView appState sel fileInfo@(FileInfo {filePath, fileSize, fileType, file fileNameView sel fileInfo ] where - fileSizeView = str . prettyFileSize . fromIntegral - fileTypeView = str . showFileType - fileModeView = str . showFileMode - fileSelectionView = str $ if Set.member filePath $ stateFileSelections appState then ">" else " " + fileSizeView = txt . prettyFileSize . fromIntegral + fileTypeView = txt . showFileType + fileModeView = txt . showFileMode + fileSelectionView = txt $ if Set.member filePath $ stateFileSelections appState then ">" else " " -showFileType :: FileType -> String +showFileType :: FileType -> Text.Text showFileType Directory = "dir" showFileType SymbolicLink = "link" showFileType UnixSocket = "sock" @@ -52,48 +53,49 @@ showFileType BlockDevice = "bdev" showFileType RegularFile = "file" showFileType UnknownFileType = "?" -showFileMode :: FileMode -> String +showFileMode :: FileMode -> Text.Text showFileMode mode = permchars where perm m c = if Posix.intersectFileModes mode m == m then c else '-' permchars = - [ perm Posix.ownerReadMode 'r', - perm Posix.ownerWriteMode 'w', - perm Posix.ownerExecuteMode 'x', - perm Posix.groupReadMode 'r', - perm Posix.groupWriteMode 'w', - perm Posix.groupExecuteMode 'x', - perm Posix.otherReadMode 'r', - perm Posix.otherWriteMode 'w', - perm Posix.otherExecuteMode 'x' - ] + Text.pack + [ perm Posix.ownerReadMode 'r', + perm Posix.ownerWriteMode 'w', + perm Posix.ownerExecuteMode 'x', + perm Posix.groupReadMode 'r', + perm Posix.groupWriteMode 'w', + perm Posix.groupExecuteMode 'x', + perm Posix.otherReadMode 'r', + perm Posix.otherWriteMode 'w', + perm Posix.otherExecuteMode 'x' + ] fileNameView :: Bool -> FileInfo -> Widget FocusTarget -fileNameView True (FileInfo {fileName, fileType = Directory}) = withAttr directorySelectedAttr $ str $ fileName <> "/" -fileNameView False (FileInfo {fileName, fileType = Directory}) = withAttr directoryAttr $ str $ fileName <> "/" -fileNameView True (FileInfo {fileName}) = withAttr fileSelectedAttr $ str fileName -fileNameView False (FileInfo {fileName}) = withAttr fileAttr $ str fileName +fileNameView True (FileInfo {fileName, fileType = Directory}) = withAttr directorySelectedAttr $ txt $ fileName <> "/" +fileNameView False (FileInfo {fileName, fileType = Directory}) = withAttr directoryAttr $ txt $ fileName <> "/" +fileNameView True (FileInfo {fileName}) = withAttr fileSelectedAttr $ txt fileName +fileNameView False (FileInfo {fileName}) = withAttr fileAttr $ txt fileName cmdlineView :: AppState -> Widget FocusTarget cmdlineView (AppState {stateFocusTarget = FocusCmdline, stateCmdlineEditor}) = - str ":" <+> renderEditor (str . unlines) True stateCmdlineEditor + txt ":" <+> renderEditor (txt . Text.unlines) True stateCmdlineEditor cmdlineView (AppState {stateFiles}) = - hBox [str ":", padLeft Max $ padRight (Pad 1) posIndicator] + hBox [txt ":", padLeft Max $ padRight (Pad 1) posIndicator] where - posIndicator = str $ cur <> "/" <> total + posIndicator = txt $ cur <> "/" <> total cur = case L.listSelected stateFiles of Nothing -> "-" - Just n -> show (n + 1) - total = show $ Vec.length $ L.listElements stateFiles + Just n -> Text.pack $ show (n + 1) + total = Text.pack $ show $ Vec.length $ L.listElements stateFiles -prettyFileSize :: Int64 -> String +prettyFileSize :: Int64 -> Text.Text prettyFileSize i | i >= 2 ^ (40 :: Int64) = format (i `divBy` (2 ** 40)) <> "T" | i >= 2 ^ (30 :: Int64) = format (i `divBy` (2 ** 30)) <> "G" | i >= 2 ^ (20 :: Int64) = format (i `divBy` (2 ** 20)) <> "M" | i >= 2 ^ (10 :: Int64) = format (i `divBy` (2 ** 10)) <> "K" - | otherwise = show i + | otherwise = Text.pack $ show i where - format = printf "%0.1f" + format = Text.pack . printf "%0.1f" divBy :: Int64 -> Double -> Double divBy a b = (fromIntegral a :: Double) / b -- cgit v1.3.1