|
1 | 1 | (defpackage :lem/filer |
2 | | - (:use :cl :lem)) |
| 2 | + (:use :cl :lem) |
| 3 | + (:export :subdirectory-p |
| 4 | + :expand-to-directory |
| 5 | + :expand-directory-item |
| 6 | + :find-child-directory-item |
| 7 | + :create-directory-item |
| 8 | + :directory-item |
| 9 | + :directory-item-open-p |
| 10 | + :directory-item-children |
| 11 | + :file-item |
| 12 | + :item-pathname)) |
3 | 13 | (in-package :lem/filer) |
4 | 14 |
|
5 | 15 | (define-attribute triangle-attribute |
6 | 16 | (t :bold t :foreground :base0D)) |
7 | 17 |
|
| 18 | +(define-attribute current-file-attribute |
| 19 | + (t :bold t :background :base01)) |
| 20 | + |
8 | 21 | (define-major-mode filer-mode () |
9 | 22 | (:name "Filer" |
10 | 23 | :keymap *filer-mode-keymap*) |
|
155 | 168 | (defun filer-active-p () |
156 | 169 | (and (lem-core::frame-leftside-window (current-frame)) |
157 | 170 | (eq 'filer-mode |
158 | | - (buffer-major-mode |
| 171 | + (buffer-major-mode |
159 | 172 | (window-buffer |
160 | 173 | (lem-core::frame-leftside-window |
161 | 174 | (current-frame))))))) |
162 | 175 |
|
| 176 | +(defun filer-buffer () |
| 177 | + "Return the Filer buffer if it exists, or nil." |
| 178 | + (alexandria:when-let ((window (frame-leftside-window (current-frame)))) |
| 179 | + (let ((buffer (window-buffer window))) |
| 180 | + (when (eq 'filer-mode (buffer-major-mode buffer)) |
| 181 | + buffer)))) |
| 182 | + |
| 183 | +(defun filer-current-directory () |
| 184 | + "Return the root directory of the current Filer, or nil if Filer is not active." |
| 185 | + (when (filer-active-p) |
| 186 | + (alexandria:when-let ((buffer (filer-buffer))) |
| 187 | + (alexandria:when-let ((root (root-item buffer))) |
| 188 | + (item-pathname root))))) |
| 189 | + |
| 190 | +(defun subdirectory-p (child parent) |
| 191 | + "Return T if CHILD is a subdirectory of PARENT." |
| 192 | + (let ((child-str (namestring child)) |
| 193 | + (parent-str (namestring parent))) |
| 194 | + (and (> (length child-str) (length parent-str)) |
| 195 | + (string= parent-str child-str :end2 (length parent-str))))) |
| 196 | + |
| 197 | +(defun find-child-directory-item (parent-item target-dir) |
| 198 | + "Find a child directory-item in PARENT-ITEM that is on the path to TARGET-DIR." |
| 199 | + (loop :for child :in (directory-item-children parent-item) |
| 200 | + :when (and (typep child 'directory-item) |
| 201 | + (let ((child-path (item-pathname child))) |
| 202 | + (or (uiop:pathname-equal child-path target-dir) |
| 203 | + (subdirectory-p target-dir child-path)))) |
| 204 | + :return child)) |
| 205 | + |
| 206 | +(defun expand-directory-item (item) |
| 207 | + "Expand a directory-item if not already expanded." |
| 208 | + (unless (directory-item-open-p item) |
| 209 | + (setf (directory-item-open-p item) t) |
| 210 | + (setf (directory-item-children item) |
| 211 | + (create-directory-children (item-pathname item))))) |
| 212 | + |
| 213 | +(defun expand-to-directory (root-item target-dir) |
| 214 | + "Expand the tree from ROOT-ITEM to TARGET-DIR. |
| 215 | +Returns T if expansion was performed, NIL otherwise." |
| 216 | + (let ((root-path (item-pathname root-item))) |
| 217 | + (when (or (uiop:pathname-equal root-path target-dir) |
| 218 | + (subdirectory-p target-dir root-path)) |
| 219 | + (expand-directory-item root-item) |
| 220 | + (loop :for current-item := root-item |
| 221 | + :then next-item |
| 222 | + :for next-item := (find-child-directory-item current-item target-dir) |
| 223 | + :while next-item |
| 224 | + :do (expand-directory-item next-item) |
| 225 | + :finally (return t))))) |
| 226 | + |
| 227 | +(defun current-file-overlay (buffer) |
| 228 | + "Get the current file highlight overlay for BUFFER." |
| 229 | + (buffer-value buffer 'current-file-overlay)) |
| 230 | + |
| 231 | +(defun (setf current-file-overlay) (overlay buffer) |
| 232 | + "Set the current file highlight overlay for BUFFER." |
| 233 | + (setf (buffer-value buffer 'current-file-overlay) overlay)) |
| 234 | + |
| 235 | +(defun clear-current-file-highlight (filer-buf) |
| 236 | + "Clear the current file highlight in FILER-BUF." |
| 237 | + (alexandria:when-let ((overlay (current-file-overlay filer-buf))) |
| 238 | + (delete-overlay overlay) |
| 239 | + (setf (current-file-overlay filer-buf) nil))) |
| 240 | + |
| 241 | +(defun highlight-file-in-filer (filer-buf file-path) |
| 242 | + "Highlight FILE-PATH in FILER-BUF and move point to it." |
| 243 | + (clear-current-file-highlight filer-buf) |
| 244 | + (with-point ((point (buffer-point filer-buf))) |
| 245 | + (buffer-start point) |
| 246 | + (loop :while (not (end-buffer-p point)) |
| 247 | + :do (back-to-indentation point) |
| 248 | + (alexandria:when-let ((item (text-property-at point :item))) |
| 249 | + (when (and (typep item 'file-item) |
| 250 | + (uiop:pathname-equal (item-pathname item) file-path)) |
| 251 | + (with-point ((start point) |
| 252 | + (end point)) |
| 253 | + (line-start start) |
| 254 | + (line-end end) |
| 255 | + (let ((overlay (make-overlay start end 'current-file-attribute))) |
| 256 | + (setf (current-file-overlay filer-buf) overlay))) |
| 257 | + (return))) |
| 258 | + (unless (line-offset point 1) |
| 259 | + (return))))) |
| 260 | + |
| 261 | +(defun sync-filer-to-buffer-directory (buffer) |
| 262 | + "Sync the Filer to BUFFER's directory if Filer is active. |
| 263 | +Expands the tree to show the buffer's directory and highlights the current file." |
| 264 | + (when (and (filer-active-p) |
| 265 | + (not (eq 'filer-mode (buffer-major-mode buffer))) |
| 266 | + (not (not-switchable-buffer-p buffer))) |
| 267 | + (alexandria:when-let ((buffer-dir (ignore-errors (probe-file (buffer-directory buffer))))) |
| 268 | + (alexandria:when-let ((filer-buf (filer-buffer))) |
| 269 | + (alexandria:when-let ((root (root-item filer-buf))) |
| 270 | + (when (expand-to-directory root buffer-dir) |
| 271 | + (render filer-buf root)) |
| 272 | + (alexandria:when-let ((file-path (buffer-filename buffer))) |
| 273 | + (highlight-file-in-filer filer-buf file-path))))))) |
| 274 | + |
| 275 | +(add-hook *switch-to-buffer-hook* 'sync-filer-to-buffer-directory) |
| 276 | + |
163 | 277 | (defun deactive-filer () |
164 | 278 | (when (eq (current-window) (lem-core::frame-leftside-window (current-frame))) |
165 | 279 | (next-window)) |
|
0 commit comments