fix: restore bookmark fns, require from separate file

This commit is contained in:
2026-07-17 14:32:45 -04:00
parent 73de843c34
commit 39259be53e
3 changed files with 276 additions and 160 deletions

View File

@@ -89,7 +89,7 @@ available. You can either set ~doom-theme~ or manually load a theme with the
~load-theme~ function. This is the default:
#+begin_src emacs-lisp
(setq doom-theme 'doom-gruvbox)
(setq doom-theme 'sanityinc-tomorrow-night)
#+end_src
** Projectile (ExoKortex)
@@ -99,9 +99,9 @@ No more auto-discovery of random =.git= repos.
#+begin_src emacs-lisp
(after! projectile
;; Only .projectile files define a project root
(setq projectile-project-root-files '(".projectile"))
;; Stop auto-scanning everything — only explicit .projectile markers count
(setq projectile-project-root-files-bottom-up nil)
(setq projectile-project-root-files-top-down-recurring nil)
(setq projectile-auto-discover nil))
#+end_src
@@ -240,48 +240,32 @@ org-agenda-files (seq-uniq org-agenda-files)
#+begin_src emacs-lisp
(after! org
;; Header size
(custom-set-faces!
'(org-document-title :height 2.0 :weight bold)
'(org-level-1 :foreground "#b8bb26" :weight semi-bold :height 1.45) ; green
'(org-level-2 :foreground "#83a598" :weight semi-bold :height 1.35) ; blue
'(org-level-3 :foreground "#d3869b" :weight semi-bold :height 1.25) ; purple
'(org-level-4 :foreground "#8ec07c" :weight semi-bold :height 1.15) ; aqua
'(org-level-5 :foreground "#fabd2f" :weight normal :height 1.10) ; yellow
'(org-level-6 :foreground "#fe8019" :weight normal :height 1.05) ; orange
'(org-level-7 :foreground "#cc241d" :weight normal :height 1.02) ; red
'(org-level-8 :foreground "#a89984" :weight normal :height 1.00)) ; grey-brown
;; '(org-level-1 :weight semi-bold :height 1.45)
;; '(org-level-2 :weight semi-bold :height 1.35)
;; '(org-level-3 :weight semi-bold :height 1.25)
;; '(org-level-4 :weight semi-bold :height 1.15)
;; '(org-level-5 :weight normal :height 1.10)
;; '(org-level-6 :weight normal :height 1.05)
;; '(org-level-7 :weight normal :height 1.02)
;; '(org-level-8 :weight normal :height 1.00))
;; 1. Update existing faces (Org defines 1-8 by default)
(set-face-attribute 'org-document-title nil :height 2.0 :weight 'bold)
(set-face-attribute 'org-level-1 nil :weight 'semi-bold :height 1.45)
(set-face-attribute 'org-level-2 nil :weight 'semi-bold :height 1.35)
(set-face-attribute 'org-level-3 nil :weight 'semi-bold :height 1.25)
(set-face-attribute 'org-level-4 nil :weight 'semi-bold :height 1.15)
(set-face-attribute 'org-level-5 nil :weight 'normal :height 1.10)
(set-face-attribute 'org-level-6 nil :weight 'normal :height 1.05)
(set-face-attribute 'org-level-7 nil :weight 'normal :height 1.02)
(set-face-attribute 'org-level-8 nil :weight 'normal :height 1.00)
;; Clamp all levels beyond 8 to use org-level-8 face
(defvar gortium/org-level-8-face
'((t :foreground "#a89984" :weight normal :height 1.00)))
;; '((t :weight normal :height 1.00)))
;; Définir org-level-9 à org-level-20 avec la même face que org-level-8
;; 2. Create missing faces (9-20) and inherit them directly from org-level-8
(cl-loop for lvl from 9 to 20
do (eval
`(defface ,(intern (format "org-level-%d" lvl))
',gortium/org-level-8-face
,(format "Face pour org heading level %d (cloné du niveau 8)." lvl)
:group 'org-faces)))
do (let ((face-name (intern (format "org-level-%d" lvl))))
(custom-declare-face face-name
'((t :inherit org-level-8))
(format "Face for org heading level %d." lvl)
:group 'org-faces)))
;; Ajout des nouvelles faces dans org-level-faces
;; 3. Add the newly created faces to Org's level list
(setq org-level-faces
(append org-level-faces
(mapcar (lambda (lvl) (intern (format "org-level-%d" lvl)))
(number-sequence 9 20))))
;; Mise à jour du nombre total de niveaux
(setq org-n-level-faces (length org-level-faces))
)
(setq org-n-level-faces (length org-level-faces)))
#+end_src
** Clock-in
@@ -2182,7 +2166,7 @@ Useful for the user, but also when you have someone over your shoulder trying to
#+begin_src emacs-lisp
(after! mu4e
(setq mu4e-maildir-shortcuts mu4e-maildir-list mu4e-maildir-initial-input mu4e-maildir-info-delimiter)
(setq mu4e-maildir-initial-input mu4e-maildir-info-delimiter)
(setq mu4e-update-interval 300)
(setq mu4e-headers-auto-update t)
(setq mu4e-contexts
@@ -2203,12 +2187,12 @@ Useful for the user, but also when you have someone over your shoulder trying to
(mu4e-trash-folder . "/TDNDE/Trash")
(mu4e-refile-folder . "/TDNDE/Archive")
;; Shortcuts
(mu4e-maildir-shortcuts . (("/TDNDE/Inbox" . ?i)
("/TDNDE/Sent" . ?s)
("/TDNDE/Drafts" . ?d)
("/TDNDE/Trash" . ?t)
("/TDNDE/Archive" . ?a)))
;; Shortcuts (Updated to modern Plist format)
(mu4e-maildir-shortcuts . ((:maildir "/TDNDE/Inbox" :key ?i)
(:maildir "/TDNDE/Sent" :key ?s)
(:maildir "/TDNDE/Drafts" :key ?d)
(:maildir "/TDNDE/Trash" :key ?t)
(:maildir "/TDNDE/Archive" :key ?a)))
;; SMTP
(smtpmail-smtp-user . "tpouplier@tdnde.com")
@@ -2221,10 +2205,10 @@ Useful for the user, but also when you have someone over your shoulder trying to
(org-msg-signature . "
Cdlt,
,#+begin_signature
#+begin_signature
--
,*Thierry Pouplier*
*Thierry Pouplier*
1319 rue Bergar, Laval, Qc, Canada \\\\
H7L 4Z7 \\\\
@@ -2232,7 +2216,7 @@ Tél. : +1 (450) 667-1884 \\\\
Cell. : +1 (514) 887-1674 \\\\
tpouplier@tdnde.com \\\\
www.tdnde.com \\\\
,#+end_signature")))
#+end_signature")))
;; --- CONTEXT: Perso ---
(make-mu4e-context
@@ -2240,18 +2224,20 @@ www.tdnde.com \\\\
:match-func (lambda (msg)
(when msg
(string-prefix-p "/Perso/" (mu4e-message-field msg :maildir))))
:vars `((user-mail-address . "thierrypouplier@gmail.com")
(user-full-name . "Thierry Pouplier")
(mu4e-sent-folder . "/Perso/Sent")
(mu4e-drafts-folder . "/Perso/Drafts")
(mu4e-trash-folder . "/Perso/Trash")
(mu4e-refile-folder . "/Perso/Archive")
;; Fixed standard single quote evaluation alignment
:vars '((user-mail-address . "thierrypouplier@gmail.com")
(user-full-name . "Thierry Pouplier")
(mu4e-sent-folder . "/Perso/Sent")
(mu4e-drafts-folder . "/Perso/Drafts")
(mu4e-trash-folder . "/Perso/Trash")
(mu4e-refile-folder . "/Perso/Archive")
(mu4e-maildir-shortcuts . (("/Perso/Inbox" . ?i)
("/Perso/Sent" . ?s)
("/Perso/Trash" . ?t)
("/Perso/Drafts" . ?d)
("/Perso/Archive" . ?a)))
;; Shortcuts (Updated to modern Plist format)
(mu4e-maildir-shortcuts . ((:maildir "/Perso/Inbox" :key ?i)
(:maildir "/Perso/Sent" :key ?s)
(:maildir "/Perso/Trash" :key ?t)
(:maildir "/Perso/Drafts" :key ?d)
(:maildir "/Perso/Archive" :key ?a)))
(smtpmail-smtp-user . "thierrypouplier@gmail.com")
(smtpmail-smtp-server . "smtp.gmail.com")
@@ -2261,11 +2247,11 @@ www.tdnde.com \\\\
(org-msg-signature . "
Cordialement,
,#+begin_signature
--
,*Thierry Pouplier* \\
#+begin_signature
-r
*Thierry Pouplier* \\
(514) 887-1674 | thierrypouplier@gmail.com
,#+end_signature"))))) ;; End of mu4e-contexts list
#+end_signature")))))
(setq mu4e-context-policy 'pick-first
mu4e-compose-context-policy 'ask)
@@ -3560,6 +3546,9 @@ to .org files under the project root."
(map! :leader
:desc "Project-aware agenda"
"o P" #'gortium/project-aware-agenda)
;; Load project bookmark functions
(require 'gortium-bookmarks nil t)
#+end_src
#+RESULTS:
@@ -3741,14 +3730,11 @@ Need chrome... :(
(map! :map org-mode-map
:localleader
:desc "Edit Edna rules" "E" #'org-edna-edit))
#+end_src
** Project-Aware Agenda
#+begin_src emacs-lisp
#+begin_src emacs-lisp :tangle no
;; Remove stale project entries and inject current project's agenda
(defvar gortium/project-agenda-commands nil
"Cached project agenda commands.")
@@ -3778,11 +3764,13 @@ Need chrome... :(
#'gortium/inject-project-agenda)
;; Also inject at startup and on file open in a project
(add-hook 'find-file-hook #'gortium/inject-project-agenda)
#+end_src
** Project Bookmarks
;; Load project bookmark functions
(require 'gortium-bookmarks nil t)
;; Load project bookmark functions
(require 'gortium-bookmarks nil t)
#+begin_src emacs-lisp
(defvar gortium/project-bookmark-names nil
"Bookmark names currently in the Plan file for the active project.")
@@ -3834,9 +3822,7 @@ NAME is the display name, URL is the link target (file path or web URL)."
(if (re-search-forward "^\* Bookmarks" nil t)
(forward-line 1)
(goto-char (point-max))
(insert "
* Bookmarks
"))
(insert "\n* Bookmarks\n"))
(insert (format "[[%s][%s]]
" url name))
(write-region (point-min) (point-max) plan-file nil 'silent)
@@ -3957,4 +3943,5 @@ in their native categories. On quit restores global bookmarks."
"b m" #'gortium/bookmark-set
:desc "Jump to project bookmark"
"p RET" #'gortium/list-project-bookmarks)
#+end_src

View File

@@ -10,12 +10,12 @@
(after! doom-big-font-mode
(setq doom-big-font-increment 8))
(setq doom-theme 'doom-gruvbox)
(setq doom-theme 'sanityinc-tomorrow-night)
(after! projectile
;; Only .projectile files define a project root
(setq projectile-project-root-files '(".projectile"))
;; Stop auto-scanning everything — only explicit .projectile markers count
(setq projectile-project-root-files-bottom-up nil)
(setq projectile-project-root-files-top-down-recurring nil)
(setq projectile-auto-discover nil))
(setq bookmark-save-flag 1)
@@ -105,48 +105,32 @@ org-agenda-files (seq-uniq org-agenda-files)
)
(after! org
;; Header size
(custom-set-faces!
'(org-document-title :height 2.0 :weight bold)
'(org-level-1 :foreground "#b8bb26" :weight semi-bold :height 1.45) ; green
'(org-level-2 :foreground "#83a598" :weight semi-bold :height 1.35) ; blue
'(org-level-3 :foreground "#d3869b" :weight semi-bold :height 1.25) ; purple
'(org-level-4 :foreground "#8ec07c" :weight semi-bold :height 1.15) ; aqua
'(org-level-5 :foreground "#fabd2f" :weight normal :height 1.10) ; yellow
'(org-level-6 :foreground "#fe8019" :weight normal :height 1.05) ; orange
'(org-level-7 :foreground "#cc241d" :weight normal :height 1.02) ; red
'(org-level-8 :foreground "#a89984" :weight normal :height 1.00)) ; grey-brown
;; '(org-level-1 :weight semi-bold :height 1.45)
;; '(org-level-2 :weight semi-bold :height 1.35)
;; '(org-level-3 :weight semi-bold :height 1.25)
;; '(org-level-4 :weight semi-bold :height 1.15)
;; '(org-level-5 :weight normal :height 1.10)
;; '(org-level-6 :weight normal :height 1.05)
;; '(org-level-7 :weight normal :height 1.02)
;; '(org-level-8 :weight normal :height 1.00))
;; 1. Update existing faces (Org defines 1-8 by default)
(set-face-attribute 'org-document-title nil :height 2.0 :weight 'bold)
(set-face-attribute 'org-level-1 nil :weight 'semi-bold :height 1.45)
(set-face-attribute 'org-level-2 nil :weight 'semi-bold :height 1.35)
(set-face-attribute 'org-level-3 nil :weight 'semi-bold :height 1.25)
(set-face-attribute 'org-level-4 nil :weight 'semi-bold :height 1.15)
(set-face-attribute 'org-level-5 nil :weight 'normal :height 1.10)
(set-face-attribute 'org-level-6 nil :weight 'normal :height 1.05)
(set-face-attribute 'org-level-7 nil :weight 'normal :height 1.02)
(set-face-attribute 'org-level-8 nil :weight 'normal :height 1.00)
;; Clamp all levels beyond 8 to use org-level-8 face
(defvar gortium/org-level-8-face
'((t :foreground "#a89984" :weight normal :height 1.00)))
;; '((t :weight normal :height 1.00)))
;; Définir org-level-9 à org-level-20 avec la même face que org-level-8
;; 2. Create missing faces (9-20) and inherit them directly from org-level-8
(cl-loop for lvl from 9 to 20
do (eval
`(defface ,(intern (format "org-level-%d" lvl))
',gortium/org-level-8-face
,(format "Face pour org heading level %d (cloné du niveau 8)." lvl)
:group 'org-faces)))
do (let ((face-name (intern (format "org-level-%d" lvl))))
(custom-declare-face face-name
'((t :inherit org-level-8))
(format "Face for org heading level %d." lvl)
:group 'org-faces)))
;; Ajout des nouvelles faces dans org-level-faces
;; 3. Add the newly created faces to Org's level list
(setq org-level-faces
(append org-level-faces
(mapcar (lambda (lvl) (intern (format "org-level-%d" lvl)))
(number-sequence 9 20))))
;; Mise à jour du nombre total de niveaux
(setq org-n-level-faces (length org-level-faces))
)
(setq org-n-level-faces (length org-level-faces)))
(after! org
;; Automatically clock in when switching to STRT
@@ -1340,7 +1324,7 @@ DIFF:
)
(after! mu4e
(setq mu4e-maildir-shortcuts mu4e-maildir-list mu4e-maildir-initial-input mu4e-maildir-info-delimiter)
(setq mu4e-maildir-initial-input mu4e-maildir-info-delimiter)
(setq mu4e-update-interval 300)
(setq mu4e-headers-auto-update t)
(setq mu4e-contexts
@@ -1361,12 +1345,12 @@ DIFF:
(mu4e-trash-folder . "/TDNDE/Trash")
(mu4e-refile-folder . "/TDNDE/Archive")
;; Shortcuts
(mu4e-maildir-shortcuts . (("/TDNDE/Inbox" . ?i)
("/TDNDE/Sent" . ?s)
("/TDNDE/Drafts" . ?d)
("/TDNDE/Trash" . ?t)
("/TDNDE/Archive" . ?a)))
;; Shortcuts (Updated to modern Plist format)
(mu4e-maildir-shortcuts . ((:maildir "/TDNDE/Inbox" :key ?i)
(:maildir "/TDNDE/Sent" :key ?s)
(:maildir "/TDNDE/Drafts" :key ?d)
(:maildir "/TDNDE/Trash" :key ?t)
(:maildir "/TDNDE/Archive" :key ?a)))
;; SMTP
(smtpmail-smtp-user . "tpouplier@tdnde.com")
@@ -1398,18 +1382,20 @@ www.tdnde.com \\\\
:match-func (lambda (msg)
(when msg
(string-prefix-p "/Perso/" (mu4e-message-field msg :maildir))))
:vars `((user-mail-address . "thierrypouplier@gmail.com")
(user-full-name . "Thierry Pouplier")
(mu4e-sent-folder . "/Perso/Sent")
(mu4e-drafts-folder . "/Perso/Drafts")
(mu4e-trash-folder . "/Perso/Trash")
(mu4e-refile-folder . "/Perso/Archive")
;; Fixed standard single quote evaluation alignment
:vars '((user-mail-address . "thierrypouplier@gmail.com")
(user-full-name . "Thierry Pouplier")
(mu4e-sent-folder . "/Perso/Sent")
(mu4e-drafts-folder . "/Perso/Drafts")
(mu4e-trash-folder . "/Perso/Trash")
(mu4e-refile-folder . "/Perso/Archive")
(mu4e-maildir-shortcuts . (("/Perso/Inbox" . ?i)
("/Perso/Sent" . ?s)
("/Perso/Trash" . ?t)
("/Perso/Drafts" . ?d)
("/Perso/Archive" . ?a)))
;; Shortcuts (Updated to modern Plist format)
(mu4e-maildir-shortcuts . ((:maildir "/Perso/Inbox" :key ?i)
(:maildir "/Perso/Sent" :key ?s)
(:maildir "/Perso/Trash" :key ?t)
(:maildir "/Perso/Drafts" :key ?d)
(:maildir "/Perso/Archive" :key ?a)))
(smtpmail-smtp-user . "thierrypouplier@gmail.com")
(smtpmail-smtp-server . "smtp.gmail.com")
@@ -1420,10 +1406,10 @@ www.tdnde.com \\\\
Cordialement,
#+begin_signature
--
-r
*Thierry Pouplier* \\
(514) 887-1674 | thierrypouplier@gmail.com
#+end_signature"))))) ;; End of mu4e-contexts list
#+end_signature")))))
(setq mu4e-context-policy 'pick-first
mu4e-compose-context-policy 'ask)
@@ -2626,6 +2612,9 @@ to .org files under the project root."
:desc "Project-aware agenda"
"o P" #'gortium/project-aware-agenda)
;; Load project bookmark functions
(require 'gortium-bookmarks nil t)
;; Like Emacs everywhere, but work in hyprland
(defun thanos/wtype-text (text)
"Process TEXT for wtype, handling newlines properly."
@@ -2679,33 +2668,3 @@ to .org files under the project root."
;; Make the frame more temporary-like
(set-frame-parameter frame 'delete-before-kill-buffer t)
(set-window-dedicated-p (selected-window) t))))
;; Remove stale project entries and inject current project's agenda
(defvar gortium/project-agenda-commands nil
"Cached project agenda commands.")
(defun gortium/inject-project-agenda ()
"Inject the current project's custom agenda commands into
`org-agenda-custom-commands', or remove them if no project."
(let* ((proj-root (and (projectile-project-p) (projectile-project-root)))
(project-commands nil))
(when proj-root
(let ((default-directory proj-root))
(setq project-commands
(gortium/read-dir-local 'gortium/project-agenda-entry))))
;; Remove stale project entries (key "P")
(setq org-agenda-custom-commands
(seq-filter (lambda (entry)
(not (and (stringp (car entry))
(string= "P" (car entry)))))
org-agenda-custom-commands))
;; Add current project commands
(when project-commands
(setq org-agenda-custom-commands
(append org-agenda-custom-commands project-commands)))))
;; Hook into project switching
(add-hook 'projectile-after-switch-project-hook
#'gortium/inject-project-agenda)
;; Also inject at startup and on file open in a project
(add-hook 'find-file-hook #'gortium/inject-project-agenda)

View File

@@ -0,0 +1,170 @@
;;; gortium-bookmarks.el — ExoKortex Project Bookmarks
;;; Loaded from README.org via (require 'gortium-bookmarks nil t)
(defvar gortium/project-bookmark-names nil
"Bookmark names currently in the Plan file for the active project.")
(defun gortium//project-plan-file ()
"Return the first .org file under 1-Plan/ at the project root, or nil."
(when-let* ((root (projectile-project-root)))
(car (directory-files (expand-file-name "1-Plan" root) t "\\.org\\'" t))))
(defun gortium//project-tag ()
"Read the project tag from .dir-locals.el in the current project."
(when (projectile-project-p)
(let ((default-directory (projectile-project-root)))
(gortium/read-dir-local 'gortium/project-tag))))
(defun gortium//read-plan-bookmarks ()
"Parse * Bookmarks from the Plan file.
Returns alist ((NAME . URL) ...) where URL is the link target."
(when-let* ((plan-file (gortium//project-plan-file))
(_ (file-exists-p plan-file)))
(with-temp-buffer
(insert-file-contents plan-file)
(goto-char (point-min))
(when (re-search-forward "^\\* Bookmarks" nil t)
(forward-line 1)
(let ((bookmarks '()))
(while (not (or (eobp) (looking-at "^\\* ")))
(cond
((looking-at "^\\*\\* \\(.+\\)\n\\[\\[file:\\(.+\\)\\]\\[\\(.+\\)\\]\\]")
(push (cons (match-string 1) (match-string 2)) bookmarks)
(forward-line 2))
((looking-at "\\[\\[\\(.+\\)\\]\\[\\(.+\\)\\]\\]")
(unless (string-prefix-p "mu4e:" (match-string 1))
(push (cons (match-string 2) (match-string 1)) bookmarks))
(forward-line 1))
(t (forward-line 1))))
(setq gortium/project-bookmark-names (mapcar #'car bookmarks))
(nreverse bookmarks))))))
(defun gortium//append-bookmark-to-plan (name url)
"Append a bookmark as [[url][name]] under * Bookmarks in the Plan file.
NAME is the display name, URL is the link target (file path or web URL)."
(when-let* ((plan-file (gortium//project-plan-file)))
(with-temp-buffer
(insert-file-contents plan-file)
(goto-char (point-min))
(if (re-search-forward "^\\* Bookmarks" nil t)
(forward-line 1)
(goto-char (point-max))
(insert "\n* Bookmarks\n"))
(insert (format "[[%s][%s]]\n" url name))
(write-region (point-min) (point-max) plan-file nil 'silent)
(message "Bookmark added to %s" (file-name-nondirectory plan-file))
(push name gortium/project-bookmark-names))))
(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)))
(with-temp-buffer
(insert-file-contents plan-file)
(goto-char (point-min))
(when (re-search-forward (format "^\\*\\* %s\n" (regexp-quote name)) nil t)
(forward-line -1)
(let ((start (point)))
(forward-line 2)
(delete-region start (point)))
(write-region (point-min) (point-max) plan-file nil 'silent)
(message "Bookmark '%s' removed from Plan file" name)
(setq gortium/project-bookmark-names
(delete name gortium/project-bookmark-names))))))
(defun gortium/bookmark-set ()
"Save a bookmark. Prompts: (g)lobal or (p)roject.
Project bookmarks go to * Bookmarks in the Plan file.
The prompt shows the project tag so you can verify the target."
(interactive)
(let ((choice (read-char-choice "Save: (g)lobal / (p)roject? " '(?g ?p))))
(pcase choice
(?g (call-interactively 'bookmark-set))
(?p
(if (projectile-project-p)
(let* ((tag (or (gortium//project-tag) "project"))
(name (read-string (format "[%s] Bookmark name: " tag)))
(file (buffer-file-name)))
(gortium//append-bookmark-to-plan name file)
(message "Project bookmark saved to Plan file."))
(user-error "Not in a Projectile project"))))))
(defun gortium/list-project-bookmarks ()
"Show project bookmarks in the bookmark menu (with categories).
Temporarily replaces bookmark-alist so the menu shows project bookmarks
in their native categories. On quit, restores global bookmarks."
(interactive)
(let* ((bookmarks (gortium//read-plan-bookmarks))
(names (mapcar #'car bookmarks)))
(if (not names)
(message "No bookmarks found in this project's Plan file.")
(let ((saved-alist (copy-tree bookmark-alist)))
(setq bookmark-alist nil)
(dolist (bm (nreverse (copy-tree bookmarks)))
(bookmark-store (car bm) `((filename . ,(cdr bm))) nil))
(bookmark-bmenu-list)
(with-current-buffer "*Bookmark List*"
(add-hook 'kill-buffer-hook
(lambda ()
(setq bookmark-alist saved-alist)
(when-let* ((plan-file (gortium//project-plan-file))
(entries (bookmark-maybe-sort-alist bookmark-alist)))
(gortium//sync-plan-to-bookmark-file
(mapcar (lambda (e)
(cons (car e)
(alist-get 'filename (cdr e))))
entries))))
nil t))))))
(defun gortium//sync-plan-to-bookmark-file (bookmarks)
"Write BOOKMARKS alist back to the Plan file under * Bookmarks."
(when-let* ((plan-file (gortium//project-plan-file)))
(with-temp-buffer
(insert-file-contents plan-file)
(when (re-search-forward "^\\* Bookmarks" nil t)
(forward-line 1)
(let ((start (point)))
(if (re-search-forward "^\\* " nil t)
(forward-line -1)
(goto-char (point-max)))
(delete-region start (point)))
(dolist (bm (nreverse (copy-tree bookmarks)))
(insert (format "[[%s][%s]]\n" (cdr bm) (car bm))))
(write-region (point-min) (point-max) plan-file nil 'silent)))))
(defun gortium/transfer-to-project ()
"Transfer a global bookmark to the current project's Plan file."
(interactive)
(if (projectile-project-p)
(let* ((name (completing-read "Transfer to project: " (bookmark-all-names) nil t))
(file (bookmark-location name)))
(when (and name file (y-or-n-p (format "Transfer '%s' to project? " name)))
(gortium//append-bookmark-to-plan name file)
(bookmark-delete name)
(bookmark-save)
(message "Transferred '%s' to project %s" name
(or (gortium//project-tag) ""))))
(user-error "Not in a Projectile project")))
(defun gortium/transfer-to-global ()
"Transfer a project bookmark to the global bookmarks list."
(interactive)
(let* ((bookmarks (gortium//read-plan-bookmarks))
(names (mapcar #'car bookmarks)))
(if names
(let* ((name (completing-read "Transfer to global: " names nil t))
(url (cdr (assoc name bookmarks))))
(when (and name url (y-or-n-p (format "Transfer '%s' to global? " name)))
(bookmark-store name `((filename . ,url)) t)
(bookmark-save)
(gortium//remove-bookmark-from-plan name)
(message "Transferred '%s' to global" name)))
(message "No project bookmarks to transfer."))))
;; Bindings
(map! :leader
:desc "Set bookmark (global/project)"
"b m" #'gortium/bookmark-set
:desc "Jump to project bookmark"
"p RET" #'gortium/list-project-bookmarks)
(provide 'gortium-bookmarks)