aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAkshay Nair <phenax5@gmail.com>2025-10-06 00:56:23 +0530
committerAkshay Nair <phenax5@gmail.com>2025-10-08 13:09:34 +0530
commita9189b183b96634a6af335b447f9aa44263ac304 (patch)
treec10617f4c5090539abab25e170fd8e98f613740f
parent41d8a90746cd97d1dcb8228c1e9a0846ba9d9b6a (diff)
downloaddaffm-a9189b183b96634a6af335b447f9aa44263ac304.tar.gz
daffm-a9189b183b96634a6af335b447f9aa44263ac304.zip
Add symlink resolution for navigation
Diffstat (limited to '')
-rw-r--r--lib/Daffm/Action/Core.hs1
-rw-r--r--lib/Daffm/Attrs.hs12
-rw-r--r--lib/Daffm/State.hs35
-rw-r--r--lib/Daffm/Types.hs4
-rw-r--r--lib/Daffm/View.hs14
-rw-r--r--notes.org7
6 files changed, 63 insertions, 10 deletions
diff --git a/lib/Daffm/Action/Core.hs b/lib/Daffm/Action/Core.hs
index a726c77..72bccef 100644
--- a/lib/Daffm/Action/Core.hs
+++ b/lib/Daffm/Action/Core.hs
@@ -48,6 +48,7 @@ openSelectedFile :: AppEvent ()
openSelectedFile = do
currentFile >>= \case
Just (FileInfo {filePath, fileType = Directory}) -> loadDir filePath
+ Just (FileInfo {filePath, fileLinkType = Just Directory}) -> loadDir filePath
Just _ -> do
opener <- gets (fromMaybe "echo '%F' | xargs -i xdg-open {}" . stateOpenerScript)
cmdSubstitutions opener >>= suspendAndRunShellCommand False
diff --git a/lib/Daffm/Attrs.hs b/lib/Daffm/Attrs.hs
index d7b7097..f1d4e16 100644
--- a/lib/Daffm/Attrs.hs
+++ b/lib/Daffm/Attrs.hs
@@ -15,6 +15,15 @@ fileSelectedAttr = listSelectedAttr <> fileAttr
directoryAttr :: A.AttrName
directoryAttr = listAttr <> A.attrName "directory"
+linkAttr :: A.AttrName
+linkAttr = listAttr <> A.attrName "link"
+
+invalidLinkAttr :: A.AttrName
+invalidLinkAttr = linkAttr <> A.attrName "invalid"
+
+directoryLinkAttr :: A.AttrName
+directoryLinkAttr = linkAttr <> A.attrName "directory"
+
directorySelectedAttr :: A.AttrName
directorySelectedAttr = listSelectedAttr <> directoryAttr
@@ -29,6 +38,9 @@ appAttrMap =
(listSelectedAttr, bg V.black),
(directoryAttr, fg V.brightCyan),
(directorySelectedAttr, fg V.brightCyan),
+ (directoryLinkAttr, fg V.green),
+ (linkAttr, fg V.brightWhite),
+ (invalidLinkAttr, fg V.red),
(fileAttr, fg V.white),
(fileSelectedAttr, fg V.white),
(searchMarchAttr, fg V.magenta)
diff --git a/lib/Daffm/State.hs b/lib/Daffm/State.hs
index 1429897..150f34b 100644
--- a/lib/Daffm/State.hs
+++ b/lib/Daffm/State.hs
@@ -1,11 +1,16 @@
+{-# OPTIONS_GHC -Wno-unrecognised-pragmas #-}
+
+{-# HLINT ignore "Redundant multi-way if" #-}
module Daffm.State where
import qualified Brick.Widgets.Edit as Editor
import qualified Brick.Widgets.List as L
import Control.Applicative ((<|>))
+import Control.Exception (try)
import Control.Monad (filterM, forM)
import Daffm.Types
import Daffm.Utils (trim)
+import Data.Either (fromRight)
import Data.List (findIndex, sortBy)
import qualified Data.Map.Strict as Map
import Data.Maybe (fromMaybe)
@@ -13,7 +18,8 @@ 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 (doesDirectoryExist, doesPathExist, getCurrentDirectory, getHomeDirectory, listDirectory, makeAbsolute, setCurrentDirectory)
+import GHC.IO.Exception (IOException (IOError))
+import System.Directory (doesDirectoryExist, doesPathExist, getCurrentDirectory, getHomeDirectory, getSymbolicLinkTarget, listDirectory, makeAbsolute, setCurrentDirectory)
import System.FilePath (joinPath, takeDirectory)
import System.PosixCompat (fileExist)
import qualified System.PosixCompat as Posix
@@ -73,7 +79,7 @@ textAsString f = Text.pack . f . Text.unpack
loadDirToState :: FilePathText -> AppState -> IO AppState
loadDirToState dir' appState@(AppState {stateCwd, stateListPositionHistory}) = do
normalizedDir <- (normalizePath . stripTrailingSlash . stripQuotes . trim) dir' >>= withCwdFallback
- stat <- Posix.getSymbolicLinkStatus $ Text.unpack normalizedDir
+ stat <- Posix.getFileStatus $ Text.unpack normalizedDir
let (dir, targetFilePathM) =
if Posix.isDirectory stat
then (normalizedDir, Nothing)
@@ -106,20 +112,39 @@ getFileInfo :: FilePathText -> IO FileInfo
getFileInfo name = do
path <- makeAbsolute $ Text.unpack name
stat <- Posix.getSymbolicLinkStatus path
+ let either2Maybe :: Either IOError a -> Maybe a
+ either2Maybe = either (const Nothing) Just
+ linkStat <- either2Maybe <$> try (Posix.getFileStatus path)
+ linkTarget <-
+ if
+ | Posix.isSymbolicLink stat -> Just . Text.pack <$> getSymbolicLinkTarget path
+ | otherwise -> pure Nothing
pure $
FileInfo
{ filePath = Text.pack path,
fileName = name,
fileSize = Posix.fileSize stat,
fileMode = Posix.fileMode stat,
- fileType = fileTypeFromStatus stat
+ fileType = fileTypeFromStatus stat,
+ fileLinkType = fileTypeFromStatus <$> linkStat,
+ fileLinkTarget = linkTarget
}
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, fileName = fa})
+ (FileInfo {fileType = Directory, fileName = fb}) =
+ compare (Text.toLower fa) (Text.toLower fb)
+fileSorter
+ (FileInfo {fileType = SymbolicLink, fileLinkType = Just Directory, fileName = fa})
+ (FileInfo {fileType = SymbolicLink, fileLinkType = Just Directory, fileName = fb}) =
+ compare (Text.toLower fa) (Text.toLower fb)
+fileSorter (FileInfo {fileType = Directory}) (FileInfo {fileType = SymbolicLink, fileLinkType = Just Directory}) = LT
+fileSorter (FileInfo {fileType = SymbolicLink, fileLinkType = Just Directory}) (FileInfo {fileType = Directory}) = GT
fileSorter (FileInfo {fileType = Directory}) _ = LT
fileSorter _ (FileInfo {fileType = Directory}) = GT
+fileSorter (FileInfo {fileLinkType = Just Directory}) _ = LT
+fileSorter _ (FileInfo {fileLinkType = Just Directory}) = GT
fileSorter (FileInfo {fileName = fa}) (FileInfo {fileName = fb}) =
compare (Text.toLower fa) (Text.toLower fb)
diff --git a/lib/Daffm/Types.hs b/lib/Daffm/Types.hs
index dc6ca36..e7751c4 100644
--- a/lib/Daffm/Types.hs
+++ b/lib/Daffm/Types.hs
@@ -30,7 +30,9 @@ data FileInfo = FileInfo
filePath :: FilePathText,
fileSize :: FileOffset,
fileMode :: FileMode,
- fileType :: FileType
+ fileType :: FileType,
+ fileLinkType :: Maybe FileType,
+ fileLinkTarget :: Maybe FilePathText
}
deriving (Show)
diff --git a/lib/Daffm/View.hs b/lib/Daffm/View.hs
index 081cd38..e65a13a 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, searchMarchAttr)
+import Daffm.Attrs (directoryAttr, directoryLinkAttr, directorySelectedAttr, fileAttr, fileSelectedAttr, invalidLinkAttr, linkAttr, searchMarchAttr)
import Daffm.Keymap (showKeySequence)
import Daffm.Types (AppState (..), FileInfo (..), FileType (..), FocusTarget (..))
import Data.Int (Int64)
@@ -78,9 +78,21 @@ showFileMode mode = permchars
fileNameView :: Bool -> FileInfo -> Widget FocusTarget
fileNameView True (FileInfo {fileName, fileType = Directory}) = withAttr directorySelectedAttr $ txt $ fileName <> "/"
fileNameView False (FileInfo {fileName, fileType = Directory}) = withAttr directoryAttr $ txt $ fileName <> "/"
+fileNameView _ file@(FileInfo {fileType = SymbolicLink}) = symbolicLinkNameView file
fileNameView True (FileInfo {fileName}) = withAttr fileSelectedAttr $ txt fileName
fileNameView False (FileInfo {fileName}) = withAttr fileAttr $ txt fileName
+symbolicLinkNameView :: FileInfo -> Widget n
+symbolicLinkNameView (FileInfo {fileName, fileLinkTarget, fileLinkType = Just Directory}) =
+ withAttr directoryLinkAttr (txt $ fileName <> "/") <+> txt " -> " <+> symTargetView (Just Directory) fileLinkTarget
+symbolicLinkNameView (FileInfo {fileName, fileLinkType, fileLinkTarget}) =
+ withAttr linkAttr (txt fileName) <+> txt " -> " <+> symTargetView fileLinkType fileLinkTarget
+
+symTargetView :: Maybe FileType -> Maybe Text.Text -> Widget n
+symTargetView _ Nothing = withAttr invalidLinkAttr $ txt "<none>"
+symTargetView Nothing (Just target) = withAttr invalidLinkAttr $ txt target
+symTargetView _ (Just target) = txt target
+
cmdlineView :: AppState -> Widget FocusTarget
cmdlineView (AppState {stateFocusTarget = FocusCmdline, stateCmdlineEditor}) =
txt ":" <+> renderEditor (txt . Text.unlines) True stateCmdlineEditor
diff --git a/notes.org b/notes.org
index 6638da2..3193ada 100644
--- a/notes.org
+++ b/notes.org
@@ -28,16 +28,17 @@
- [X] Config extend (extend = true will extend config.toml)
- [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)
+- [X] cd into dir symlinks
- [ ] Command: bind
-- [ ] cd into dir symlinks
** Right after
- [ ] cmdline tab completion
- [ ] Allow escaping % in commands
- [ ] theme from config
- [ ] Cmdline history
+- [ ] Store last directory path (for auto cd in shell)
+- [ ] Store selections path (for copy/move/paste across instances)
- [ ] Rethink % substituions
+- [ ] Preserve back directory when going inside sym link to directories
** Later
- [ ] Fix keymap evaluation? (If vd is bound, it doesnt evaluate a v binding)
- [ ] Allow escaping % in commands