fix: dir-locals bookmarks mu4e project-agenda (user+gemini)
This commit is contained in:
@@ -2287,60 +2287,68 @@ Cordialement,
|
||||
(defconst gortium/project-mu4e-keys '(?I ?S ?D ?T ?A)
|
||||
"Character keys used for project mu4e maildir shortcuts (uppercase avoids conflicts).")
|
||||
|
||||
(defvar gortium/project-mu4e-bookmarks nil
|
||||
"Cached project mu4e maildir shortcuts for re-injection when mu4e loads.")
|
||||
|
||||
(defun gortium/inject-project-mu4e-bookmarks ()
|
||||
"Inject project mu4e maildirs into mu4e-maildir-shortcuts.
|
||||
Keys [PI]/[PS]/[PD]/[PT]/[PA] (uppercase). Filters by context-tag:
|
||||
Keys [I]/[S]/[D]/[T]/[A] (uppercase). Filters by context-tag:
|
||||
only injects when the project's context-tag matches current mu4e context."
|
||||
(let* ((proj-root (and (projectile-project-p) (projectile-project-root)))
|
||||
(proj-tag (when proj-root
|
||||
(let ((default-directory proj-root))
|
||||
(gortium/read-dir-local 'gortium/project-tag))))
|
||||
(context-tag (when proj-root
|
||||
(let ((default-directory proj-root))
|
||||
(gortium/read-dir-local 'gortium/context-tag))))
|
||||
(cur-context (and (boundp 'mu4e-context-current)
|
||||
(car-safe mu4e-context-current)))
|
||||
(project-bm '()))
|
||||
;; Only inject if context-tag matches current mu4e context
|
||||
;; (if no context-tag set, always inject)
|
||||
(when (and proj-tag
|
||||
(or (null context-tag)
|
||||
(and cur-context
|
||||
(string= context-tag
|
||||
(mu4e-context-name cur-context)))))
|
||||
(dolist (folder '(("Inbox" . ?I)
|
||||
("Sent" . ?S)
|
||||
("Drafts" . ?D)
|
||||
("Trash" . ?T)
|
||||
("Archive" . ?A)))
|
||||
(push (cons (format "maildir:/%s.%s" (car folder) proj-tag)
|
||||
(cdr folder))
|
||||
project-bm)))
|
||||
;; Inject into mu4e-maildir-shortcuts
|
||||
(setq gortium/project-mu4e-bookmarks project-bm)
|
||||
(when (and (boundp 'mu4e-maildir-shortcuts) project-bm)
|
||||
;; Remove stale project entries
|
||||
(setq mu4e-maildir-shortcuts
|
||||
(seq-filter (lambda (s)
|
||||
(not (and (consp s) (stringp (car s))
|
||||
(string-match "^\\[[A-Z]+\\]" (car s)))))
|
||||
mu4e-maildir-shortcuts))
|
||||
;; Add current project entries
|
||||
(setq mu4e-maildir-shortcuts
|
||||
(append mu4e-maildir-shortcuts project-bm))
|
||||
;; Refresh
|
||||
(setq mu4e--bookmark-items-cached nil)
|
||||
(when (get-buffer "*mu4e-main*")
|
||||
(with-current-buffer "*mu4e-main*"
|
||||
(mu4e--main-redraw))))))
|
||||
(interactive)
|
||||
(when (boundp 'mu4e-maildir-shortcuts)
|
||||
(let* ((proj-root (and (projectile-project-p) (projectile-project-root)))
|
||||
(proj-tag (when proj-root
|
||||
(let ((default-directory proj-root))
|
||||
(gortium/read-dir-local 'gortium/project-tag))))
|
||||
(context-tag (when proj-root
|
||||
(let ((default-directory proj-root))
|
||||
(gortium/read-dir-local 'gortium/context-tag))))
|
||||
;; Extract the context name safely
|
||||
(cur-context-name (and (boundp 'mu4e-context-current)
|
||||
mu4e-context-current
|
||||
(mu4e-context-name mu4e-context-current)))
|
||||
(project-shortcuts '()))
|
||||
|
||||
(add-hook 'projectile-after-switch-project-hook
|
||||
#'gortium/inject-project-mu4e-bookmarks)
|
||||
;; 1. Always strip out previous project shortcuts safely
|
||||
(setq mu4e-maildir-shortcuts
|
||||
(seq-filter (lambda (shortcut)
|
||||
(let ((key (if (listp (cdr shortcut))
|
||||
(plist-get shortcut :key)
|
||||
(cdr shortcut))))
|
||||
(not (memq key gortium/project-mu4e-keys))))
|
||||
mu4e-maildir-shortcuts))
|
||||
|
||||
;; 2. Determine whether to inject and log the reason to *Messages*
|
||||
(cond
|
||||
((not proj-tag)
|
||||
;; No tag found, do nothing silently (prevents spam on non-project files)
|
||||
nil)
|
||||
|
||||
((and context-tag cur-context-name (not (string= context-tag cur-context-name)))
|
||||
(message "[mu4e Project] Skipped: .dir-locals context '%s' does not match active mu4e context '%s'."
|
||||
context-tag cur-context-name))
|
||||
|
||||
(t
|
||||
;; 3. Build and inject the shortcuts
|
||||
(dolist (folder '(("Inbox" . ?I)
|
||||
("Sent" . ?S)
|
||||
("Drafts" . ?D)
|
||||
("Trash" . ?T)
|
||||
("Archive" . ?A)))
|
||||
(push `(:maildir ,(format "/%s.%s" (car folder) proj-tag)
|
||||
:key ,(cdr folder)
|
||||
:name ,(format "[%s] %s" proj-tag (car folder)))
|
||||
project-shortcuts))
|
||||
|
||||
(setq mu4e-maildir-shortcuts
|
||||
(append mu4e-maildir-shortcuts (nreverse project-shortcuts)))
|
||||
(message "[mu4e Project] Successfully injected maildirs for project tag: %s" proj-tag))))))
|
||||
|
||||
;; Apply the hooks
|
||||
(add-hook 'projectile-after-switch-project-hook #'gortium/inject-project-mu4e-bookmarks)
|
||||
(add-hook 'find-file-hook #'gortium/inject-project-mu4e-bookmarks)
|
||||
(add-hook 'mu4e-main-mode-hook #'gortium/inject-project-mu4e-bookmarks)
|
||||
|
||||
;; CRITICAL: Re-inject AFTER mu4e applies its context variables
|
||||
(add-hook 'mu4e-context-changed-hook #'gortium/inject-project-mu4e-bookmarks)
|
||||
|
||||
(after! mu4e
|
||||
(gortium/inject-project-mu4e-bookmarks))
|
||||
#+end_src
|
||||
@@ -3590,6 +3598,7 @@ Saves only project bookmarks without contaminating global list."
|
||||
;; Restore globals
|
||||
(setq bookmark-alist saved-alist)
|
||||
(message "Bookmark added to .bookmarks"))))
|
||||
|
||||
(defun gortium/remove-bookmark-from-plan (name)
|
||||
"Remove a bookmark entry from * Bookmarks in the Plan file."
|
||||
(when-let* ((plan-file (gortium/project-plan-file)))
|
||||
@@ -3637,6 +3646,34 @@ Project bookmarks go to .bookmarks file in project root."
|
||||
(message "Project bookmark \"%s\" saved to .bookmarks" name))
|
||||
(user-error "Not in a Projectile project"))))))
|
||||
|
||||
(defun gortium/bookmark-delete ()
|
||||
"Delete a bookmark. Prompts: (g)lobal or (p)roject.
|
||||
Project bookmarks are deleted from the .bookmarks file in the project root."
|
||||
(interactive)
|
||||
(let ((choice (read-char-choice "Delete: (g)lobal / (p)roject? " '(?g ?p))))
|
||||
(pcase choice
|
||||
(?g (call-interactively 'bookmark-delete))
|
||||
(?p
|
||||
(if (not (projectile-project-p))
|
||||
(user-error "Not in a Projectile project")
|
||||
(let ((proj-file (expand-file-name ".bookmarks" (projectile-project-root))))
|
||||
(if (not (file-exists-p proj-file))
|
||||
(message "No project bookmarks found.")
|
||||
|
||||
;; Dynamically bind the project bookmarks
|
||||
(let* ((bookmark-alist (with-temp-buffer
|
||||
(insert-file-contents proj-file)
|
||||
(read (current-buffer))))
|
||||
(names (bookmark-all-names)))
|
||||
|
||||
(if (not names)
|
||||
(message "No bookmarks in project.")
|
||||
(let ((bm-name (completing-read "Delete Project Bookmark: " names nil t)))
|
||||
(when (and bm-name (not (string-empty-p bm-name)))
|
||||
;; Delete from the temporarily bound alist and save
|
||||
(bookmark-delete bm-name nil)
|
||||
(bookmark-save nil proj-file t)
|
||||
(message "Deleted project bookmark: %s" bm-name))))))))))))
|
||||
|
||||
(defun gortium/sync-plan-to-bookmark-file (bookmarks)
|
||||
"Write BOOKMARKS alist back to the Plan file under * Bookmarks."
|
||||
@@ -3700,34 +3737,48 @@ Project bookmarks go to .bookmarks file in project root."
|
||||
(message "Transferred '%s' to global" name)))))))
|
||||
|
||||
(defun gortium/list-project-bookmarks ()
|
||||
"Show project bookmarks menu — restores globals after RET jump and on q."
|
||||
"Jump to a project bookmark using standard completion.
|
||||
Keeps global bookmarks completely uncontaminated."
|
||||
(interactive)
|
||||
(if (not (projectile-project-p))
|
||||
(user-error "Not in a project")
|
||||
(let* ((proj-file (expand-file-name ".bookmarks" (projectile-project-root)))
|
||||
(saved-alist (copy-tree bookmark-alist))
|
||||
(saved-file bookmark-default-file))
|
||||
(let ((proj-file (expand-file-name ".bookmarks" (projectile-project-root))))
|
||||
(if (not (file-exists-p proj-file))
|
||||
(message "No .bookmarks file in this project.")
|
||||
;; Read project bookmarks directly (read, not bookmark-load)
|
||||
(setq bookmark-alist
|
||||
(with-temp-buffer
|
||||
(insert-file-contents proj-file)
|
||||
(read (current-buffer))))
|
||||
(bookmark-bmenu-list)
|
||||
;; Restore globals after each RET jump AND on q
|
||||
(with-current-buffer "*Bookmark List*"
|
||||
(local-set-key (kbd "RET")
|
||||
(lambda ()
|
||||
(interactive)
|
||||
(bookmark-bmenu-this-window)
|
||||
(setq bookmark-alist saved-alist)
|
||||
(setq bookmark-default-file saved-file)))
|
||||
(add-hook 'kill-buffer-hook
|
||||
(lambda ()
|
||||
(setq bookmark-alist saved-alist)
|
||||
(setq bookmark-default-file saved-file))
|
||||
nil t))))))
|
||||
|
||||
;; 1. Dynamically bind bookmark-alist.
|
||||
;; Emacs will ONLY see these bookmarks inside this let block.
|
||||
(let* ((bookmark-alist (with-temp-buffer
|
||||
(insert-file-contents proj-file)
|
||||
(read (current-buffer))))
|
||||
|
||||
;; 2. Fetch names based on the temporary project alist
|
||||
(names (bookmark-all-names))
|
||||
|
||||
;; 3. Prompt the user for a selection
|
||||
(bm-name (when names
|
||||
(completing-read "Project Bookmark: " names nil t))))
|
||||
|
||||
;; 4. Jump to it. Once this let block ends, global bookmarks instantly return.
|
||||
(if bm-name
|
||||
(bookmark-jump bm-name)
|
||||
(message "No bookmarks found in project.")))))))
|
||||
|
||||
(map! :leader
|
||||
:desc "Set global/project bookmark"
|
||||
"b m" #'gortium/bookmark-set
|
||||
|
||||
:desc "Delete global/project bookmark"
|
||||
"b M" #'gortium/bookmark-delete)
|
||||
|
||||
;; Example using General.el / Doom Emacs map! macro
|
||||
(map! :leader
|
||||
:desc "List project bookmarks" "p RET" #'gortium/list-project-bookmarks)
|
||||
#+end_src
|
||||
|
||||
** Refile done task
|
||||
|
||||
#+begin_src emacs-lisp
|
||||
(defun gortium/refile-to-today-daily ()
|
||||
"Refile the current subtree under the 'Journal' headline in today's daily note.
|
||||
This version uses `org-roam-dailies-capture-today` to ensure the daily note
|
||||
@@ -3966,6 +4017,5 @@ Need chrome... :(
|
||||
:desc "Edit Edna rules" "E" #'org-edna-edit))
|
||||
|
||||
|
||||
** Project-Aware Agenda
|
||||
|
||||
|
||||
|
||||
@@ -1441,60 +1441,68 @@ Cordialement,
|
||||
(defconst gortium/project-mu4e-keys '(?I ?S ?D ?T ?A)
|
||||
"Character keys used for project mu4e maildir shortcuts (uppercase avoids conflicts).")
|
||||
|
||||
(defvar gortium/project-mu4e-bookmarks nil
|
||||
"Cached project mu4e maildir shortcuts for re-injection when mu4e loads.")
|
||||
|
||||
(defun gortium/inject-project-mu4e-bookmarks ()
|
||||
"Inject project mu4e maildirs into mu4e-maildir-shortcuts.
|
||||
Keys [PI]/[PS]/[PD]/[PT]/[PA] (uppercase). Filters by context-tag:
|
||||
Keys [I]/[S]/[D]/[T]/[A] (uppercase). Filters by context-tag:
|
||||
only injects when the project's context-tag matches current mu4e context."
|
||||
(let* ((proj-root (and (projectile-project-p) (projectile-project-root)))
|
||||
(proj-tag (when proj-root
|
||||
(let ((default-directory proj-root))
|
||||
(gortium/read-dir-local 'gortium/project-tag))))
|
||||
(context-tag (when proj-root
|
||||
(let ((default-directory proj-root))
|
||||
(gortium/read-dir-local 'gortium/context-tag))))
|
||||
(cur-context (and (boundp 'mu4e-context-current)
|
||||
(car-safe mu4e-context-current)))
|
||||
(project-bm '()))
|
||||
;; Only inject if context-tag matches current mu4e context
|
||||
;; (if no context-tag set, always inject)
|
||||
(when (and proj-tag
|
||||
(or (null context-tag)
|
||||
(and cur-context
|
||||
(string= context-tag
|
||||
(mu4e-context-name cur-context)))))
|
||||
(dolist (folder '(("Inbox" . ?I)
|
||||
("Sent" . ?S)
|
||||
("Drafts" . ?D)
|
||||
("Trash" . ?T)
|
||||
("Archive" . ?A)))
|
||||
(push (cons (format "maildir:/%s.%s" (car folder) proj-tag)
|
||||
(cdr folder))
|
||||
project-bm)))
|
||||
;; Inject into mu4e-maildir-shortcuts
|
||||
(setq gortium/project-mu4e-bookmarks project-bm)
|
||||
(when (and (boundp 'mu4e-maildir-shortcuts) project-bm)
|
||||
;; Remove stale project entries
|
||||
(setq mu4e-maildir-shortcuts
|
||||
(seq-filter (lambda (s)
|
||||
(not (and (consp s) (stringp (car s))
|
||||
(string-match "^\\[[A-Z]+\\]" (car s)))))
|
||||
mu4e-maildir-shortcuts))
|
||||
;; Add current project entries
|
||||
(setq mu4e-maildir-shortcuts
|
||||
(append mu4e-maildir-shortcuts project-bm))
|
||||
;; Refresh
|
||||
(setq mu4e--bookmark-items-cached nil)
|
||||
(when (get-buffer "*mu4e-main*")
|
||||
(with-current-buffer "*mu4e-main*"
|
||||
(mu4e--main-redraw))))))
|
||||
(interactive)
|
||||
(when (boundp 'mu4e-maildir-shortcuts)
|
||||
(let* ((proj-root (and (projectile-project-p) (projectile-project-root)))
|
||||
(proj-tag (when proj-root
|
||||
(let ((default-directory proj-root))
|
||||
(gortium/read-dir-local 'gortium/project-tag))))
|
||||
(context-tag (when proj-root
|
||||
(let ((default-directory proj-root))
|
||||
(gortium/read-dir-local 'gortium/context-tag))))
|
||||
;; Extract the context name safely
|
||||
(cur-context-name (and (boundp 'mu4e-context-current)
|
||||
mu4e-context-current
|
||||
(mu4e-context-name mu4e-context-current)))
|
||||
(project-shortcuts '()))
|
||||
|
||||
(add-hook 'projectile-after-switch-project-hook
|
||||
#'gortium/inject-project-mu4e-bookmarks)
|
||||
;; 1. Always strip out previous project shortcuts safely
|
||||
(setq mu4e-maildir-shortcuts
|
||||
(seq-filter (lambda (shortcut)
|
||||
(let ((key (if (listp (cdr shortcut))
|
||||
(plist-get shortcut :key)
|
||||
(cdr shortcut))))
|
||||
(not (memq key gortium/project-mu4e-keys))))
|
||||
mu4e-maildir-shortcuts))
|
||||
|
||||
;; 2. Determine whether to inject and log the reason to *Messages*
|
||||
(cond
|
||||
((not proj-tag)
|
||||
;; No tag found, do nothing silently (prevents spam on non-project files)
|
||||
nil)
|
||||
|
||||
((and context-tag cur-context-name (not (string= context-tag cur-context-name)))
|
||||
(message "[mu4e Project] Skipped: .dir-locals context '%s' does not match active mu4e context '%s'."
|
||||
context-tag cur-context-name))
|
||||
|
||||
(t
|
||||
;; 3. Build and inject the shortcuts
|
||||
(dolist (folder '(("Inbox" . ?I)
|
||||
("Sent" . ?S)
|
||||
("Drafts" . ?D)
|
||||
("Trash" . ?T)
|
||||
("Archive" . ?A)))
|
||||
(push `(:maildir ,(format "/%s.%s" (car folder) proj-tag)
|
||||
:key ,(cdr folder)
|
||||
:name ,(format "[%s] %s" proj-tag (car folder)))
|
||||
project-shortcuts))
|
||||
|
||||
(setq mu4e-maildir-shortcuts
|
||||
(append mu4e-maildir-shortcuts (nreverse project-shortcuts)))
|
||||
(message "[mu4e Project] Successfully injected maildirs for project tag: %s" proj-tag))))))
|
||||
|
||||
;; Apply the hooks
|
||||
(add-hook 'projectile-after-switch-project-hook #'gortium/inject-project-mu4e-bookmarks)
|
||||
(add-hook 'find-file-hook #'gortium/inject-project-mu4e-bookmarks)
|
||||
(add-hook 'mu4e-main-mode-hook #'gortium/inject-project-mu4e-bookmarks)
|
||||
|
||||
;; CRITICAL: Re-inject AFTER mu4e applies its context variables
|
||||
(add-hook 'mu4e-context-changed-hook #'gortium/inject-project-mu4e-bookmarks)
|
||||
|
||||
(after! mu4e
|
||||
(gortium/inject-project-mu4e-bookmarks))
|
||||
|
||||
@@ -2657,6 +2665,7 @@ Saves only project bookmarks without contaminating global list."
|
||||
;; Restore globals
|
||||
(setq bookmark-alist saved-alist)
|
||||
(message "Bookmark added to .bookmarks"))))
|
||||
|
||||
(defun gortium/remove-bookmark-from-plan (name)
|
||||
"Remove a bookmark entry from * Bookmarks in the Plan file."
|
||||
(when-let* ((plan-file (gortium/project-plan-file)))
|
||||
@@ -2704,6 +2713,34 @@ Project bookmarks go to .bookmarks file in project root."
|
||||
(message "Project bookmark \"%s\" saved to .bookmarks" name))
|
||||
(user-error "Not in a Projectile project"))))))
|
||||
|
||||
(defun gortium/bookmark-delete ()
|
||||
"Delete a bookmark. Prompts: (g)lobal or (p)roject.
|
||||
Project bookmarks are deleted from the .bookmarks file in the project root."
|
||||
(interactive)
|
||||
(let ((choice (read-char-choice "Delete: (g)lobal / (p)roject? " '(?g ?p))))
|
||||
(pcase choice
|
||||
(?g (call-interactively 'bookmark-delete))
|
||||
(?p
|
||||
(if (not (projectile-project-p))
|
||||
(user-error "Not in a Projectile project")
|
||||
(let ((proj-file (expand-file-name ".bookmarks" (projectile-project-root))))
|
||||
(if (not (file-exists-p proj-file))
|
||||
(message "No project bookmarks found.")
|
||||
|
||||
;; Dynamically bind the project bookmarks
|
||||
(let* ((bookmark-alist (with-temp-buffer
|
||||
(insert-file-contents proj-file)
|
||||
(read (current-buffer))))
|
||||
(names (bookmark-all-names)))
|
||||
|
||||
(if (not names)
|
||||
(message "No bookmarks in project.")
|
||||
(let ((bm-name (completing-read "Delete Project Bookmark: " names nil t)))
|
||||
(when (and bm-name (not (string-empty-p bm-name)))
|
||||
;; Delete from the temporarily bound alist and save
|
||||
(bookmark-delete bm-name nil)
|
||||
(bookmark-save nil proj-file t)
|
||||
(message "Deleted project bookmark: %s" bm-name))))))))))))
|
||||
|
||||
(defun gortium/sync-plan-to-bookmark-file (bookmarks)
|
||||
"Write BOOKMARKS alist back to the Plan file under * Bookmarks."
|
||||
@@ -2767,34 +2804,44 @@ Project bookmarks go to .bookmarks file in project root."
|
||||
(message "Transferred '%s' to global" name)))))))
|
||||
|
||||
(defun gortium/list-project-bookmarks ()
|
||||
"Show project bookmarks menu — restores globals after RET jump and on q."
|
||||
"Jump to a project bookmark using standard completion.
|
||||
Keeps global bookmarks completely uncontaminated."
|
||||
(interactive)
|
||||
(if (not (projectile-project-p))
|
||||
(user-error "Not in a project")
|
||||
(let* ((proj-file (expand-file-name ".bookmarks" (projectile-project-root)))
|
||||
(saved-alist (copy-tree bookmark-alist))
|
||||
(saved-file bookmark-default-file))
|
||||
(let ((proj-file (expand-file-name ".bookmarks" (projectile-project-root))))
|
||||
(if (not (file-exists-p proj-file))
|
||||
(message "No .bookmarks file in this project.")
|
||||
;; Read project bookmarks directly (read, not bookmark-load)
|
||||
(setq bookmark-alist
|
||||
(with-temp-buffer
|
||||
(insert-file-contents proj-file)
|
||||
(read (current-buffer))))
|
||||
(bookmark-bmenu-list)
|
||||
;; Restore globals after each RET jump AND on q
|
||||
(with-current-buffer "*Bookmark List*"
|
||||
(local-set-key (kbd "RET")
|
||||
(lambda ()
|
||||
(interactive)
|
||||
(bookmark-bmenu-this-window)
|
||||
(setq bookmark-alist saved-alist)
|
||||
(setq bookmark-default-file saved-file)))
|
||||
(add-hook 'kill-buffer-hook
|
||||
(lambda ()
|
||||
(setq bookmark-alist saved-alist)
|
||||
(setq bookmark-default-file saved-file))
|
||||
nil t))))))
|
||||
|
||||
;; 1. Dynamically bind bookmark-alist.
|
||||
;; Emacs will ONLY see these bookmarks inside this let block.
|
||||
(let* ((bookmark-alist (with-temp-buffer
|
||||
(insert-file-contents proj-file)
|
||||
(read (current-buffer))))
|
||||
|
||||
;; 2. Fetch names based on the temporary project alist
|
||||
(names (bookmark-all-names))
|
||||
|
||||
;; 3. Prompt the user for a selection
|
||||
(bm-name (when names
|
||||
(completing-read "Project Bookmark: " names nil t))))
|
||||
|
||||
;; 4. Jump to it. Once this let block ends, global bookmarks instantly return.
|
||||
(if bm-name
|
||||
(bookmark-jump bm-name)
|
||||
(message "No bookmarks found in project.")))))))
|
||||
|
||||
(map! :leader
|
||||
:desc "Set global/project bookmark"
|
||||
"b m" #'gortium/bookmark-set
|
||||
|
||||
:desc "Delete global/project bookmark"
|
||||
"b M" #'gortium/bookmark-delete)
|
||||
|
||||
;; Example using General.el / Doom Emacs map! macro
|
||||
(map! :leader
|
||||
:desc "List project bookmarks" "p RET" #'gortium/list-project-bookmarks)
|
||||
|
||||
(defun gortium/refile-to-today-daily ()
|
||||
"Refile the current subtree under the 'Journal' headline in today's daily note.
|
||||
This version uses `org-roam-dailies-capture-today` to ensure the daily note
|
||||
|
||||
Reference in New Issue
Block a user