-
Notifications
You must be signed in to change notification settings - Fork 2
Extending Example
Let’s say we want to add some kind of a window tagging
feature. We’ll use an org-mode like syntax for tags:
:foo:bar:quux:
. The tags can be used to mark windows with a tag
query. The query syntax is also org-mode:ish:
foo
- Match windows with a foo tag.
+foo -bar
- Match windows with a
foo
tag, but nobar
tag. foo-bar+quux
- Match
foo
andquux
and nobar
.
First, we need to store the tags somehow in Stumpwm. Let’s just use a simple weak hash table (with SBCL).
(defvar *window-tags* (make-hash-table :weakness :key)) (defun window-tags (window) (gethash window *window-tags* ":")) ;Use : for empty taglist (defun (setf window-tags) (new-value window) (setf (gethash window *window-tags*) new-value))
Then we must add the tags to the custom data fields for windows.
(pushnew (cons :tags 'window-tags) stumpbuffer:*window-data-fields* :test #'equal)
And make a command to set new tags. This has a slight problem of not
accepting an empty string through Stumpish. We work around that in
Emacs by adding a :
instead of empty tag list.
(defcommand stumpbuffer-set-window-tags (window-id new-tags) ((:number "Window- ID") (:string "Tags: ")) (stumpbuffer:with-simple-error-handling (let ((window (stumpbuffer:find-window-by-id window-id))) (setf (window-tags window) (or new-tags "")))))
That’s all we need on the Stumpwm side, because we’re not really interested in doing anything with the tags in Stumpwm itself. For Emacs we have to write a bit more code to manage the tags with.
First, make the tag field visible.
(setq stumpbuffer-window-format '((:number 3 "N") (:title 35 "Title") (:class 10 "Class") (:role 10 "Role") (:instance 10 "Instance") (:tags nil "Tags")))
Then add a simple command to edit tags. We’ll also bind it to t
for window rows only using the stumpbuffer-mode-window-map
.
(defun my-stumpbuffer-set-window-tags (window-id new-tags &optional updatep) (interactive (let ((wplist (cl-getf (stumpbuffer-on-window) :window-plist))) (list (cl-getf wplist :id) (read-string "Tags: " (cl-getf wplist :tags)) t))) (when (and window-id new-tags) (stumpbuffer-command "set-window-tags" window-id new-tags) (when updatep (stumpbuffer-update)))) (define-key stumpbuffer-mode-window-map (kbd "t") 'my-stumpbuffer-set-window-tags)
For queries we’ll have to write some code to parse the tags and the query strings and match them.
(defun my-stumpbuffer-parse-query (query) (cl-loop with start-pos = 0 for match-pos = (string-match "\\(\\(?: \\|^\\|\\+\\|-\\)[^ +-]+\\)" query start-pos) while match-pos collect (let ((match (string-trim (match-string 1 query)))) (cl-case (aref match 0) (?+ (cons :positive (subseq match 1))) (?- (cons :negative (subseq match 1))) (otherwise (cons :positive match)))) do (setq start-pos (1+ match-pos)))) (defun my-stumpbuffer-parse-tags (tags) (cl-loop with start-pos = 0 for match-pos = (string-match ":\\([^:]+\\)" tags start-pos) while match-pos collect (match-string 1 tags) do (setq start-pos (1+ match-pos)))) (defun my-stumpbuffer-match-tags (tags parsed-query) (let ((parsed-tags (my-stumpbuffer-parse-tags tags))) (cl-every (lambda (query-part) (cl-destructuring-bind (type . tag) query-part (cl-case type (:positive (member tag parsed-tags)) (:negative (not (member tag parsed-tags)))))) parsed-query)))
With these it’s easy to write a command to mark windows by a tag
query. We’ll bind it to % t
in the whole buffer.
(defun my-stumpbuffer-mark-windows-by-tag-query (query mark) (interactive (list (read-string "Query: ") (if current-prefix-arg (read-char "Mark: ") ?*))) (let ((parsed-query (my-stumpbuffer-parse-query query))) (stumpbuffer-do-windows (win) (let ((tags (cl-getf (cl-getf win :window-plist) :tags))) (when (my-stumpbuffer-match-tags tags parsed-query) (stumpbuffer-mark mark)))))) (define-key stumpbuffer-mode-map (kbd "% t") 'my-stumpbuffer-mark-windows-by-tag-query)
Let’s also write commands to add or remove a single tag from marked
windows (or the highlighted one). Those will be bound to +
and -
respectively.
(defun my-stumpbuffer-concat-tags (tags) (with-output-to-string (write-char ?:) (cl-loop for tag in (cl-remove-duplicates tags :test #'string-equal) do (princ tag) (write-char ?:)))) (defun my-stumpbuffer-add-tag (tag) (interactive (list (string-trim (read-string "Tag: ")))) (cl-flet ((try-add-tag (win) (let* ((wplist (cl-getf win :window-plist)) (tags (my-stumpbuffer-parse-tags (cl-getf wplist :tags)))) (unless (member tag tags) (my-stumpbuffer-set-window-tags (cl-getf wplist :id) (my-stumpbuffer-concat-tags (cons tag tags)) nil))))) (let (marksp) (stumpbuffer-do-marked-windows (win) (let ((mark (cl-getf win :mark))) (when (char-equal mark ?*) (setq marksp t) (try-add-tag win)))) (unless marksp (when-let ((win (stumpbuffer-on-window))) (try-add-tag win))) (stumpbuffer-update)))) (defun my-stumpbuffer-remove-tag (tag) (interactive (list (string-trim (read-string "Tag: ")))) (cl-flet ((try-remove-tag (win) (let* ((wplist (cl-getf win :window-plist)) (tags (my-stumpbuffer-parse-tags (cl-getf wplist :tags)))) (when (member tag tags) (my-stumpbuffer-set-window-tags (cl-getf wplist :id) (my-stumpbuffer-concat-tags (remove tag tags)) nil))))) (let (marksp) (stumpbuffer-do-marked-windows (win) (let ((mark (cl-getf win :mark))) (when (char-equal mark ?*) (setq marksp t) (try-remove-tag win)))) (unless marksp (when-let ((win (stumpbuffer-on-window))) (try-remove-tag win))) (stumpbuffer-update)))) (define-key stumpbuffer-mode-map (kbd "+") 'my-stumpbuffer-add-tag) (define-key stumpbuffer-mode-map (kbd "-") 'my-stumpbuffer-remove-tag)
Finally we should implement quick filtering based on tag
queries. This adds filter syntax for (:with-tags parsed-query)
and
binds / t
to push such quick filter.
(defun my-stumpbuffer-tag-filter-handler (how plist) (pcase how (`(:with-tags ,query) (when-let ((tags (cl-getf plist :tags))) (my-stumpbuffer-match-tags tags query))))) (add-to-list 'stumpbuffer-filter-handlers 'my-stumpbuffer-tag-filter-handler) (defun my-stumpbuffer-push-tag-filter (query) (interactive (list (read-string "Query: "))) (let ((query (my-stumpbuffer-parse-query query))) (stumpbuffer-push-quick-filter `(:show-windows :with-tags ,query)) (stumpbuffer-update))) (define-key stumpbuffer-mode-map (kbd "/ t") 'my-stumpbuffer-push-tag-filter)