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
|
module Daffm.View where
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.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@(AppState {stateFiles}) = [ui]
where
ui = vBox [vLimit 1 header, box, vLimit 1 cmdline]
header = headerView appState
cmdline = cmdlineView appState
box = L.renderListWithIndex (fileItemView appState) True stateFiles
hFixed :: Int -> Widget n -> Widget n
hFixed w = hLimit w . padRight Max
headerView :: AppState -> Widget n
headerView (AppState {stateCwd}) = txt stateCwd
fileItemView :: AppState -> Int -> Bool -> FileInfo -> Widget FocusTarget
fileItemView appState index sel fileInfo@(FileInfo {filePath, fileSize, fileType, fileMode}) =
hBox
[ hFixed 2 fileSelectionView,
hFixed 10 $ fileModeView fileMode,
hFixed 6 $ fileTypeView fileType,
hFixed 7 $ fileSizeView fileSize,
fileNameView sel fileInfo,
searchMatchIndicatorView
]
where
fileSizeView = txt . prettyFileSize . fromIntegral
fileTypeView = txt . showFileType
fileModeView = txt . showFileMode
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 = "dir"
showFileType SymbolicLink = "link"
showFileType UnixSocket = "sock"
showFileType NamedPipe = "pipe"
showFileType CharacterDevice = "cdev"
showFileType BlockDevice = "bdev"
showFileType RegularFile = "file"
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 True (FileInfo {fileName}) = withAttr fileSelectedAttr $ txt fileName
fileNameView False (FileInfo {fileName}) = withAttr fileAttr $ txt fileName
cmdlineView :: AppState -> Widget FocusTarget
cmdlineView (AppState {stateFocusTarget = FocusCmdline, stateCmdlineEditor}) =
txt ":" <+> 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
|