|
1 | 1 | (ns examples.file-browser |
2 | 2 | "File browser demonstrating list component with a details pane." |
3 | | - (:require [charm.core :as charm] |
4 | | - [clojure.java.io :as io]) |
5 | | - (:import [java.io File] |
6 | | - [java.text SimpleDateFormat] |
7 | | - [java.util Date])) |
| 3 | + (:require |
| 4 | + [charm.ansi.width :as w] |
| 5 | + [charm.components.help :as help] |
| 6 | + [charm.core :as charm] |
| 7 | + [charm.style.border :as border] |
| 8 | + [charm.style.core :as style] |
| 9 | + [clojure.java.io :as io] |
| 10 | + [clojure.string :as str]) |
| 11 | + (:import |
| 12 | + [java.io File] |
| 13 | + [java.text SimpleDateFormat] |
| 14 | + [java.util Date])) |
8 | 15 |
|
9 | 16 | (def title-style |
10 | | - (charm/style :fg charm/magenta :bold true)) |
| 17 | + (style/style :fg charm/magenta :bold true)) |
11 | 18 |
|
12 | 19 | (def path-style |
13 | | - (charm/style :fg 240)) |
| 20 | + (style/style :fg 240)) |
14 | 21 |
|
15 | 22 | (def detail-label-style |
16 | | - (charm/style :fg 240)) |
| 23 | + (style/style :fg 240)) |
17 | 24 |
|
18 | 25 | (def detail-value-style |
19 | | - (charm/style :fg charm/cyan)) |
| 26 | + (style/style :fg charm/cyan)) |
20 | 27 |
|
21 | 28 | (def help-bindings |
22 | 29 | (charm/help-from-pairs |
|
25 | 32 | "Backspace/h" "back" |
26 | 33 | "q" "quit")) |
27 | 34 |
|
28 | | -(def ^:private details-width 34) |
| 35 | +(def ^:private details-width 36) |
| 36 | + |
| 37 | +;; Header (title + path + blank line) + blank line before help + help line |
| 38 | +(def ^:private chrome-height 5) |
29 | 39 |
|
30 | 40 | (defn format-size |
31 | 41 | "Format file size in human-readable format." |
|
74 | 84 | (format-size (:size info))) |
75 | 85 | :data info})) |
76 | 86 |
|
77 | | -(defn- make-file-list [items width] |
| 87 | +(defn- list-width [term-width] |
| 88 | + (max 20 (- term-width details-width))) |
| 89 | + |
| 90 | +(defn- list-height |
| 91 | + "Number of list items visible. Each item takes 2 lines (title + description)." |
| 92 | + [term-height] |
| 93 | + (max 3 (quot (- term-height chrome-height) 2))) |
| 94 | + |
| 95 | +(defn- make-file-list [items term-width term-height] |
78 | 96 | (charm/item-list items |
79 | | - :height 15 |
80 | | - :width width |
| 97 | + :height (list-height term-height) |
| 98 | + :width (list-width term-width) |
81 | 99 | :show-descriptions true |
82 | 100 | :cursor-style (charm/style :fg charm/cyan :bold true))) |
83 | 101 |
|
84 | | -(defn- list-width [term-width] |
85 | | - (max 20 (- term-width details-width 2))) |
86 | | - |
87 | 102 | (defn init [] |
88 | 103 | (let [start-path (System/getProperty "user.dir") |
89 | 104 | files (list-directory start-path) |
|
92 | 107 | :files files |
93 | 108 | :items items |
94 | 109 | :term-width 80 |
95 | | - :file-list (make-file-list items (list-width 80)) |
| 110 | + :term-height 24 |
| 111 | + :file-list (make-file-list items 80 24) |
96 | 112 | :help (charm/help help-bindings :width 60)} |
97 | 113 | nil])) |
98 | 114 |
|
|
106 | 122 | :current-path path |
107 | 123 | :files files |
108 | 124 | :items items |
109 | | - :file-list (make-file-list items (list-width (:term-width state))))) |
| 125 | + :file-list (make-file-list items (:term-width state) (:term-height state)))) |
110 | 126 | state))) |
111 | 127 |
|
112 | 128 | (defn go-up |
|
136 | 152 |
|
137 | 153 | ;; Window resize |
138 | 154 | (charm/window-size? msg) |
139 | | - (let [w (:width msg)] |
| 155 | + (let [w (:width msg) |
| 156 | + h (:height msg)] |
140 | 157 | [(assoc state |
141 | 158 | :term-width w |
142 | | - :file-list (make-file-list (:items state) (list-width w))) |
| 159 | + :term-height h |
| 160 | + :file-list (make-file-list (:items state) w h)) |
143 | 161 | nil]) |
144 | 162 |
|
145 | 163 | ;; Go up directory |
|
164 | 182 | [state] |
165 | 183 | (if-let [selected (charm/list-selected-item (:file-list state))] |
166 | 184 | (let [info (:data selected)] |
167 | | - (str (charm/render detail-label-style "Name ") |
168 | | - (charm/render detail-value-style (:name info)) "\n" |
169 | | - (charm/render detail-label-style "Type ") |
170 | | - (charm/render detail-value-style (if (:directory? info) "Directory" "File")) "\n" |
171 | | - (charm/render detail-label-style "Size ") |
172 | | - (charm/render detail-value-style (format-size (:size info))) "\n" |
173 | | - (charm/render detail-label-style "Modified ") |
174 | | - (charm/render detail-value-style (format-date (:modified info))) "\n" |
175 | | - (charm/render detail-label-style "Access ") |
176 | | - (charm/render detail-value-style |
| 185 | + (str (style/render detail-label-style "Name ") |
| 186 | + (style/render detail-value-style (:name info)) "\n" |
| 187 | + (style/render detail-label-style "Type ") |
| 188 | + (style/render detail-value-style (if (:directory? info) "Directory" "File")) "\n" |
| 189 | + (style/render detail-label-style "Size ") |
| 190 | + (style/render detail-value-style (format-size (:size info))) "\n" |
| 191 | + (style/render detail-label-style "Modified ") |
| 192 | + (style/render detail-value-style (format-date (:modified info))) "\n" |
| 193 | + (style/render detail-label-style "Access ") |
| 194 | + (style/render detail-value-style |
177 | 195 | (str (when (:readable? info) "r") |
178 | 196 | (when (:writable? info) "w") |
179 | 197 | (when (:hidden? info) " (hidden)"))))) |
180 | | - (charm/render detail-label-style "No file selected"))) |
| 198 | + (style/render detail-label-style "No file selected"))) |
| 199 | + |
| 200 | +(defn- two-columns |
| 201 | + "Join left and right text blocks into a fixed-width, fixed-height grid. |
| 202 | + Each row is: left padded to left-width, then right. Rows are filled to height." |
| 203 | + [left right left-width height] |
| 204 | + (let [left-lines (str/split-lines left) |
| 205 | + right-lines (str/split-lines right) |
| 206 | + render-row (fn [i] |
| 207 | + (str (w/pad-right (nth left-lines i "") left-width) |
| 208 | + (nth right-lines i "")))] |
| 209 | + (str/join "\n" (map render-row (range height))))) |
181 | 210 |
|
182 | 211 | (defn view [state] |
183 | 212 | (let [file-list-view (charm/list-view (:file-list state)) |
184 | 213 | details-view (render-details state) |
185 | | - details-style (charm/style :border charm/rounded-border |
| 214 | + details-style (style/style :border border/rounded |
186 | 215 | :border-fg 240 |
187 | 216 | :padding [0 1] |
188 | | - :width (- details-width 4))] |
189 | | - (str (charm/render title-style "File Browser") "\n" |
190 | | - (charm/render path-style (:current-path state)) "\n\n" |
191 | | - (charm/join-horizontal :top |
192 | | - file-list-view |
193 | | - " " |
194 | | - (charm/render details-style details-view)) |
195 | | - "\n\n" |
196 | | - (charm/help-view (:help state))))) |
| 217 | + :width (- details-width 2)) |
| 218 | + content-height (* (list-height (:term-height state)) 2) |
| 219 | + left-w (list-width (:term-width state))] |
| 220 | + (str (style/render title-style "File Browser") "\n" |
| 221 | + (style/render path-style (:current-path state)) "\n\n" |
| 222 | + (two-columns file-list-view |
| 223 | + (style/render details-style details-view) |
| 224 | + left-w |
| 225 | + content-height) |
| 226 | + "\n" |
| 227 | + (help/short-help-view (:help state))))) |
197 | 228 |
|
198 | 229 | (defn -main [& _args] |
199 | 230 | (charm/run {:init init |
|
0 commit comments