branch: externals/denote-sequence
commit a2682a48b31429ba4b121acdfe102d1003ee75ce
Author: Protesilaos Stavrou <[email protected]>
Commit: Protesilaos Stavrou <[email protected]>
WORK-IN-PROGRESS Remove hierarchy.el experiment and define my own
implementation (overrides commit b00e96c)
This is the core idea. There are more to be done with it.
---
denote-sequence.el | 184 +++++++++++++++++++++++++++++++++++++++++------------
1 file changed, 144 insertions(+), 40 deletions(-)
diff --git a/denote-sequence.el b/denote-sequence.el
index 5baf69be42..bc3f796607 100644
--- a/denote-sequence.el
+++ b/denote-sequence.el
@@ -1238,6 +1238,139 @@ CHECK THE RESULTING SEQUENCES FOR DUPLICATES."
;;;; Display a hierarchy
+(defgroup denote-sequence-hierarchy ()
+ "Hierarchy view of Denote sequences."
+ :group 'denote
+ :group 'denote-sequence
+ :link '(info-link "(denote) top")
+ :link '(info-link "(denote-sequence) top")
+ :link '(url-link :tag "Denote homepage"
"https://protesilaos.com/emacs/denote")
+ :link '(url-link :tag "Denote Sequence homepage"
"https://protesilaos.com/emacs/denote-sequence"))
+
+(defcustom denote-sequence-hierarchy-indentation 2
+ "Number of spaces to indent each level of depth in
`denote-sequence-view-hierarchy'."
+ :type 'natnum
+ :package-version '(denote . "0.3.0")
+ :group 'denote-sequence-hierarchy)
+
+(defun denote-sequence--hierarchy-insert (file)
+ "Insert FILE in the hierarchy with indentation matching the sequence depth."
+ (condition-case data
+ (let* ((title (denote-retrieve-title-or-filename file
(denote-filetype-heuristics file)))
+ (keywords (denote-retrieve-filename-keywords file))
+ (sequence (denote-retrieve-filename-signature file))
+ (depth (denote-sequence-depth sequence))
+ (indent (if (eq depth 1)
+ ""
+ (make-string (* (- depth 1)
denote-sequence-hierarchy-indentation) ? )))
+ (beginning (point))
+ (inhibit-read-only t))
+ (insert
+ (propertize
+ ;; FIXME 2025-11-19: Adjust this to account only for
+ ;; elements that are present. Only the sequence is
+ ;; mandatory in this regard.
+ (format "%s%s %s _%s"
+ (propertize indent
+ 'cursor-sensor-functions
+ (list
+ (lambda (&rest _)
+ (re-search-forward "[[:alnum:]]" nil t)
+ (forward-char -1))))
+ (propertize sequence
'denote-sequence-hierarchy-sequence-text t)
+ (propertize title 'denote-sequence-hierarchy-title-text t)
+ (propertize keywords
'denote-sequence-hierarchy-keywords-text t))
+ 'denote-sequence-hierarchy-level depth
+ 'denote-sequence-hierarchy-file file))
+ (insert "\n"))
+ (error (message "Failed label-button-fn with data: %s" data))))
+
+(defun denote-sequence-hierarchy-get-level ()
+ "Return the outline level at point."
+ (if-let* ((level (get-text-property (point)
'denote-sequence-hierarchy-level)))
+ level
+ (user-error "No outline level found at position `%s'" position)))
+
+(defun denote-sequence-hierarchy-find-file (position)
+ "Find the file at POSITION in `denote-sequence-view-hierarchy' buffer.
+When called interactively POSITION is the current `point'."
+ (interactive (list (point)))
+ (if-let* ((file (get-text-property position
'denote-sequence-hierarchy-file)))
+ (funcall denote-open-link-function file)
+ (user-error "No file found at position `%s'" position)))
+
+(defun denote-sequence--hierarchy-get-buffer (prefix depth)
+ "Return buffer for `denote-sequence-view-hierarchy'.
+PREFIX and DEPTH are used to derive the name of the buffer as well as to
+set the `revert-buffer-function'."
+ (let* ((name (format-message "*denote-sequence-hierarchy with prefix `%s';
depth `%s'*" (or prefix "ALL") (or depth "ALL")))
+ (buffer (get-buffer-create name))
+ (inhibit-read-only t))
+ (with-current-buffer buffer
+ (erase-buffer)
+ (setq-local revert-buffer-function
+ (lambda (_ignore-auto _no-confirm)
+ (denote-sequence-view-hierarchy prefix depth))))
+ buffer))
+
+;; TODO 2025-11-19: Review which keybindings we need to cover the
+;; basic use-case. I do not want to have a million options here.
+(defvar denote-sequence-hierarchy-mode-map
+ (let ((map (make-sparse-keymap)))
+ (define-key map (kbd "RET") #'denote-sequence-hierarchy-find-file)
+ (define-key map (kbd "TAB") #'outline-cycle)
+ (define-key map (kbd "S-TAB") #'outline-cycle-buffer)
+ (define-key map (kbd "<backtab>") #'outline-cycle-buffer)
+ (define-key map (kbd "g") #'revert-buffer)
+ (define-key map (kbd "f") #'outline-forward-same-level)
+ (define-key map (kbd "b") #'outline-backward-same-level)
+ (define-key map (kbd "n") #'outline-next-visible-heading)
+ (define-key map (kbd "p") #'outline-previous-visible-heading)
+ map)
+ "Key map for `denote-sequence-hierarchy-mode'.")
+
+(defun denote-sequence--hierarchy-face-matcher-subr (property)
+ "Search forward for PROPERTY and return match data."
+ (when-let* ((properties (text-property-search-forward property))
+ (beginning (prop-match-beginning properties))
+ (end (prop-match-end properties)) )
+ (set-match-data (list beginning end))
+ (point)))
+
+(defun denote-sequence--hierarchy-face-matcher-sequence (limit)
+ "Font lock matcher for sequences using LIMIT."
+ (denote-sequence--hierarchy-face-matcher-subr
'denote-sequence-hierarchy-sequence-text))
+
+(defun denote-sequence--hierarchy-face-matcher-title (limit)
+ "Font lock matcher for titles using LIMIT."
+ (denote-sequence--hierarchy-face-matcher-subr
'denote-sequence-hierarchy-title-text))
+
+(defun denote-sequence--hierarchy-face-matcher-keywords (limit)
+ "Font lock matcher for keywords using LIMIT."
+ (denote-sequence--hierarchy-face-matcher-subr
'denote-sequence-hierarchy-keywords-text))
+
+(defvar denote-sequence-hierarchy-font-lock-keywords
+ '((denote-sequence--hierarchy-face-matcher-sequence
+ (0 'denote-faces-signature))
+ (denote-sequence--hierarchy-face-matcher-title
+ (0 'denote-faces-title))
+ (denote-sequence--hierarchy-face-matcher-keywords
+ (0 'denote-faces-keywords)))
+ "Font lock keywords for `denote-sequence-hierarchy-mode'.")
+
+(define-derived-mode denote-sequence-hierarchy-mode text-mode "Denote
Hierarchy"
+ "Major mode for `denote-sequence-view-hierarchy' buffers."
+ :interactive nil
+ (setq-local font-lock-defaults
'(denote-sequence-hierarchy-font-lock-keywords))
+ (setq-local outline-regexp "[\s[:alnum:]]+")
+ (setq-local outline-level #'denote-sequence-hierarchy-get-level)
+ (setq-local outline-minor-mode-highlight 'append)
+ (setq-local outline-minor-mode-cycle t)
+ (setq-local outline-minor-mode-use-buttons nil)
+ (setq-local buffer-read-only t)
+ (cursor-sensor-mode 1)
+ (outline-minor-mode 1))
+
;;;###autoload
(defun denote-sequence-view-hierarchy (&optional prefix depth)
"Show a hierachy of sequences.
@@ -1264,49 +1397,20 @@ string, which means to not use a prefix as a
restriction."
(denote-sequence-prompt "Limit to files that extend SEQUENCE (empty
for all)")))
(t
nil))))
- (require 'hierarchy)
(if-let* ((files-with-prefix (if (and prefix (not (string-blank-p prefix)))
(denote-sequence-get-all-files-with-prefix
prefix)
(denote-sequence-get-all-files)))
- (files-with-depth (if depth
-
(denote-sequence-get-all-files-with-max-depth depth files-with-prefix)
- files-with-prefix))
- ;; NOTE 2025-11-19: We need this to base all our files
- ;; relative to it. I was trying to work without it, but
- ;; nothing yielded the desired results.
- (phony-root (denote-format-file-name (car (denote-directories))
"00000000T000000" '("keyword") "title" ".txt" "0"))
- (files (append (list phony-root) files-with-depth)))
- (let* ((buffer (get-buffer-create "*denote-sequence-hierarchy*"))
- (hierarchy (hierarchy-new))
- (all-roots (seq-remove
- (lambda (file)
- (denote-sequence--infer-parent
(denote-retrieve-filename-signature file)))
- (remove phony-root files)))
- (children-fn (lambda (file)
- (condition-case data
- (if (string= file phony-root)
- all-roots
- (denote-sequence-get-relative
(denote-retrieve-filename-signature file) 'children files))
- (error (message "Failed childern-fn with data:
%s" data)))))
- (label-button-fn (lambda (file indent)
- (condition-case data
- (if (string= file phony-root)
- (insert "")
- (let* ((signature
(denote-retrieve-filename-signature file))
- (title
(denote-retrieve-title-or-filename file (denote-filetype-heuristics file)))
- (keywords
(denote-retrieve-filename-keywords-as-list file))
- (children
(denote-sequence-get-relative signature 'children files)))
- (if children
- (insert (format "%s: %s (%s) [%s]"
signature title (string-join keywords ", ") (length children)))
- (insert (format "%s: %s (%s)"
signature title (string-join keywords ", "))))))
- (error (message "Failed label-button-fn with
data: %s" data)))))
- (label-action-fn (lambda (file &rest _)
- (unless (string= file phony-root)
- (funcall denote-open-link-function file))))
- (label-fn (hierarchy-labelfn-button label-button-fn
label-action-fn)))
- (hierarchy-add-trees hierarchy files nil children-fn nil
:delay-children-processing)
- (hierarchy-sort hierarchy #'denote-sequence--file-smaller-p)
- (display-buffer (hierarchy-tree-display hierarchy label-fn buffer)))
+ (files (if depth
+ (denote-sequence-get-all-files-with-max-depth depth
files-with-prefix)
+ files-with-prefix)))
+ (let* ((buffer (denote-sequence--hierarchy-get-buffer prefix depth))
+ (sorted (denote-sequence-sort-files files)))
+ (with-current-buffer buffer
+ (dolist (file sorted)
+ (denote-sequence--hierarchy-insert file))
+ (goto-char (point-min))
+ (denote-sequence-hierarchy-mode))
+ (display-buffer buffer))
(user-error "No sequences found")))
(provide 'denote-sequence)