2023-10-19 17:49:54 +02:00
|
|
|
;; tinmop: a multiprotocol client
|
2023-10-19 17:46:22 +02:00
|
|
|
;; Copyright © cage
|
2021-12-05 15:28:29 +01:00
|
|
|
|
|
|
|
;; This program is free software: you can redistribute it and/or modify
|
|
|
|
;; it under the terms of the GNU General Public License as published by
|
|
|
|
;; the Free Software Foundation, either version 3 of the License, or
|
|
|
|
;; (at your option) any later version.
|
|
|
|
|
|
|
|
;; This program is distributed in the hope that it will be useful,
|
|
|
|
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
;; GNU General Public License for more details.
|
|
|
|
|
|
|
|
;; You should have received a copy of the GNU General Public License
|
|
|
|
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
(in-package :filesystem-tree-window)
|
|
|
|
|
|
|
|
(defun make-node-data (path dirp)
|
2021-12-10 11:50:37 +01:00
|
|
|
(cond
|
|
|
|
((or (fs:backreference-dir-p path)
|
|
|
|
(fs:loopback-reference-dir-p path)
|
|
|
|
(not dirp))
|
2021-12-13 18:12:20 +01:00
|
|
|
(list :path path :dirp dirp :markedp nil))
|
2021-12-10 11:50:37 +01:00
|
|
|
(dirp
|
2022-01-23 13:06:24 +01:00
|
|
|
(if (fs:path-referencing-dir-p path)
|
2021-12-13 18:12:20 +01:00
|
|
|
(list :path path :dirp t :markedp nil)
|
|
|
|
(list :path (strcat path "/") :dirp t :markedp nil)))))
|
2021-12-05 15:28:29 +01:00
|
|
|
|
|
|
|
(defun make-root-tree (&optional (path "/"))
|
|
|
|
(mtree:make-node (make-node-data path t)))
|
|
|
|
|
2021-12-10 11:50:37 +01:00
|
|
|
(defclass filesystem-tree-window (wrapper-window
|
|
|
|
tree-holder
|
|
|
|
key-config-holder
|
2021-12-05 15:28:29 +01:00
|
|
|
row-oriented-widget
|
|
|
|
focus-marked-window
|
2021-12-10 11:50:37 +01:00
|
|
|
title-window
|
|
|
|
border-window)
|
2021-12-05 15:28:29 +01:00
|
|
|
((filesystem-root
|
|
|
|
:initform (make-root-tree)
|
|
|
|
:initarg :filesystem-root
|
|
|
|
:accessor filesystem-root
|
|
|
|
:type m-tree
|
2021-12-10 11:50:37 +01:00
|
|
|
:documentation "The filesystem tree")
|
|
|
|
(filesystem-expand-function
|
|
|
|
:initform #'expand-local-filesystem-node
|
|
|
|
:accessor filesystem-expand-function
|
2022-01-06 11:39:29 +01:00
|
|
|
:type function
|
|
|
|
:documentation "A function with the node as parameter. Will modify
|
|
|
|
the argument appending its children")
|
2021-12-11 11:06:06 +01:00
|
|
|
(filesystem-rename-function
|
2021-12-10 11:50:37 +01:00
|
|
|
:initform #'rename-local-filesystem-node
|
|
|
|
:accessor filesystem-rename-function
|
2022-01-06 11:39:29 +01:00
|
|
|
:type function
|
|
|
|
:documentation "A function with two parameters: a node and the new
|
|
|
|
name for the path of the matching node")
|
2021-12-11 11:06:06 +01:00
|
|
|
(filesystem-delete-function
|
2021-12-10 15:30:26 +01:00
|
|
|
:initform #'delete-local-filesystem-node
|
|
|
|
:accessor filesystem-delete-function
|
2022-01-06 11:39:29 +01:00
|
|
|
:type function
|
|
|
|
:documentation "A function with the node as parameter.")
|
2021-12-11 11:06:06 +01:00
|
|
|
(filesystem-create-function
|
|
|
|
:initform #'create-local-filesystem-node
|
|
|
|
:accessor filesystem-create-function
|
2022-01-06 11:39:29 +01:00
|
|
|
:type function
|
|
|
|
:documentation "A function with two parameter the path to create
|
|
|
|
and a boolean thah values true if a directory must be created")
|
2021-12-12 12:53:03 +01:00
|
|
|
(filesystem-download-function
|
|
|
|
:initform #'download-local-filesystem-node
|
|
|
|
:accessor filesystem-download-function
|
2022-01-06 11:39:29 +01:00
|
|
|
:type function
|
|
|
|
:documentation "A function to download a remote file, parameters
|
2022-01-06 13:00:16 +01:00
|
|
|
are:
|
|
|
|
|
|
|
|
- node (remote file)
|
|
|
|
- destination-file (local file, note that
|
|
|
|
this should be an optional parameter with default:
|
|
|
|
(make-temporary-file-from-node node).
|
|
|
|
|
|
|
|
Must returns the path of the downloaded file.")
|
2021-12-12 14:37:38 +01:00
|
|
|
(filesystem-upload-function
|
|
|
|
:initform #'upload-local-filesystem-node
|
|
|
|
:accessor filesystem-upload-function
|
2022-01-06 11:39:29 +01:00
|
|
|
:type function
|
|
|
|
:documentation "A function to upload a local file, parameters:
|
|
|
|
- source-path (local path)
|
|
|
|
- matching-node (remote directory).")
|
2021-12-13 14:41:34 +01:00
|
|
|
(filesystem-query-path-function
|
|
|
|
:initform #'query-local-filesystem-path
|
|
|
|
:accessor filesystem-query-path-function
|
|
|
|
:type function
|
2022-01-06 11:39:29 +01:00
|
|
|
:documentation "function with two parameter the path and a feature
|
2022-01-06 13:00:16 +01:00
|
|
|
to query Valid feature values are :size. Returns nil if Returns
|
2022-01-09 14:47:22 +01:00
|
|
|
nil if the path does not point to an actual file.")
|
2022-02-05 14:28:54 +01:00
|
|
|
(filesystem-collect-tree
|
2022-02-05 14:18:24 +01:00
|
|
|
:initform #'fs:collect-tree
|
2022-02-05 14:28:54 +01:00
|
|
|
:accessor filesystem-collect-tree
|
2022-02-05 14:18:24 +01:00
|
|
|
:type function
|
|
|
|
:documentation "function with a single parameter, the
|
2022-02-05 14:28:54 +01:00
|
|
|
path. Returns a two values a list of path to all the reachable files from the
|
|
|
|
argument as root directory and a list of the paths to all directory reachable from the root
|
|
|
|
e.g
|
|
|
|
(funcall filesystem-collect-tree \"foo/\")
|
|
|
|
; => (values (foo/bar/baz
|
|
|
|
foo/a/b
|
|
|
|
...)
|
|
|
|
(foo/bar/
|
|
|
|
foo/a/
|
|
|
|
...)")
|
2022-01-09 14:47:22 +01:00
|
|
|
(filesystem-close-connection-function
|
2022-01-23 12:53:08 +01:00
|
|
|
:initform (constantly t)
|
2022-01-09 14:47:22 +01:00
|
|
|
:accessor filesystem-close-connection-function
|
|
|
|
:type function
|
2022-01-23 12:53:08 +01:00
|
|
|
:documentation "function with no parameter to close the connection."))
|
2022-01-09 14:47:22 +01:00
|
|
|
(:documentation "A window that shows and allow interacting with a
|
2022-01-06 13:00:16 +01:00
|
|
|
hierarchical filesystem"))
|
2021-12-05 15:28:29 +01:00
|
|
|
|
2022-01-09 14:47:22 +01:00
|
|
|
(defmethod initialize-instance :after ((object filesystem-tree-window)
|
|
|
|
&key (handlers-plist nil) &allow-other-keys)
|
|
|
|
(when handlers-plist
|
|
|
|
(setf (filesystem-expand-function object)
|
2022-02-05 14:18:24 +01:00
|
|
|
(getf handlers-plist :filesystem-expand-function))
|
|
|
|
(setf (filesystem-expand-function object)
|
|
|
|
(getf handlers-plist :filesystem-expand-function))
|
|
|
|
(setf (filesystem-rename-function object)
|
|
|
|
(getf handlers-plist :filesystem-rename-function))
|
|
|
|
(setf (filesystem-delete-function object)
|
|
|
|
(getf handlers-plist :filesystem-delete-function))
|
|
|
|
(setf (filesystem-create-function object)
|
|
|
|
(getf handlers-plist :filesystem-create-function))
|
|
|
|
(setf (filesystem-download-function object)
|
|
|
|
(getf handlers-plist :filesystem-download-function))
|
|
|
|
(setf (filesystem-upload-function object)
|
|
|
|
(getf handlers-plist :filesystem-upload-function))
|
|
|
|
(setf (filesystem-query-path-function object)
|
|
|
|
(getf handlers-plist :filesystem-query-path-function))
|
2022-02-05 14:28:54 +01:00
|
|
|
(setf (filesystem-collect-tree object)
|
|
|
|
(getf handlers-plist :filesystem-collect-tree))
|
2022-02-05 14:18:24 +01:00
|
|
|
(setf (filesystem-close-connection-function object)
|
2022-01-23 12:53:08 +01:00
|
|
|
(getf handlers-plist :filesystem-close-connection-function)))
|
2022-01-09 14:47:22 +01:00
|
|
|
object)
|
|
|
|
|
2021-12-10 11:50:37 +01:00
|
|
|
(defmethod refresh-config :after ((object filesystem-tree-window))
|
2021-12-05 15:28:29 +01:00
|
|
|
(with-croatoan-window (croatoan-window object)
|
2021-12-10 11:50:37 +01:00
|
|
|
(refresh-config-colors object swconf:+key-file-explorer+)
|
|
|
|
(refresh-config-sizes object swconf:+key-file-explorer+)
|
2022-01-06 12:17:31 +01:00
|
|
|
(when (not command-line:*gemini-full-screen-mode*)
|
|
|
|
(let ((height (- (win-height *main-window*)
|
|
|
|
(win-height *message-window*)))
|
|
|
|
(width (win-width *main-window*)))
|
2022-03-21 21:42:50 +01:00
|
|
|
(c:resize croatoan-window height width)))
|
2022-01-06 12:17:31 +01:00
|
|
|
(win-move object 0 0)))
|
2021-12-05 15:28:29 +01:00
|
|
|
|
2021-12-10 11:50:37 +01:00
|
|
|
(defmethod calculate :after ((object filesystem-tree-window) dt)
|
2021-12-05 15:28:29 +01:00
|
|
|
(declare (ignore object dt)))
|
|
|
|
|
|
|
|
(defmacro gen-tree-data-fetcher (name key)
|
2021-12-13 18:12:20 +01:00
|
|
|
(let ((fn-name (misc:format-fn-symbol t "tree-~a" name)))
|
|
|
|
`(progn
|
|
|
|
(defun ,fn-name (data)
|
|
|
|
(getf data ,key))
|
|
|
|
(defsetf ,fn-name (data) (val)
|
|
|
|
`(setf (getf ,data ,,key) ,val)))))
|
2021-12-05 15:28:29 +01:00
|
|
|
|
|
|
|
(gen-tree-data-fetcher path :path)
|
|
|
|
|
|
|
|
(gen-tree-data-fetcher dir-p :dirp)
|
|
|
|
|
2021-12-12 14:37:38 +01:00
|
|
|
(gen-tree-data-fetcher marked-p :markedp)
|
2021-12-05 15:28:29 +01:00
|
|
|
|
|
|
|
(defun build-data-for-print (data)
|
|
|
|
(tree-path data))
|
|
|
|
|
|
|
|
(defun treenode->text (data window)
|
|
|
|
(declare (ignore window))
|
|
|
|
(build-data-for-print data))
|
|
|
|
|
|
|
|
(defun treenode->selected-text (data window)
|
|
|
|
(tui-string-apply-colors (treenode->text data window)
|
2022-03-21 21:42:50 +01:00
|
|
|
(c:bgcolor window)
|
|
|
|
(c:fgcolor window)))
|
2021-12-05 15:28:29 +01:00
|
|
|
|
2021-12-13 14:41:34 +01:00
|
|
|
(defun query-local-filesystem-path (path what)
|
2022-01-22 15:17:33 +01:00
|
|
|
(case what
|
2021-12-13 14:41:34 +01:00
|
|
|
(:size
|
|
|
|
(and (fs:file-exists-p path)
|
2022-01-22 15:17:33 +01:00
|
|
|
(fs:file-size path)))
|
|
|
|
(otherwise
|
|
|
|
(_ "not implemented"))))
|
2021-12-13 14:41:34 +01:00
|
|
|
|
2021-12-10 11:50:37 +01:00
|
|
|
(defun expand-local-filesystem-node (matching-node)
|
|
|
|
(let ((path (tree-path (data matching-node))))
|
|
|
|
(assert path)
|
|
|
|
(let* ((children (mapcar (lambda (a)
|
|
|
|
(if (not (or (fs:backreference-dir-p a)
|
|
|
|
(fs:loopback-reference-dir-p a)))
|
2022-01-28 12:24:24 +01:00
|
|
|
(fs:normalize-path a)
|
2021-12-10 11:50:37 +01:00
|
|
|
a))
|
|
|
|
(fs:collect-children path)))
|
|
|
|
(files (remove-if #'fs:dirp children))
|
|
|
|
(directories (remove-if-not #'fs:dirp children)))
|
|
|
|
(when (or files directories)
|
|
|
|
(remove-all-children matching-node)
|
|
|
|
(loop for directory in directories do
|
|
|
|
(add-child matching-node
|
|
|
|
(make-instance 'm-tree :data (make-node-data directory t))))
|
|
|
|
(loop for file in files do
|
|
|
|
(add-child matching-node
|
|
|
|
(make-instance 'm-tree :data (make-node-data file nil)))))
|
|
|
|
matching-node)))
|
2021-12-05 15:28:29 +01:00
|
|
|
|
2021-12-11 11:06:06 +01:00
|
|
|
(defun create-local-filesystem-node (path dirp)
|
|
|
|
(assert path)
|
|
|
|
(if dirp
|
|
|
|
(fs:make-directory path)
|
|
|
|
(fs:create-a-file path)))
|
|
|
|
|
2021-12-10 11:50:37 +01:00
|
|
|
(defun rename-local-filesystem-node (matching-node new-path)
|
|
|
|
(let ((path (tree-path (data matching-node))))
|
|
|
|
(assert path)
|
|
|
|
(fs:rename-a-file path new-path)))
|
2021-12-05 15:28:29 +01:00
|
|
|
|
2021-12-10 15:30:26 +01:00
|
|
|
(defun delete-local-filesystem-node (matching-node)
|
|
|
|
(let ((path (tree-path (data matching-node))))
|
|
|
|
(assert path)
|
|
|
|
(fs:recursive-delete path)))
|
|
|
|
|
2022-01-06 13:00:16 +01:00
|
|
|
(defun make-temporary-file-from-path (path)
|
|
|
|
(let ((extension (fs:get-extension path)))
|
|
|
|
(fs:temporary-file :extension extension)))
|
|
|
|
|
|
|
|
(defun make-temporary-file-from-node (node)
|
|
|
|
(let ((path (tree-path (data node))))
|
|
|
|
(make-temporary-file-from-path path)))
|
|
|
|
|
2022-01-14 17:09:23 +01:00
|
|
|
(define-constant +download-buffer+ (expt 2 24) :test #'=)
|
|
|
|
|
2021-12-12 12:53:03 +01:00
|
|
|
(defun download-local-filesystem-node (matching-node
|
2022-01-06 13:00:16 +01:00
|
|
|
&optional
|
|
|
|
(destination-file
|
|
|
|
(make-temporary-file-from-node matching-node)))
|
2021-12-12 12:53:03 +01:00
|
|
|
(with-open-file (input-stream (tree-path (data matching-node))
|
|
|
|
:direction :input
|
|
|
|
:element-type +octect-type+)
|
|
|
|
(with-open-file (output-stream destination-file
|
|
|
|
:direction :output
|
|
|
|
:if-exists :supersede
|
|
|
|
:if-does-not-exist :create
|
|
|
|
:element-type +octect-type+)
|
|
|
|
(let* ((buffer (misc:make-array-frame +download-buffer+ 0 '(unsigned-byte 8) t)))
|
|
|
|
(loop named write-loop
|
|
|
|
for read-so-far = (read-sequence buffer input-stream)
|
|
|
|
then (read-sequence buffer input-stream)
|
|
|
|
do
|
|
|
|
(write-sequence buffer output-stream :start 0 :end read-so-far)
|
|
|
|
(when (< read-so-far +download-buffer+)
|
|
|
|
(return-from write-loop t))))))
|
|
|
|
destination-file)
|
|
|
|
|
2022-01-09 14:47:22 +01:00
|
|
|
(defun upload-local-filesystem-node (source-path destination-path)
|
2021-12-12 14:37:38 +01:00
|
|
|
(with-open-file (input-stream source-path
|
|
|
|
:direction :input
|
|
|
|
:element-type +octect-type+)
|
2022-01-09 14:47:22 +01:00
|
|
|
(with-open-file (output-stream destination-path
|
2021-12-12 14:37:38 +01:00
|
|
|
:direction :output
|
2022-01-08 13:18:45 +01:00
|
|
|
:if-exists :supersede
|
2021-12-12 14:37:38 +01:00
|
|
|
:if-does-not-exist :create
|
|
|
|
:element-type +octect-type+)
|
|
|
|
(let* ((buffer (misc:make-array-frame +download-buffer+ 0 '(unsigned-byte 8) t)))
|
|
|
|
(loop named write-loop
|
|
|
|
for read-so-far = (read-sequence buffer input-stream)
|
|
|
|
then (read-sequence buffer input-stream)
|
|
|
|
do
|
|
|
|
(write-sequence buffer output-stream :start 0 :end read-so-far)
|
|
|
|
(when (< read-so-far +download-buffer+)
|
2022-01-09 14:47:22 +01:00
|
|
|
(return-from write-loop t)))))))
|
2021-12-12 14:37:38 +01:00
|
|
|
|
2021-12-10 11:50:37 +01:00
|
|
|
(defun %expand-treenode (root path-to-expand expand-fn)
|
2021-12-14 13:05:40 +01:00
|
|
|
(when-let ((matching-node (find-node root path-to-expand)))
|
2021-12-10 11:50:37 +01:00
|
|
|
(funcall expand-fn matching-node)))
|
2021-12-05 15:28:29 +01:00
|
|
|
|
2022-01-28 15:08:49 +01:00
|
|
|
(defun expand-treenode-collect-paths (root path-to-expand expand-fn)
|
|
|
|
(when-let ((root-node (%expand-treenode root path-to-expand expand-fn)))
|
|
|
|
(mapcar #'tree-path (mtree:collect-nodes-data root-node))))
|
|
|
|
|
2021-12-10 11:50:37 +01:00
|
|
|
(defun %build-annotated-tree-rows (window root-node)
|
2021-12-05 15:28:29 +01:00
|
|
|
(with-accessors ((render-arrow-value render-arrow-value)
|
|
|
|
(render-leaf-value render-leaf-value)
|
|
|
|
(render-branch-value render-branch-value)
|
|
|
|
(render-spacer-value render-spacer-value)
|
|
|
|
(render-vertical-line-value render-vertical-line-value)
|
|
|
|
(filesystem-root filesystem-root)) window
|
2021-12-10 11:50:37 +01:00
|
|
|
(when-let* ((tree-lines (tree->annotated-lines root-node
|
2021-12-05 15:28:29 +01:00
|
|
|
:print-data-fn #'build-data-for-print
|
|
|
|
:arrow-char render-arrow-value
|
|
|
|
:spacer-child render-spacer-value
|
|
|
|
:child-char render-branch-value
|
|
|
|
:line-char render-vertical-line-value
|
|
|
|
:last-child-char render-leaf-value
|
2021-12-10 11:50:37 +01:00
|
|
|
:print-data t))
|
|
|
|
(tree-data (collect-nodes-data root-node)))
|
2021-12-05 15:28:29 +01:00
|
|
|
(with-accessors ((tree-color-map tree-color-map)) window
|
|
|
|
(let ((colorized-rows (loop for line in tree-lines
|
|
|
|
collect
|
2021-12-10 11:50:37 +01:00
|
|
|
(reduce #'cat-tui-string
|
|
|
|
(colorize-tree-line line tree-color-map)))))
|
|
|
|
(mapcar (lambda (colored-text node-data)
|
2021-12-05 15:28:29 +01:00
|
|
|
(make-instance 'line
|
2021-12-10 11:50:37 +01:00
|
|
|
:normal-text colored-text
|
|
|
|
:selected-text
|
|
|
|
(tui-string-apply-colors colored-text
|
|
|
|
(win-bgcolor window)
|
|
|
|
(win-fgcolor window))
|
2021-12-05 15:28:29 +01:00
|
|
|
:fields node-data
|
2021-12-10 11:50:37 +01:00
|
|
|
:normal-bg (win-bgcolor window)
|
|
|
|
:normal-fg (win-fgcolor window)
|
|
|
|
:selected-bg (win-fgcolor window)
|
|
|
|
:selected-fg (win-bgcolor window)))
|
|
|
|
colorized-rows
|
|
|
|
(reverse tree-data)))))))
|
|
|
|
|
|
|
|
(defun build-annotated-tree-rows (window)
|
|
|
|
(%build-annotated-tree-rows window (filesystem-root window)))
|
|
|
|
|
|
|
|
(defmethod resync-rows-db ((object filesystem-tree-window)
|
|
|
|
&key
|
|
|
|
(redraw t) (selected-path nil))
|
|
|
|
(with-accessors ((rows rows)
|
|
|
|
(selected-line-bg selected-line-bg)
|
|
|
|
(selected-line-fg selected-line-fg)) object
|
|
|
|
(line-oriented-window:update-all-rows object
|
|
|
|
(build-annotated-tree-rows object))
|
|
|
|
(when (string-not-empty-p selected-path)
|
|
|
|
(when-let ((index (rows-position-if object
|
|
|
|
(lambda (a)
|
|
|
|
(string= (tree-path (fields a))
|
|
|
|
selected-path)))))
|
|
|
|
(select-row object index)))
|
|
|
|
(when redraw
|
|
|
|
(draw object))
|
|
|
|
object))
|
|
|
|
|
|
|
|
(defun find-node (root-node matching-path)
|
|
|
|
(first (mtree:find-child-if root-node
|
|
|
|
(lambda (a)
|
|
|
|
(string= (tree-path (data a))
|
|
|
|
matching-path)))))
|
|
|
|
|
|
|
|
(defun close-treenode (window node-path)
|
|
|
|
(when-let ((matching-node (find-node (filesystem-root window) node-path)))
|
|
|
|
(remove-all-children matching-node)
|
|
|
|
(win-clear window :redraw nil)
|
|
|
|
(resync-rows-db window :redraw t :selected-path node-path)))
|
|
|
|
|
|
|
|
(defun jump-to-parent-node (window path)
|
|
|
|
(when (fs:backreference-dir-p path)
|
2022-01-28 12:24:24 +01:00
|
|
|
(let ((parent-path (fs:normalize-path path)))
|
2021-12-10 11:50:37 +01:00
|
|
|
(win-clear window :redraw nil)
|
|
|
|
(resync-rows-db window :selected-path parent-path :redraw t))))
|
|
|
|
|
|
|
|
(defun expand-treenode (window expand-root-path &key (recurse t))
|
|
|
|
(with-accessors ((filesystem-root filesystem-root)
|
|
|
|
(filesystem-expand-function filesystem-expand-function)) window
|
|
|
|
(if (or (fs:backreference-dir-p expand-root-path)
|
|
|
|
(fs:loopback-reference-dir-p expand-root-path))
|
|
|
|
(jump-to-parent-node window expand-root-path)
|
|
|
|
(when-let ((matching-node (first (mtree:find-child-if filesystem-root
|
|
|
|
(lambda (a)
|
|
|
|
(string= (tree-path (data a))
|
|
|
|
expand-root-path))))))
|
|
|
|
(when (tree-dir-p (data matching-node))
|
|
|
|
(%expand-treenode filesystem-root expand-root-path filesystem-expand-function)
|
|
|
|
(resync-rows-db window :selected-path expand-root-path :redraw nil)
|
|
|
|
(when recurse
|
|
|
|
(let* ((expanded-tree (%expand-treenode (make-root-tree expand-root-path)
|
|
|
|
expand-root-path
|
|
|
|
filesystem-expand-function))
|
|
|
|
(expanded-rows (%build-annotated-tree-rows window expanded-tree))
|
|
|
|
|
|
|
|
(window-width (usable-window-width window))
|
|
|
|
(max-line-width nil))
|
|
|
|
(loop for expanded-row in expanded-rows
|
2022-03-21 21:42:50 +01:00
|
|
|
when (> (c:text-width (normal-text expanded-row))
|
2021-12-10 11:50:37 +01:00
|
|
|
window-width)
|
|
|
|
do
|
|
|
|
(setf max-line-width expanded-row))
|
|
|
|
(when max-line-width
|
|
|
|
(let ((new-root (fs:parent-dir-path (tree-path (fields max-line-width)))))
|
|
|
|
(setf filesystem-root (make-root-tree new-root))
|
|
|
|
(expand-treenode window new-root :recurse nil))))))
|
|
|
|
(win-clear window :redraw nil)
|
|
|
|
(draw window)))))
|
|
|
|
|
|
|
|
(defun rename-treenode (window old-path new-path)
|
|
|
|
(when-let* ((root-node (filesystem-root window))
|
|
|
|
(matching-node (find-node root-node old-path))
|
|
|
|
(parent-node (find-node root-node
|
|
|
|
(fs:parent-dir-path (tree-path (data matching-node))))))
|
|
|
|
(funcall (filesystem-rename-function window)
|
|
|
|
matching-node
|
|
|
|
new-path)
|
|
|
|
(remove-all-children parent-node)
|
|
|
|
(expand-treenode window (tree-path (data parent-node)))
|
|
|
|
(win-clear window :redraw nil)
|
|
|
|
(resync-rows-db window :redraw t :selected-path new-path)))
|
|
|
|
|
2021-12-10 15:30:26 +01:00
|
|
|
(defun delete-treenode (window path)
|
|
|
|
(when-let* ((root-node (filesystem-root window))
|
|
|
|
(matching-node (find-node root-node path))
|
|
|
|
(parent-node (find-node root-node
|
|
|
|
(fs:parent-dir-path (tree-path (data matching-node))))))
|
|
|
|
(funcall (filesystem-delete-function window)
|
|
|
|
matching-node)
|
|
|
|
(remove-all-children parent-node)
|
|
|
|
(expand-treenode window (tree-path (data parent-node)))
|
|
|
|
(win-clear window :redraw nil)
|
|
|
|
(resync-rows-db window :redraw t :selected-path path)))
|
|
|
|
|
2021-12-11 11:06:06 +01:00
|
|
|
(defun create-treenode (window path dirp)
|
2022-02-16 17:59:08 +01:00
|
|
|
(when-let* ((root-node (filesystem-root window))
|
|
|
|
(parent-node (find-node root-node (fs:parent-dir-path path))))
|
2021-12-11 11:06:06 +01:00
|
|
|
(funcall (filesystem-create-function window) path dirp)
|
|
|
|
(remove-all-children parent-node)
|
|
|
|
(expand-treenode window (tree-path (data parent-node)))
|
|
|
|
(win-clear window :redraw nil)
|
|
|
|
(resync-rows-db window :redraw t :selected-path path)))
|
|
|
|
|
2022-02-16 17:55:32 +01:00
|
|
|
(defun download-path (window remote-path
|
|
|
|
&optional
|
|
|
|
(destination-file (make-temporary-file-from-path remote-path)))
|
2022-02-16 17:52:10 +01:00
|
|
|
(when-let ((type (filesystem-query-path window remote-path :type)))
|
2022-02-16 17:40:22 +01:00
|
|
|
(let ((dirp (eq type :directory)))
|
|
|
|
(fs:create-file destination-file)
|
|
|
|
(funcall (filesystem-download-function window)
|
|
|
|
(make-instance 'm-tree :data (make-node-data remote-path dirp))
|
|
|
|
destination-file))))
|
2021-12-12 12:53:03 +01:00
|
|
|
|
2022-02-16 17:59:08 +01:00
|
|
|
(defun upload-path (window source-file remote-path &key (force-upload nil))
|
|
|
|
(let ((root-node (filesystem-root window)))
|
2022-01-29 17:44:47 +01:00
|
|
|
(if force-upload
|
|
|
|
(funcall (filesystem-upload-function window)
|
|
|
|
source-file
|
|
|
|
(fs:normalize-path remote-path))
|
|
|
|
(when-let* ((parent-node (find-node root-node (fs:parent-dir-path remote-path)))
|
|
|
|
(parent-path (tree-path (data parent-node))))
|
|
|
|
(funcall (filesystem-upload-function window)
|
|
|
|
source-file
|
|
|
|
(fs:normalize-path remote-path))
|
|
|
|
(remove-all-children parent-node)
|
|
|
|
(expand-treenode window parent-path)
|
|
|
|
(win-clear window :redraw nil)
|
|
|
|
(resync-rows-db window :redraw t :selected-path remote-path)))))
|
2021-12-12 14:37:38 +01:00
|
|
|
|
2022-02-05 14:18:24 +01:00
|
|
|
(defun recursive-delete-node (window path
|
|
|
|
&key
|
|
|
|
(progress-function (lambda (filename file-count all-files-number)
|
|
|
|
(declare (ignore filename file-count all-files-number)))))
|
2021-12-14 13:05:40 +01:00
|
|
|
(with-accessors ((root-node filesystem-root)
|
2022-02-05 14:18:24 +01:00
|
|
|
(filesystem-expand-function filesystem-expand-function)
|
2022-02-05 14:28:54 +01:00
|
|
|
(list-all-file-function filesystem-collect-tree))
|
2022-02-05 14:18:24 +01:00
|
|
|
window
|
2021-12-14 13:05:40 +01:00
|
|
|
(let* ((matching-node (find-node root-node path))
|
|
|
|
(filep (not (tree-dir-p (data matching-node)))))
|
|
|
|
(if filep
|
|
|
|
(delete-treenode window path)
|
|
|
|
(when (not (or (fs:loopback-reference-dir-p path)
|
|
|
|
(fs:backreference-dir-p path)))
|
2022-02-05 14:18:24 +01:00
|
|
|
(multiple-value-bind (all-files-to-delete all-dirs-to-delete)
|
|
|
|
(funcall list-all-file-function path)
|
|
|
|
(let ((items-count 0)
|
|
|
|
(items-total-number (+ (length all-files-to-delete)
|
|
|
|
(length all-dirs-to-delete))))
|
|
|
|
(flet ((delete-items (items)
|
|
|
|
(loop for item in items
|
|
|
|
for count from 0
|
|
|
|
when (not (or (fs:backreference-dir-p item)
|
|
|
|
(fs:loopback-reference-dir-p item)))
|
|
|
|
do
|
|
|
|
(let ((node (mtree:make-node (make-node-data item nil))))
|
|
|
|
(incf items-count)
|
|
|
|
(funcall (filesystem-delete-function window)
|
|
|
|
node)
|
|
|
|
(funcall progress-function
|
|
|
|
item
|
|
|
|
items-count
|
|
|
|
items-total-number)))))
|
|
|
|
(delete-items all-files-to-delete)
|
|
|
|
(delete-items all-dirs-to-delete)
|
|
|
|
(delete-items (list path))
|
|
|
|
(when-let* ((parent-node (find-node root-node
|
|
|
|
(fs:parent-dir-path path)))
|
|
|
|
(parent-path (tree-path (data parent-node))))
|
|
|
|
(remove-all-children parent-node)
|
|
|
|
(expand-treenode window parent-path)
|
|
|
|
(win-clear window :redraw nil)
|
|
|
|
(resync-rows-db window :redraw t :selected-path parent-path))))))))))
|
2021-12-14 13:05:40 +01:00
|
|
|
|
2022-02-16 17:52:10 +01:00
|
|
|
(defun filesystem-query-path (window path what)
|
2022-01-28 15:08:49 +01:00
|
|
|
(assert (member what '(:size :size-string :permissions :permissions-string :type)))
|
2022-02-16 17:40:22 +01:00
|
|
|
(funcall (filesystem-query-path-function window)
|
|
|
|
path
|
|
|
|
what))
|
2021-12-13 14:41:34 +01:00
|
|
|
|
2021-12-12 21:40:59 +01:00
|
|
|
(defmethod search-row ((object filesystem-tree-window) regex &key (redraw t))
|
|
|
|
(handler-case
|
|
|
|
(with-accessors ((row-selected-index row-selected-index)) object
|
|
|
|
(when-let* ((scanner (create-scanner regex :case-insensitive-mode nil))
|
|
|
|
(position-header (rows-position-if object
|
|
|
|
(lambda (a)
|
|
|
|
(scan scanner (tree-path (fields a))))
|
|
|
|
:start (1+ row-selected-index))))
|
|
|
|
(resync-rows-db object :redraw nil)
|
|
|
|
(unselect-all object)
|
|
|
|
(select-row object position-header)
|
|
|
|
(when redraw
|
|
|
|
(win-clear object :redraw nil)
|
|
|
|
(draw object))))
|
|
|
|
(error ()
|
|
|
|
(ui:error-message (_ "Invalid regular expression")))))
|
|
|
|
|
2021-12-10 11:50:37 +01:00
|
|
|
(defmethod draw :after ((object filesystem-tree-window))
|
|
|
|
(when-window-shown (object)
|
|
|
|
(let* ((window-width (usable-window-width object))
|
|
|
|
(rows (renderizable-rows-data object))
|
|
|
|
(x (if (uses-border-p object)
|
|
|
|
1
|
|
|
|
0))
|
|
|
|
(y-start (if (uses-border-p object)
|
|
|
|
1
|
|
|
|
0)))
|
|
|
|
(loop
|
|
|
|
for y from y-start by 1
|
|
|
|
for ct from 0
|
|
|
|
for row in rows do
|
2021-12-13 18:12:20 +01:00
|
|
|
(cond
|
|
|
|
((selectedp row)
|
|
|
|
(let ((text (tui:copy-tui-string (selected-text row))))
|
|
|
|
(print-text object (text-ellipsis text window-width) x y)))
|
|
|
|
((tree-marked-p (fields row))
|
|
|
|
(let ((text (tui:copy-tui-string (normal-text row))))
|
|
|
|
(print-text object
|
|
|
|
(tui:apply-attributes (text-ellipsis text window-width)
|
|
|
|
:all
|
|
|
|
(tui:combine-attributes (tui:attribute-reverse)
|
|
|
|
(tui:attribute-bold)))
|
|
|
|
x y)))
|
|
|
|
(t
|
|
|
|
(let ((text (tui:copy-tui-string (normal-text row))))
|
|
|
|
(print-text object (text-ellipsis text window-width) x y))))))))
|
|
|
|
|
|
|
|
(defun mark-node (window path &key (toggle t))
|
|
|
|
(when-let* ((root-node (filesystem-root window))
|
|
|
|
(matching-node (find-node root-node path)))
|
|
|
|
(if toggle
|
|
|
|
(setf (tree-marked-p (data matching-node))
|
|
|
|
(not (tree-marked-p (data matching-node))))
|
|
|
|
(setf (tree-marked-p (data matching-node)) t))))
|
2021-12-05 15:28:29 +01:00
|
|
|
|
2022-01-06 13:00:16 +01:00
|
|
|
(defun open-node (window path)
|
|
|
|
(when-let* ((root-node (filesystem-root window))
|
|
|
|
(matching-node (find-node root-node path))
|
|
|
|
(node-data (data matching-node))
|
|
|
|
(node-path (tree-path node-data)))
|
|
|
|
(if (tree-dir-p node-data)
|
|
|
|
(expand-treenode window node-path)
|
2022-02-16 17:55:32 +01:00
|
|
|
(let ((downloaded-path (download-path window node-path)))
|
2022-01-06 13:00:16 +01:00
|
|
|
(os-utils:open-resource-with-external-program downloaded-path nil)))))
|
|
|
|
|
2022-01-08 13:18:45 +01:00
|
|
|
(defun edit-node (window path)
|
|
|
|
(when-let* ((root-node (filesystem-root window))
|
|
|
|
(matching-node (find-node root-node path))
|
|
|
|
(node-data (data matching-node))
|
|
|
|
(node-path (tree-path node-data)))
|
|
|
|
(if (tree-dir-p node-data)
|
|
|
|
(expand-treenode window node-path)
|
2022-02-16 17:55:32 +01:00
|
|
|
(let ((downloaded-path (download-path window node-path)))
|
2022-01-08 13:18:45 +01:00
|
|
|
(croatoan:end-screen)
|
|
|
|
(os-utils:open-resource-with-external-program downloaded-path nil :open-for-edit t)
|
2022-02-16 17:59:08 +01:00
|
|
|
(upload-path window
|
2022-01-08 13:18:45 +01:00
|
|
|
downloaded-path
|
|
|
|
node-path)))))
|
2022-01-23 12:53:08 +01:00
|
|
|
|
|
|
|
(defun close-connection (window)
|
|
|
|
(funcall (filesystem-close-connection-function window)))
|
|
|
|
|
2022-01-28 15:08:49 +01:00
|
|
|
(defun filter-node-children (window path-pattern)
|
|
|
|
(with-accessors ((filesystem-root filesystem-root)
|
|
|
|
(filesystem-expand-function filesystem-expand-function)) window
|
|
|
|
(when-let* ((dir (if (fs:extension-dir-p path-pattern)
|
|
|
|
path-pattern
|
|
|
|
(fs:parent-dir-path path-pattern)))
|
|
|
|
(all-children (expand-treenode-collect-paths filesystem-root
|
|
|
|
dir
|
|
|
|
filesystem-expand-function))
|
|
|
|
(all-files (remove-if (lambda (a)
|
2022-02-16 17:52:10 +01:00
|
|
|
(let* ((type (filesystem-query-path window
|
|
|
|
a
|
|
|
|
:type)))
|
2022-01-28 15:56:12 +01:00
|
|
|
(or (null type)
|
|
|
|
(eq type :directory))))
|
2022-01-28 15:08:49 +01:00
|
|
|
all-children)))
|
|
|
|
(remove-if-not (lambda (a) (fs:filename-pattern-match path-pattern a))
|
|
|
|
all-files))))
|
|
|
|
|
2022-01-09 14:47:22 +01:00
|
|
|
(defun init (root &optional (handlers-plist nil))
|
2021-12-05 15:28:29 +01:00
|
|
|
"Initialize the window"
|
|
|
|
(let* ((low-level-window (make-croatoan-window :border t))
|
2021-12-10 11:50:37 +01:00
|
|
|
(high-level-window (make-instance 'filesystem-tree-window
|
|
|
|
:uses-border-p t
|
|
|
|
:title (_ "File explorer")
|
|
|
|
:key-config swconf:+key-keybindings-window+
|
|
|
|
:keybindings *filesystem-explorer-keymap*
|
|
|
|
:croatoan-window low-level-window
|
2022-01-09 14:47:22 +01:00
|
|
|
:filesystem-root (make-root-tree root)
|
|
|
|
:handlers-plist handlers-plist)))
|
2021-12-05 15:28:29 +01:00
|
|
|
(refresh-config high-level-window)
|
2021-12-10 11:50:37 +01:00
|
|
|
(setf *filesystem-explorer-window* high-level-window)
|
|
|
|
(resync-rows-db high-level-window :redraw t :selected-path root)
|
2021-12-05 15:28:29 +01:00
|
|
|
high-level-window))
|