module Daffm.State where 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 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 qualified Graphics.Vty as K import System.Directory (doesDirectoryExist, getHomeDirectory, listDirectory, makeAbsolute, setCurrentDirectory) import System.FilePath (joinPath) import System.PosixCompat (fileExist) import qualified System.PosixCompat as Posix mkEditor :: (Zipper.GenericTextZipper a) => a -> Editor.Editor a FocusTarget mkEditor = Editor.editor FocusCmdline (Just 1) mkEmptyAppState :: Configuration -> AppState mkEmptyAppState config = AppState { stateFiles = L.list FocusMain (Vec.fromList []) 1, stateCmdlineEditor = mkEditor "", stateFocusTarget = FocusMain, stateListPositionCache = Map.empty, stateFileSelections = Set.empty, stateCwd = "", stateKeyMap = defaultKeymaps <> configKeymap config, stateKeySequence = [] } where defaultKeymaps = Map.fromList [ ([K.KChar 'q'], CmdQuit), ([K.KChar 'r', K.KChar 'r'], CmdReload), ([K.KChar '!'], CmdSetCmdline "!"), ([K.KChar ':'], CmdEnterCmdline), ([K.KChar 'l'], CmdOpenSelection), ([K.KChar 'h'], CmdGoBack), ([K.KEnter], CmdOpenSelection), ([K.KBS], CmdGoBack), ([K.KChar 'v'], CmdToggleSelection), ([K.KChar '\t'], CmdToggleSelection), ([K.KChar 'C'], CmdClearSelection), ([K.KChar '~'], CmdChangeDir "~"), ([K.KChar 'g', K.KChar 'h'], CmdChangeDir "~") ] 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 toggleFileSelection :: FilePathText -> AppState -> AppState toggleFileSelection path st = st {stateFileSelections = toggleSetItem path $ stateFileSelections st} normalizePath :: FilePathText -> IO FilePathText normalizePath (Text.null -> True) = normalizePath "~" normalizePath "~" = Text.pack <$> getHomeDirectory normalizePath (Text.splitAt 2 -> ("~/", rest)) = do home <- normalizePath "~" pure . Text.pack . joinPath $ map Text.unpack [home, rest] normalizePath dir = pure dir loadDirToState :: FilePathText -> AppState -> IO AppState loadDirToState dir' appState@(AppState {stateCwd, stateListPositionCache}) = do dir <- normalizePath dir' doesDirectoryExist (Text.unpack dir) >>= \case True -> do setCurrentDirectory $ Text.unpack 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 } False -> pure appState 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 :: FilePathText -> IO FileInfo getFileInfo name = do path <- makeAbsolute $ Text.unpack name stat <- Posix.getSymbolicLinkStatus path pure $ FileInfo { filePath = Text.pack path, fileName = name, fileSize = Posix.fileSize stat, fileMode = Posix.fileMode stat, fileType = fileTypeFromStatus stat } fileSorter :: FileInfo -> FileInfo -> Ordering fileSorter (FileInfo {fileType = Directory, fileName = fa}) (FileInfo {fileType = Directory, fileName = 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 (Text.toLower fa) (Text.toLower fb) listFilesInDir :: FilePathText -> IO [FileInfo] listFilesInDir dir = do files <- listDirectory (Text.unpack dir) sortBy fileSorter <$> forM files (getFileInfo . Text.pack) cacheDirPosition :: AppState -> AppState cacheDirPosition appState@(AppState {stateListPositionCache, stateCwd, stateFiles}) = appState { stateListPositionCache = Map.insert stateCwd pos stateListPositionCache } where pos = fromMaybe 0 $ L.listSelected stateFiles filterInvalidSelections :: AppState -> IO AppState filterInvalidSelections st = do selections <- filterM (fileExist . Text.unpack) . Set.elems $ stateFileSelections st pure $ st {stateFileSelections = Set.fromList selections}