1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
|
module Daffm.View where
import Brick.Types (Context (availWidth), Size (Fixed), Widget (Widget, render), getContext)
import Brick.Widgets.Core (Padding (Max, Pad), TextWidth (textWidth), 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
import Daffm.Keymap (showKeySequence)
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
import Text.Printf (printf)
appView :: AppState -> [Widget FocusTarget]
appView appState =
[ vBox
[ headerView appState,
fileListView appState,
messageView appState,
vLimit 1 $ cmdlineView appState
]
]
hFixed :: Int -> Widget n -> Widget n
hFixed w = hLimit w . padRight Max
headerView :: AppState -> Widget n
headerView (AppState {stateCwd}) = vLimit 1 $ Widget Fixed Fixed $ do
c <- getContext
let width = availWidth c - 2 -- with padding
render . toWidget . trunc width $ stateCwd
where
toWidget = padLeft (Pad 1) . padRight (Pad 1) . txt
trunc width text
| width < textWidth text = "…" <> Text.takeEnd (width - 1) text
| otherwise = Text.takeEnd width text
fileListView :: AppState -> Widget FocusTarget
fileListView appState@(AppState {stateFiles}) =
L.renderListWithIndex (fileItemView appState) True stateFiles
fileItemView :: AppState -> Int -> Bool -> FileInfo -> Widget FocusTarget
fileItemView appState index sel fileInfo@(FileInfo {filePath, fileSize, fileType, fileMode, fileUser, fileGroup}) =
hBox
[ hFixed 2 fileSelectionView,
hFixed 1 $ fileTypeView fileType,
hFixed 10 $ fileModeView fileMode,
hFixed 16 $ fileOwnerView fileUser fileGroup,
hFixed 7 $ fileSizeView fileSize,
fileNameView sel fileInfo,
searchMatchIndicatorView
]
where
fileSizeView = txt . prettyFileSize . fromIntegral
fileTypeView = withAttr fileTypeAttr . txt . showFileType
fileModeView = withAttr fileModeAttr . txt . showFileMode
fileOwnerView user group = withAttr fileOwnerAttr . txt $ user <> ":" <> group
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 = "d"
showFileType SymbolicLink = "l"
showFileType UnixSocket = "s"
showFileType NamedPipe = "p"
showFileType CharacterDevice = "c"
showFileType BlockDevice = "b"
showFileType RegularFile = "-"
showFileType UnknownFileType = "?"
showFileMode :: FileMode -> Text.Text
showFileMode mode = permchars
where
perm m c = if Posix.intersectFileModes mode m == m then c else '-'
permchars =
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 $ 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
messageView :: AppState -> Widget FocusTarget
messageView (AppState {stateMessage = Just message}) = txt message
messageView _ = emptyWidget
cmdlineView :: AppState -> Widget FocusTarget
cmdlineView (AppState {stateFocusTarget = FocusCmdline, stateCmdlineEditor}) =
txt ":" <+> editor
where
editor = renderEditor (txt . Text.unlines) True stateCmdlineEditor
cmdlineView (AppState {stateFocusTarget = FocusMain, stateFiles, stateFileSelections, stateKeySequence}) =
hBox
[ txt ":",
rightAligned [keysView, padLeft (Pad 2) selectionsCountView, padLeft (Pad 2) posIndicatorView]
]
where
keysView = txt $ showKeySequence stateKeySequence
rightAligned = padLeft Max . padRight (Pad 1) . hBox
posIndicatorView = str $ cur <> "/" <> total
selectionsCountView =
if Set.null stateFileSelections
then emptyWidget
else str $ show $ Set.size stateFileSelections
cur = case L.listSelected stateFiles of
Nothing -> "-"
Just n -> show (n + 1)
total = show $ Vec.length $ L.listElements stateFiles
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 = Text.pack $ show i
where
format = Text.pack . printf "%0.1f"
divBy :: Int64 -> Double -> Double
divBy a b = (fromIntegral a :: Double) / b
|