1492 lines
58 KiB
EmacsLisp
1492 lines
58 KiB
EmacsLisp
;;; $DOOMDIR/config.el -*- lexical-binding: t; -*-
|
||
|
||
(setq user-full-name "Thierry Pouplier"
|
||
user-mail-address "tpouplier@tdnde.com")
|
||
|
||
(setq doom-font (font-spec :family "JetBrainsMono Nerd Font" :size 16)
|
||
doom-symbol-font (font-spec :family "JetBrainsMono Nerd Font" :size 20))
|
||
|
||
;; BIGGER FONT MODE
|
||
(after! doom-big-font-mode
|
||
(setq doom-big-font-increment 8))
|
||
|
||
(setq doom-theme 'doom-gruvbox)
|
||
|
||
(setq bookmark-save-flag 1)
|
||
|
||
(setq global-auto-revert-mode 1)
|
||
|
||
(map! :leader
|
||
(:prefix ("w" . "window")
|
||
:desc "Minimize window" "O" #'minimize-window))
|
||
|
||
;; Super- auto -save
|
||
(use-package! super-save
|
||
:config
|
||
(setq super-save-auto-save-when-idle t)
|
||
(setq super-save-all-buffers t)
|
||
(setq super-save-triggers
|
||
'(switch-to-buffer other-window windmove-up windmove-down
|
||
windmove-left windmove-right next-buffer previous-buffer
|
||
evil-window-delete evil-window-vsplit evil-window-spliti
|
||
evil-prev-buffer evil-next-buffer))
|
||
(super-save-mode +1))
|
||
|
||
;; Fixes..
|
||
(require 'cl-lib)
|
||
(unless (fboundp 'find-if)
|
||
(defalias 'find-if #'cl-find-if))
|
||
(unless (fboundp 'getf)
|
||
(defalias 'getf #'cl-getf))
|
||
|
||
(setq
|
||
org-directory "~/ExoKortex/"
|
||
+org-capture-todo-file "~/ExoKortex/2-Areas/Meta-Planning/Core/master_list.org"
|
||
+org-capture-notes-file "~/ExoKortex/2-Areas/Meta-Planning/Core/master_list.org"
|
||
+org-capture-journal-file "~/ExoKortex/2-Areas/Meta-Planning/Core/master_list.org"
|
||
+org-capture-emails-file "~/ExoKortex/2-Areas/Meta-Planning/Core/master_list.org"
|
||
;; +org-capture-central-project-todo-file '"refile.org"
|
||
;; +org-capture-project-todo-file '"refile.org"
|
||
org-agenda-files (directory-files-recursively "~/ExoKortex/" "\\.org$")
|
||
)
|
||
|
||
(after! org
|
||
(setq org-refile-targets
|
||
'((nil :maxlevel . 6) ;; Current buffer
|
||
(org-agenda-files :maxlevel . 6))) ;; All 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
|
||
|
||
;; 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)))
|
||
|
||
;; Définir org-level-9 à org-level-20 avec la même face que 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)))
|
||
|
||
;; Ajout des nouvelles faces dans org-level-faces
|
||
(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))
|
||
)
|
||
|
||
(after! org
|
||
;; Automatically clock in when switching to STRT
|
||
(defun gortium/org-clock-in-if-starting ()
|
||
"Clock in when task is switched to STRT."
|
||
(when (and (string= org-state "STRT")
|
||
(not (bound-and-true-p org-clock-resolving-clocks-due-to-idleness)))
|
||
(org-clock-in)))
|
||
|
||
;; Automatically clock out when switching to DONE
|
||
(defun gortium/org-clock-out-if-not-starting ()
|
||
"Clock out if task was in STRT and is being changed to anything else."
|
||
(when (and (string= org-last-state "STRT")
|
||
(not (string= org-state "STRT")))
|
||
(when (org-clocking-p)
|
||
(org-clock-out))))
|
||
|
||
;; Hook both functions into todo state change
|
||
(add-hook 'org-after-todo-state-change-hook #'gortium/org-clock-in-if-starting)
|
||
(add-hook 'org-after-todo-state-change-hook #'gortium/org-clock-out-if-not-starting)
|
||
|
||
;; Enable Org Clocking
|
||
(setq org-clock-persist 'history) ;; Save clock history across sessions
|
||
(add-hook 'org-mode-hook 'org-clock-load) ; Load persisted clocks
|
||
(setq org-clock-persist 'history) ; Or 't for full persistence
|
||
(setq org-clock-in-resume t) ; Resume clock when restarting the task
|
||
|
||
;; Save clock data and state changes when exiting Emacs
|
||
(setq org-clock-out-remove-zero-time-clocks t)
|
||
(setq org-clock-in-resume t) ;; Resume clocking when moving between tasks
|
||
|
||
;; Always clock out when exiting Emacs
|
||
;; (add-hook 'kill-emacs-hook #'(lambda () (when (org-clocking-p) (org-clock-out))))
|
||
|
||
;; Mode-line clock display
|
||
(setq org-clock-mode-line-total 'auto) ;; show today's total
|
||
(org-clock-display) ;; activate the mode-line display
|
||
|
||
(custom-set-faces!
|
||
'(org-mode-line-clock
|
||
:foreground "gold"
|
||
:weight bold))
|
||
)
|
||
|
||
(after! org
|
||
(setq org-columns-default-format "%65ITEM %4TODO %3PRIORITY %ASSIGNEE %5EFFORT{:} %5CLOCKSUM %16SCHEDULED %16DEADLINE %10TAGS")
|
||
|
||
(setq org-global-properties
|
||
'(("Effort_ALL" . "0:10 0:30 1:00 2:00 4:00 8:00")
|
||
("ASSIGNEE_ALL" . "Thierry Cath Martin Michel Gabriel Silvie George Miguel")))
|
||
|
||
(setq org-stuck-projects
|
||
'("TODO=\"PROJ\"" ("NEXT") nil ""))
|
||
|
||
(setq org-agenda-exporter-settings '((ps-print-color-p 'nil)))
|
||
(setq
|
||
org-agenda-follow-mode nil
|
||
org-agenda-skip-deadline-if-done nil
|
||
org-agenda-skip-scheduled-if-done nil
|
||
org-agenda-skip-timestamp-if-done nil
|
||
org-journal-enable-agenda-integration t
|
||
org-log-done 'time
|
||
org-log-into-drawer t
|
||
org-log-redeadline 'time
|
||
org-log-reschedule 'time
|
||
org-todo-repeat-to-state '"LOOP"
|
||
org-todo-keywords
|
||
'(
|
||
(sequence
|
||
;; Tasks
|
||
"TODO(t)" "STRT(s)" "LOOP(r)" "NEXT(n)" "DELG(g)"
|
||
"WAIT(w@/!)" "HOLD(h@/!)" "IDEA(i)"
|
||
|
||
;; Framework
|
||
"EPIC(e)" "AREA(a)" "PROJ(p)"
|
||
|
||
;; Done / Cancelled
|
||
"|" "DONE(d!)" "CNCL(c@/!)")
|
||
|
||
;; Checkboxes
|
||
(sequence "[ ](T)" "[-](S)" "|" "[X](D)")
|
||
|
||
;; Special indicators
|
||
(sequence "[!](F)" "[?](W)" "|" "[i](I)")
|
||
|
||
;; Binary questions
|
||
(sequence "Y/N(M)" "|" "YES(Y)" "NOP(N)")
|
||
)
|
||
)
|
||
;; Not there yet.. Need better integration with org-modern and gruvbox theme
|
||
;; org-todo-keyword-faces
|
||
;; '(
|
||
;; ;; Main task keywords
|
||
;; ("TODO" . (:foreground "orange red" :weight bold))
|
||
;; ("PROJ" . (:foreground "orchid" :weight bold))
|
||
;; ("STRT" . (:foreground "deep sky blue" :weight bold))
|
||
;; ("LOOP" . (:foreground "cyan" :weight bold))
|
||
;; ("NEXT" . (:foreground "light salmon" :weight bold))
|
||
;; ("DELG" . (:foreground "khaki" :weight bold))
|
||
;; ("WAIT" . (:foreground "goldenrod" :weight bold))
|
||
;; ("HOLD" . (:foreground "light goldenrod" :weight bold))
|
||
;; ("IDEA" . (:foreground "medium purple" :weight bold))
|
||
|
||
;; ;; Done and cancelled
|
||
;; ("DONE" . (:foreground "forest green" :weight bold))
|
||
;; ("CNCL" . (:foreground "dim gray" :weight bold :slant italic))
|
||
|
||
;; ;; Checkbox-like states
|
||
;; ("[ ]" . (:foreground "orange red" :weight bold))
|
||
;; ("[-]" . (:foreground "deep sky blue" :weight bold))
|
||
;; ("[X]" . (:foreground "forest green" :weight bold))
|
||
|
||
;; ;; Special states
|
||
;; ("[?]" . (:foreground "goldenrod" :weight bold))
|
||
;; ("[i]" . (:foreground "medium purple" :weight bold))
|
||
|
||
;; ;; Yes/No states
|
||
;; ("Y/N" . (:foreground "light steel blue" :weight bold))
|
||
;; ("YES" . (:foreground "pale green" :weight bold))
|
||
;; ("NOP" . (:foreground "indian red" :weight bold))
|
||
;; )
|
||
;; %a : org-store-link
|
||
;; %i : insert from selection
|
||
;; %? : cursor position at the end
|
||
;; %u : unactive date
|
||
;; %t : ative date
|
||
;; %:subject : subject of the message
|
||
;; %:from : The full sender string including name and address
|
||
;; %:fromname : The display name of the sender
|
||
;; %:fromaddress : The email address of the sender
|
||
;; %:to, %:toname, %toaddress : Same for the recipient
|
||
;; %:date : Date of the message
|
||
;; %:date-timestamp : The date of the message as a active org timestamp
|
||
;;
|
||
;; Original Doom capture:
|
||
;; ("p" "Templates for projects")
|
||
;; ("pt" "Project-local todo" entry (file+headline +org-capture-project-todo-file "Inbox") "* TODO %?\n %i\n %a" :prepend t)
|
||
;; ("pn" "Project-local notes" entry (file+headline +org-capture-project-notes-file "Inbox") "* %U %?\n %i\n %a" :prepend t)
|
||
;; ("o" "Centralized templates for projects")
|
||
;; ("ot" "Project todo" entry #'+org-capture-central-project-todo-file "* TODO %?\n %i\n %a" :heading "Tasks" :prepend nil)
|
||
;; ("on" "Project notes" entry #'+org-capture-central-project-notes-file "* %U %?\n %i\n %a" :heading "Notes" :prepend t)
|
||
|
||
;; Code to automate task status, but i think some package exist that do a better job
|
||
;; (defun org-summary-todo (n-done n-not-done)
|
||
;; "Switch entry to DONE when all subentries are done, to TODO otherwise."
|
||
;; (let (org-log-done org-log-states) ; turn off logging
|
||
;; (org-todo (if (= n-not-done 0) "DONE" "TODO"))))
|
||
;; (add-hook 'org-after-todo-statistics-hook 'org-summary-todo)
|
||
(setq gtd/started-head "🚀 Started:")
|
||
(setq gtd/next-action-head "👉 Next actions:")
|
||
(setq gtd/waiting-head "⏳ Waiting on:")
|
||
;; (setq gtd/complete-head "Completed items:")
|
||
(setq gtd/project-head "‼ Stuck projects:")
|
||
(setq gtd/someday-head "💡 Someday/maybe:")
|
||
(setq qaa/q "❓ All the Questions:")
|
||
(setq qaa/a "ℹ All the Answers:")
|
||
(setq review/done "🦾 Completed Tasks")
|
||
(setq review/unfinished "🔀 Unfinished Scheduled Tasks")
|
||
(setq org-agenda-start-day "+0")
|
||
(setq org-agenda-custom-commands
|
||
'(
|
||
("w" . "Work commands")
|
||
("p" . "Personal commands")
|
||
("wg" "GTD view"
|
||
(
|
||
(agenda "" ((org-agenda-span 'day)))
|
||
(todo "STRT" ((org-agenda-overriding-header gtd/started-head)))
|
||
(todo "NEXT" ((org-agenda-overriding-header gtd/next-action-head)))
|
||
(todo "WAIT" ((org-agenda-overriding-header gtd/waiting-head)))
|
||
;; (todo "DONE" ((org-agenda-overriding-header gtd/complete-head)))
|
||
(stuck "" ((org-stuck-projects '("TODO=\"PROJ\"" ("NEXT" "STRT") nil ""))
|
||
(org-agenda-overriding-header gtd/project-head)))
|
||
(todo "IDEA" ((org-agenda-overriding-header gtd/someday-head)))
|
||
)
|
||
((org-agenda-tag-filter-preset '("+work")))
|
||
)
|
||
("pg" "GTD view"
|
||
(
|
||
(agenda "" ((org-agenda-span 'day)))
|
||
(todo "STRT" ((org-agenda-overriding-header gtd/started-head)))
|
||
(todo "NEXT" ((org-agenda-overriding-header gtd/next-action-head)))
|
||
(todo "WAIT" ((org-agenda-overriding-header gtd/waiting-head)))
|
||
;; (todo "DONE" ((org-agenda-overriding-header gtd/complete-head)))
|
||
(stuck "" ((org-stuck-projects '("TODO=\"PROJ\"" ("NEXT" "STRT") nil ""))
|
||
(org-agenda-overriding-header gtd/project-head)))
|
||
(todo "IDEA" ((org-agenda-overriding-header gtd/someday-head)))
|
||
)
|
||
((org-agenda-tag-filter-preset '("+perso")))
|
||
)
|
||
("wq" "All the questions.."
|
||
(
|
||
(todo "[?]" ((org-agenda-overriding-header qaa/q)))
|
||
(todo "[i]" ((org-agenda-overriding-header qaa/a)))
|
||
)
|
||
((org-agenda-tag-filter-preset '("+work")))
|
||
)
|
||
("pq" "All the questions.."
|
||
(
|
||
(todo "[?]" ((org-agenda-overriding-header qaa/q)))
|
||
(todo "[i]" ((org-agenda-overriding-header qaa/a)))
|
||
)
|
||
((org-agenda-tag-filter-preset '("+perso")))
|
||
)
|
||
("wr" "Weekly Review"
|
||
((agenda ""
|
||
((org-agenda-overriding-header review/done)
|
||
(org-agenda-skip-function '(org-agenda-skip-entry-if 'nottodo 'done))
|
||
(org-agenda-span 'week)
|
||
(org-agenda-start-day nil)))
|
||
|
||
(agenda ""
|
||
((org-agenda-overriding-header review/unfinished)
|
||
(org-agenda-skip-function '(org-agenda-skip-entry-if 'todo 'done))
|
||
(org-agenda-span 'week)
|
||
(org-agenda-start-day nil)))
|
||
)
|
||
((org-agenda-tag-filter-preset '("+work")))
|
||
)
|
||
("pr" "Weekly Review"
|
||
((agenda ""
|
||
((org-agenda-overriding-header review/done)
|
||
(org-agenda-skip-function '(org-agenda-skip-entry-if 'nottodo 'done))
|
||
(org-agenda-span 'week)
|
||
(org-agenda-start-day nil)))
|
||
|
||
(agenda ""
|
||
((org-agenda-overriding-header review/unfinished)
|
||
(org-agenda-skip-function '(org-agenda-skip-entry-if 'todo 'done))
|
||
(org-agenda-span 'week)
|
||
(org-agenda-start-day nil)))
|
||
)
|
||
((org-agenda-tag-filter-preset '("+perso")))
|
||
)
|
||
("wp" "Planning"
|
||
((agenda ""
|
||
((org-agenda-span 'month)
|
||
(org-agenda-start-day nil)
|
||
(org-agenda-overriding-header "📅 Sheduled Tasks"))
|
||
)
|
||
|
||
(todo "TODO|NEXT|DELG|WAIT|HOLD|STRT|IDEA"
|
||
((org-agenda-overriding-header "📋 Unscheduled TODOs")
|
||
(org-agenda-skip-function '(org-agenda-skip-entry-if 'scheduled))
|
||
)
|
||
)
|
||
)
|
||
((org-agenda-tag-filter-preset '("+work")))
|
||
)
|
||
("pp" "Planning"
|
||
((agenda ""
|
||
((org-agenda-span 'month)
|
||
(org-agenda-start-day nil)
|
||
(org-agenda-overriding-header "📅 Sheduled Tasks"))
|
||
)
|
||
|
||
(todo "TODO|NEXT|DELG|WAIT|HOLD|STRT|IDEA"
|
||
((org-agenda-overriding-header "📋 Unscheduled TODOs")
|
||
(org-agenda-skip-function '(org-agenda-skip-entry-if 'scheduled))
|
||
)
|
||
)
|
||
)
|
||
((org-agenda-tag-filter-preset '("+perso")))
|
||
)
|
||
("wP" "THE PLAN"
|
||
((agenda ""
|
||
((org-agenda-span 'month)
|
||
(org-agenda-start-day nil)
|
||
(org-agenda-overriding-header "📅 THE PLAN"))
|
||
)
|
||
)
|
||
((org-agenda-tag-filter-preset '("+work")))
|
||
)
|
||
)
|
||
)
|
||
)
|
||
|
||
(after! org
|
||
;; Rust code block setting
|
||
(setq rustic-babel-display-error-popup nil)
|
||
)
|
||
|
||
(after! org
|
||
(setq
|
||
|
||
;; LaTeX math preview
|
||
org-format-latex-options '(:foreground default :background default :scale 2 :html-foreground "Black"
|
||
:html-background "Transparent" :html-scale 1.0
|
||
:matchers ("begin" "$1" "$" "$$" "\\(" "\\["))
|
||
)
|
||
)
|
||
|
||
(after! org
|
||
(setq
|
||
org-capture-templates
|
||
'(
|
||
("t" "TODO" entry (file+olp +org-capture-todo-file "Tasks")
|
||
"* IDEA %? \n:PROPERTIES:\n:CREATED: %U\n:END:\n%a\n%i" :prepend t)
|
||
("c" "Code TODO" entry
|
||
(file+olp +org-capture-todo-file "Tasks")
|
||
"* TODO %a\n:PROPERTIES:\n:CREATED: %U\n:END:\n":prepend t)
|
||
("n" "Note" entry (file+olp +org-capture-notes-file "Notes")
|
||
"* %? \n:PROPERTIES:\n:CREATED: %U\n:END:\n%a\n%i")
|
||
("w" "Question" entry (file+olp +org-capture-notes-file "Questions")
|
||
"* [?] %? \n:PROPERTIES:\n:CREATED: %U\n:END:\n%a\n%i")
|
||
("j" "Journal" entry (file+olp+datetree +org-capture-journal-file "Journal")
|
||
"* %?\n%a\n%i")
|
||
("m" "Meeting" entry (file+olp +org-capture-notes-file "Meetings")
|
||
"* %? %U :meeting:\n:PROPERTIES:\n:CREATED: %U\n:END:\n\n/Met with: /")
|
||
("a" "Appointment" entry (file+olp +org-capture-journal-file "Appointments")
|
||
"* %? :appointment:\n:PROPERTIES:\n:CREATED: %U\n:END:\n%a\n%i" :time-prompt t)
|
||
("e" "Email Workflow")
|
||
("ef" "Follow Up" entry (file+olp +org-capture-emails-file "Mails" "Follow Up") "* TODO Follow up with %:fromname on %a\nSCHEDULED:%t\nDEADLINE:%(org-insert-time-stamp (org-read-date nil t \"+2d\"))\n\n%i" :immediate-finish t)
|
||
("er" "Read Later" entry (file+olp +org-capture-emails-file "Mails" "Read Later") "* TODO Read: %a\nSCHEDULED:%t\nDEADLINE:%(org-insert-time-stamp (org-read-date nil t \"+2d\"))\n\n%i" :immediate-finish t)
|
||
("ew" "Waiting Response" entry (file+olp +org-capture-emails-file "Mails" "Waiting Response") "* WAIT Waiting response from %:toname on %a\nDEADLINE:%(org-insert-time-stamp (org-read-date nil t \"+2d\"))\n\n%i" :immediate-finish t)
|
||
)
|
||
)
|
||
)
|
||
|
||
(setq org-roam-directory (file-truename "~/ExoKortex/")
|
||
org-roam-db-location (file-truename "~/ExoKortex/2-Areas/IT/Roam/org-roam.db")
|
||
org-attach-id-dir "assets/"
|
||
org-roam-dailies-directory "~/ExoKortex/2-Areas/Meta-Planning/Journal/Daily")
|
||
|
||
(after! org-roam
|
||
org-roam-completion-everywhere t
|
||
org-roam-db-autosync-mode 1
|
||
(require 'org-roam-dailies)
|
||
|
||
(setq org-roam-dailies-capture-templates
|
||
'(("n" "Note" entry "** %<%H:%M> 📝 %?"
|
||
:target (file+head+olp "%<%Y-%m-%d>.org"
|
||
"#+title: %<%Y-%m-%d>\n\n* Journal\n"
|
||
("Journal"))
|
||
:empty-lines 1)
|
||
|
||
("t" "Task" entry "** %<%H:%M> 🛠 %a"
|
||
:target (file+head+olp "%<%Y-%m-%d>.org"
|
||
"#+title: %<%Y-%m-%d>\n\n* Journal\n"
|
||
("Journal"))
|
||
:immediate-finish t
|
||
:empty-lines 1)
|
||
|
||
("m" "Meeting" entry "** %<%H:%M> 👥 Meeting %?"
|
||
:target (file+head+olp "%<%Y-%m-%d>.org"
|
||
"#+title: %<%Y-%m-%d>\n\n* Journal\n"
|
||
("Journal"))
|
||
:empty-lines 1)
|
||
|
||
("u" "Muffin" entry "** %<%H:%M> 🥐 Muffin"
|
||
:target (file+head+olp "%<%Y-%m-%d>.org"
|
||
"#+title: %<%Y-%m-%d>\n\n* Journal\n"
|
||
("Journal"))
|
||
:immediate-finish t
|
||
:empty-lines 1)
|
||
|
||
("e" "Event" entry "** %<%H:%M> 📅 Event: %?"
|
||
:target (file+head+olp "%<%Y-%m-%d>.org"
|
||
"#+title: %<%Y-%m-%d>\n\n* Journal\n"
|
||
("Journal"))
|
||
:empty-lines 1)
|
||
|
||
("c" "Clock In" entry "** %<%H:%M> ⏱ Clocked In"
|
||
:target (file+head+olp "%<%Y-%m-%d>.org"
|
||
"#+title: %<%Y-%m-%d>\n\n* Journal\n"
|
||
("Journal"))
|
||
:immediate-finish t
|
||
:empty-lines 1)
|
||
|
||
("o" "Clock Out" entry "** %<%H:%M> 🚪 Clocked Out"
|
||
:target (file+head+olp "%<%Y-%m-%d>.org"
|
||
"#+title: %<%Y-%m-%d>\n\n* Journal\n"
|
||
("Journal"))
|
||
:immediate-finish t
|
||
:empty-lines 1)
|
||
)
|
||
)
|
||
)
|
||
|
||
;; Org-roam-dailies new entry help function
|
||
;; Adapted from https://rostre.bearblog.dev/building-my-ideal-emacs-journal/
|
||
(defun gortium/org-roam-dailies-new-entry ()
|
||
"Create a new entry in org-roam daily notes with interactive prompt.
|
||
Handles org-clock and context link capture for tasks."
|
||
(interactive)
|
||
(let* ((choices '(("Task 🛠" . task)
|
||
("Note 📝" . note)
|
||
("Meeting 👥" . meeting)
|
||
("Muffin 🥐" . muffin)
|
||
("Event 📅" . event)
|
||
("Clock In ⏱" . in)
|
||
("Clock Out 🚪" . out)))
|
||
(choice-label (completing-read "Entry type: " (mapcar #'car choices)))
|
||
(entry-type (cdr (assoc choice-label choices))))
|
||
|
||
;; Handle preconditions
|
||
(cond
|
||
((eq entry-type 'task)
|
||
(let ((check-and-clock-in
|
||
(lambda ()
|
||
(unless (org-entry-get nil "TODO" t)
|
||
(user-error "Point is not under a TODO heading"))
|
||
(org-todo "STRT")
|
||
(org-clock-in)
|
||
(org-store-link nil t))))
|
||
(cond
|
||
((eq major-mode 'org-agenda-mode)
|
||
(save-window-excursion
|
||
(org-agenda-switch-to)
|
||
(funcall check-and-clock-in)))
|
||
((eq major-mode 'org-mode)
|
||
(funcall check-and-clock-in))
|
||
(t
|
||
(user-error "Unsupported major mode for task entry")))))
|
||
|
||
((eq entry-type 'muffin)
|
||
(org-clock-out))
|
||
|
||
((eq entry-type 'out)
|
||
(org-clock-out))
|
||
|
||
((eq entry-type 'in)
|
||
(org-clock-in)))
|
||
|
||
;; Launch capture
|
||
(let ((key (pcase entry-type
|
||
('note "n") ('task "t") ('meeting "m")
|
||
('muffin "m") ('event "e")
|
||
('in "c") ('out "o"))))
|
||
(org-roam-dailies-capture-date nil nil key))))
|
||
|
||
;; Connecteam org-roam-dailies integration
|
||
(require 'request)
|
||
|
||
(defvar gortium/connecteam-api-key (auth-source-passage-get 'secret "connecteam") "Your Connecteam API Key")
|
||
(defvar gortium/connecteam-user-id "9885891" "Your Connecteam User ID")
|
||
(defvar gortium/connecteam-clock-id "9335145" "Your Connecteam time clock ID used in API calls.")
|
||
|
||
(defun gortium/connecteam-clock-in (job-id time-clock-id)
|
||
"Send a clock-in request to Connecteam API for JOB-ID and TIME-CLOCK-ID."
|
||
(request
|
||
(format "https://api.connecteam.com/time-clock/v1/time-clocks/%s/clock-in" time-clock-id)
|
||
:type "POST"
|
||
:headers `(("X-API-KEY" . ,gortium/connecteam-api-key)
|
||
("accept" . "application/json")
|
||
("content-type" . "application/json"))
|
||
:data (json-encode `((userId . ,gortium/connecteam-user-id)
|
||
(jobId . ,job-id)))
|
||
:parser 'json-read
|
||
:success (cl-function
|
||
(lambda (&key data &allow-other-keys)
|
||
(message "Connecteam clock-in successful: %s" (alist-get 'message data))))
|
||
:error (cl-function
|
||
(lambda (&rest args &key error-thrown &allow-other-keys)
|
||
(message "Connecteam clock-in failed: %S" error-thrown)))))
|
||
|
||
(defun gortium/connecteam-clock-out (time-clock-id)
|
||
"Send a clock-out request to Connecteam API for TIME-CLOCK-ID."
|
||
(request
|
||
(format "https://api.connecteam.com/time-clock/v1/time-clocks/%s/clock-out" time-clock-id)
|
||
:type "POST"
|
||
:headers `(("X-API-KEY" . ,gortium/connecteam-api-key)
|
||
("accept" . "application/json")
|
||
("content-type" . "application/json"))
|
||
:data (json-encode `((userId . ,gortium/connecteam-user-id)))
|
||
:parser 'json-read
|
||
:success (cl-function
|
||
(lambda (&key data &allow-other-keys)
|
||
(message "Connecteam clock-out successful: %s" (alist-get 'message data))))
|
||
:error (cl-function
|
||
(lambda (&rest args &key error-thrown &allow-other-keys)
|
||
(message "Connecteam clock-out failed: %S" error-thrown)))))
|
||
|
||
(defun gortium/org-clock-in-to-connecteam ()
|
||
"Clock in to Connecteam using the job ID in the current Org task."
|
||
(when (org-entry-get nil "CONNECTEAM_JOB_ID" t)
|
||
(let ((job-id (org-entry-get nil "CONNECTEAM_JOB_ID" t)))
|
||
(gortium/connecteam-clock-in job-id gortium/connecteam-clock-id))))
|
||
|
||
(add-hook 'org-clock-in-hook #'gortium/org-clock-in-to-connecteam)
|
||
|
||
(defun gortium/org-clock-out-from-connecteam ()
|
||
"Clock out from Connecteam or trigger vacation request if MUFFIN property is set."
|
||
(let ((muffin (org-entry-get nil "MUFFIN")))
|
||
(if muffin
|
||
(gortium/connecteam-vacation-request)
|
||
(gortium/connecteam-clock-out gortium/connecteam-clock-id))))
|
||
|
||
(add-hook 'org-clock-out-hook #'gortium/org-clock-out-from-connecteam)
|
||
|
||
(after! calendar
|
||
;; Define a function to open org-roam daily note for selected date in calendar
|
||
(defun gortium/org-roam-dailies-open-at-point ()
|
||
"Open org-roam daily note for date at point in calendar."
|
||
(interactive)
|
||
(let ((date (calendar-cursor-to-date)))
|
||
(org-roam-dailies-goto-date date)))
|
||
)
|
||
|
||
;; Bind RET in calendar mode to open daily note instead of default exit
|
||
(evil-define-key 'normal calendar-mode-map
|
||
(kbd "RET") #'gortium/org-roam-dailies-open-at-point)
|
||
|
||
(after! dap-mode
|
||
(require 'dap-python)
|
||
(setq dap-python-debugger 'debugpy)
|
||
(dap-ui-mode 1)
|
||
(add-hook 'dap-terminated-hook
|
||
(lambda ()
|
||
(dap-ui-controls-mode -1)
|
||
(dap-ui-mode -1))))
|
||
|
||
(after! dap-python
|
||
(dap-register-debug-template
|
||
"Python :: My Script with Args"
|
||
(list :type "python"
|
||
:args ["--start" "73"
|
||
"--input" "/home/tpouplier/ExoKortex/1-Projects/Exit_strat/Resources/G05730212_111 scan plans/curve_G05730212_111.src"
|
||
"--template" "/home/tpouplier/ExoKortex/1-Projects/Exit_strat/exit_strat/ExitPath_Template.src"
|
||
"--output" "/home/tpouplier/ExoKortex/1-Projects/Exit_strat/exit_strat/output/ExitPath.src"]
|
||
:cwd nil
|
||
:module nil
|
||
:program nil
|
||
:request "launch"
|
||
:name "Python :: My Script with Args")))
|
||
|
||
;; LSP BABY
|
||
(after! lsp-mode
|
||
(setq lsp-csharp-server-path "~/.local/tools/omnisharp-mono/omnisharp.sh")
|
||
)
|
||
|
||
;; To keep my eye in the center of the screen while scrolling. Like in my nvim x)
|
||
(setq scroll-margin 10)
|
||
|
||
;; Enable visual line wrapping globally
|
||
;; (global-visual-line-mode 1)
|
||
;; Enable visual line wrapping in certain modes
|
||
(after! org
|
||
(add-hook 'org-mode-hook #'visual-line-mode))
|
||
(after! prog-mode
|
||
(add-hook 'prog-mode-hook #'visual-line-mode))
|
||
(after! text-mode
|
||
(add-hook 'text-mode-hook #'visual-line-mode))
|
||
|
||
(use-package! org-phscroll
|
||
:after org)
|
||
|
||
;; Now I can write x) (spellchecking)
|
||
(after! ispell
|
||
(setq ispell-program-name "hunspell"
|
||
ispell-dictionary "en_CA,fr_CA"
|
||
ispell-personal-dictionary "/home/tpouplier/ExoKortex/2-Areas/IT/Dictionaries/perso.dic")
|
||
|
||
;; Set the dictionary path environment variable
|
||
(setenv "DICPATH" "/home/tpouplier/ExoKortex/2-Areas/IT/Dictionaries")
|
||
|
||
(setq ispell-hunspell-dict-paths-alist
|
||
'(("fr_CA" "/home/tpouplier/ExoKortex/2-Areas/IT/Dictionaries/fr_CA.aff")
|
||
("en_CA" "/home/tpouplier/ExoKortex/2-Areas/IT/Dictionaries/en_CA.aff")))
|
||
|
||
(setq ispell-hunspell-dictionary-alist
|
||
'(("fr_CA" "[[:alpha:]]" "[^[:alpha:]]" "[']" nil ("-d" "fr_CA") nil utf-8)
|
||
("en_CA" "[[:alpha:]]" "[^[:alpha:]]" "[']" nil ("-d" "en_CA") nil utf-8)))
|
||
|
||
;; ispell-set-spellchecker-params has to be called
|
||
;; before ispell-hunspell-add-multi-dic will work
|
||
(ispell-set-spellchecker-params)
|
||
(ispell-hunspell-add-multi-dic "en_CA,fr_CA"))
|
||
|
||
;; Let me write like a broken engineer, thank you.
|
||
(add-hook 'writegood-mode-hook 'writegood-passive-voice-turn-off)
|
||
|
||
;; Move selected region up or down
|
||
(use-package! drag-stuff
|
||
:config
|
||
;; Enable drag-stuff-mode globally
|
||
(drag-stuff-global-mode 1)
|
||
|
||
;; Set keybindings
|
||
(map! :v "M-j" #'drag-stuff-down
|
||
:v "M-k" #'drag-stuff-up
|
||
:n "M-j" #'drag-stuff-down
|
||
:n "M-k" #'drag-stuff-up)
|
||
|
||
;; Fold bindings
|
||
(after! evil
|
||
(define-key evil-motion-state-map (kbd "z C") #'hs-hide-level))
|
||
|
||
;; Disable global bindings for C-j and C-k
|
||
(global-unset-key (kbd "C-j"))
|
||
(global-unset-key (kbd "C-k"))
|
||
|
||
;; Optionally define default keybindings (if needed)
|
||
(drag-stuff-define-keys))
|
||
|
||
(setq display-line-numbers-type 'relative)
|
||
|
||
;; JK to escape was not working. Added it back.
|
||
(use-package! evil-escape
|
||
:config
|
||
(setq evil-escape-excluded-states '(normal visual multiedit emacs motion)
|
||
evil-escape-excluded-major-modes '(neotree-mode treemacs-mode vterm-mode)
|
||
evil-escape-key-sequence "jk"
|
||
evil-escape-delay 0.15)
|
||
(evil-escape-mode 1)
|
||
)
|
||
|
||
(after! nix-mode
|
||
(setq lsp-nix-nil-formatter ["nixpkgs-fmt"])
|
||
(add-hook 'nix-mode-hook #'lsp!))
|
||
|
||
(after! lsp-mode
|
||
(setq lsp-nix-server 'nil))
|
||
|
||
;; KRL mode
|
||
(add-hook 'krl-mode-hook 'font-lock-mode)
|
||
(add-hook 'krl-mode-hook 'display-line-numbers-mode)
|
||
(use-package! krl-mode
|
||
:mode (("\\.src\\'" . krl-mode)
|
||
("\\.dat\\'" . krl-mode)
|
||
("\\.sub\\'" . krl-mode)))
|
||
|
||
(use-package! auto-highlight-symbol
|
||
:hook (krl-mode . auto-highlight-symbol-mode)
|
||
:config
|
||
(setq ahs-idle-interval 0.5 ; highlight after 0.5s
|
||
ahs-default-range 'ahs-range-whole-buffer ; highlight in whole buffer
|
||
ahs-case-fold-search t ; case-INsensitive matching
|
||
ahs-include-definition t)) ; highlight definition too
|
||
|
||
(defcustom krl-formatter-command "python"
|
||
"Command to run the KRL formatter."
|
||
:type 'string
|
||
:group 'krl)
|
||
|
||
(defcustom krl-formatter-script-path "~/ExoKortex/1-Projects/Exit_strat/exit_strat/scripts/krl_formatter.py"
|
||
"Path to the KRL formatter script."
|
||
:type 'string
|
||
:group 'krl)
|
||
|
||
(defun krl-format-buffer ()
|
||
"Format the current buffer using the KRL formatter."
|
||
(interactive)
|
||
(let ((original-point (point))
|
||
(original-line (line-number-at-pos)))
|
||
(shell-command-on-region
|
||
(point-min)
|
||
(point-max)
|
||
(concat krl-formatter-command " " krl-formatter-script-path)
|
||
t t)
|
||
;; Try to restore cursor position
|
||
(goto-char (point-min))
|
||
(forward-line (1- original-line))
|
||
(message "KRL buffer formatted")))
|
||
|
||
(defun krl-format-region (start end)
|
||
"Format the selected region using the KRL formatter."
|
||
(interactive "r")
|
||
(shell-command-on-region
|
||
start
|
||
end
|
||
(concat krl-formatter-command " " krl-formatter-script-path)
|
||
t t)
|
||
(message "KRL region formatted"))
|
||
|
||
;; Auto-format on save (optional)
|
||
(defun krl-format-before-save ()
|
||
"Format KRL code before saving."
|
||
(when (eq major-mode 'krl-mode)
|
||
(krl-format-buffer)))
|
||
|
||
;; Uncomment the next line to enable auto-formatting on save
|
||
(add-hook 'before-save-hook 'krl-format-before-save)
|
||
|
||
(use-package! hledger-mode
|
||
:config
|
||
(setq hledger-jfile "~/2-Areas/Finances/finance_vault/tpouplier.hledger")
|
||
(setq hledger-top-asset-account "Assets")
|
||
(setq hledger-top-income-account "Incomes")
|
||
(setq hledger-top-expense-account "Expenses")
|
||
(setq hledger-ratios-debt-accounts "Liabilities")
|
||
(setq hledger-ratios-assets-accounts "Assets")
|
||
(setq hledger-ratios-income-accounts "Incomes")
|
||
(setq hledger-ratios-liquid-asset-accounts "Assets:Cash")
|
||
(setq hledger-ratios--accounts "Assets:Cash")
|
||
;; (setq hledger-ratios-debt-accounts "Liabilities")
|
||
(add-to-list 'auto-mode-alist '("\\.hledger\\'" . hledger-mode))
|
||
)
|
||
|
||
;; Hook fragtog to org-mode to have editable latex preview
|
||
(use-package! org-fragtog
|
||
:hook (org-mode . org-fragtog-mode))
|
||
|
||
;; Rescale latex preview when changing font size
|
||
(defun gortium/org-latex-refresh-on-zoom (&rest _)
|
||
"Dynamically rescale and refresh all LaTeX previews after text zoom."
|
||
(when (derived-mode-p 'org-mode)
|
||
;; Dynamically update scale relative to zoom level
|
||
(setq org-format-latex-options
|
||
(plist-put org-format-latex-options
|
||
:scale (+ 2 (* 0.5 text-scale-mode-amount))
|
||
))
|
||
;; Clear all previews
|
||
(org-clear-latex-preview)
|
||
;; Re-render all previews
|
||
(org-latex-preview '(16))
|
||
))
|
||
|
||
(advice-add 'text-scale-increase :after #'gortium/org-latex-refresh-on-zoom)
|
||
(advice-add 'text-scale-decrease :after #'gortium/org-latex-refresh-on-zoom)
|
||
(advice-add 'text-scale-set :after #'gortium/org-latex-refresh-on-zoom)
|
||
|
||
(use-package! age
|
||
:demand t
|
||
:config
|
||
(setq age-program "rage")
|
||
(setq age-default-identity "~/.ssh/gortium_ssh_key")
|
||
(setq age-default-recipient "~/.ssh/gortium_ssh_key.pub")
|
||
(age-file-enable))
|
||
|
||
(require 'notifications)
|
||
(require 'cl-lib)
|
||
|
||
(defun gortium/age-notify (msg &optional simple)
|
||
"Notify about AGE operations. SIMPLE uses `message` instead of desktop notification."
|
||
(if simple
|
||
(message "%s" msg)
|
||
(if (eq system-type 'gnu/linux)
|
||
(notifications-notify
|
||
:title "age.el"
|
||
:body msg
|
||
:urgency 'low
|
||
:timeout 800)
|
||
(message "%s" msg))))
|
||
|
||
(defun gortium/age-notify-decrypt (&rest args)
|
||
"Notification hook for age decryption."
|
||
(cl-destructuring-bind (context cipher) args
|
||
(gortium/age-notify (format "Decrypting %s" (age-data-file cipher)) t)))
|
||
|
||
(defun gortium/age-notify-encrypt (&rest args)
|
||
"Notification hook for age encryption."
|
||
(cl-destructuring-bind (context plain recipients) args
|
||
(gortium/age-notify (format "Encrypting %s" (age-data-file plain)) t)))
|
||
|
||
(defun gortium/age-toggle-decrypt-notifications ()
|
||
"Toggle notifications for age decryption."
|
||
(interactive)
|
||
(if (advice-member-p #'gortium/age-notify-decrypt #'age-start-decrypt)
|
||
(progn
|
||
(advice-remove #'age-start-decrypt #'gortium/age-notify-decrypt)
|
||
(message "Disabled age decrypt notifications."))
|
||
(advice-add #'age-start-decrypt :before #'gortium/age-notify-decrypt)
|
||
(message "Enabled age decrypt notifications.")))
|
||
|
||
(defun gortium/age-toggle-encrypt-notifications ()
|
||
"Toggle notifications for age encryption."
|
||
(interactive)
|
||
(if (advice-member-p #'gortium/age-notify-encrypt #'age-start-encrypt)
|
||
(progn
|
||
(advice-remove #'age-start-encrypt #'gortium/age-notify-encrypt)
|
||
(message "Disabled age encrypt notifications."))
|
||
(advice-add #'age-start-encrypt :before #'gortium/age-notify-encrypt)
|
||
(message "Enabled age encrypt notifications.")))
|
||
|
||
;; enable notifications by default
|
||
(gortium/age-toggle-decrypt-notifications)
|
||
(gortium/age-toggle-encrypt-notifications)
|
||
|
||
(use-package! passage
|
||
:demand t
|
||
:config
|
||
;; rebind function value for pass to passage
|
||
(fset #'pass (lambda () (interactive) (passage)))
|
||
(setq age-program "rage")
|
||
(setq auth-source-passage-filename (expand-file-name "~/ExoKortex/2-Areas/IT/dotfiles/secrets"))
|
||
(setenv "PASSAGE_IDENTITIES_FILE" (expand-file-name age-default-identity))
|
||
(setenv "PASSAGE_RECIPIENTS_FILE" (expand-file-name age-default-recipient))
|
||
(setenv "PASSAGE_AGE" "rage")
|
||
(setenv "PASSAGE_DIR" (expand-file-name "~/ExoKortex/2-Areas/IT/dotfiles/secrets"))
|
||
)
|
||
|
||
;; TUI tools in emacs
|
||
(after! eee
|
||
(setq ee-terminal-command "kitty")
|
||
)
|
||
|
||
;; Ensure C-j works properly in insert state in vterm
|
||
(after! vterm
|
||
(add-hook 'vterm-mode-hook
|
||
(lambda ()
|
||
(evil-local-set-key 'insert (kbd "C-j") #'vterm--self-insert))))
|
||
|
||
;; GPTel AI chat for emacs
|
||
(use-package! gptel
|
||
:config
|
||
;; (advice-add 'gptel-rewrite :after #'gptel-rewrite-diff)
|
||
(add-hook 'gptel-post-stream-hook 'gptel-auto-scroll)
|
||
(setq gptel-display-buffer-action
|
||
'(display-buffer-same-window))
|
||
(defun gptel-set-default-directory ()
|
||
(unless (buffer-file-name)
|
||
(setq default-directory "~/ExoKortex/3-Resources/AI-Chat/")))
|
||
(add-hook 'gptel-mode-hook #'gptel-set-default-directory)
|
||
|
||
(setq gptel-expert-commands t
|
||
gptel-default-mode 'org-mode
|
||
;; gptel-model 'OpenRouter:deepseek/deepseek-chat-v3-0324:free
|
||
gptel-api-key (auth-source-passage-get 'secret "openrouter"))
|
||
|
||
(require 'gptel-integrations)
|
||
(gptel-make-openai "OpenRouter"
|
||
:host "openrouter.ai"
|
||
:endpoint "/api/v1/chat/completions"
|
||
:stream t
|
||
:key (auth-source-passage-get 'secret "openrouter")
|
||
:models '(deepseek/deepseek-r1-0528-qwen3-8b:free
|
||
google/gemini-2.0-flash-exp:free
|
||
deepseek/deepseek-chat-v3-0324:free
|
||
meta-llama/llama-4-maverick:free
|
||
qwen/qwen3-coder:free))
|
||
(gptel-make-gemini "Gemini"
|
||
:key (auth-source-passage-get 'secret "gemini")
|
||
:stream t
|
||
:models '(gemini-2.5-pro
|
||
gemini-2.5-flash))
|
||
(gptel-make-ollama "Ollama" ;Any name of your choosing
|
||
:host "localhost:11434" ;Where it's running
|
||
:stream t ;Stream responses
|
||
:models '(deepseek-r1:1.5b
|
||
orieg/gemma3-tools:4b)) ;List of models
|
||
(gptel-make-openai "OpenWebUI"
|
||
:host "ai.aziworkhorse.duckdns.org"
|
||
:curl-args '("--insecure") ; needed for self-signed certs
|
||
:key (auth-source-passage-get 'secret "openwebui")
|
||
:endpoint "/api/chat/completions"
|
||
:stream t
|
||
:models '("orieg/gemma3-tools:1b"))
|
||
)
|
||
|
||
(map! :after gptel
|
||
:leader
|
||
(:prefix ("r" . "GPTel Rewrite")
|
||
:desc "Rewrite region" "r" #'gptel-rewrite
|
||
:desc "Show rewrite diff" "d" #'gptel--rewrite-diff
|
||
:desc "Accept rewrite" "a" #'gptel--rewrite-accept
|
||
:desc "Reject rewrite" "x" #'gptel--rewrite-reject
|
||
:desc "Iterate rewrite" "i" #'gptel--rewrite-iterate))
|
||
|
||
(diff-hl-mode +1)
|
||
|
||
;; set `tramp-direct-async-process' locally in all ssh connections
|
||
(connection-local-set-profile-variables
|
||
'remote-direct-async-process
|
||
'((tramp-direct-async-process . t)))
|
||
(connection-local-set-profiles
|
||
'(:application tramp :protocol "ssh")
|
||
'remote-direct-async-process)
|
||
|
||
;; Dirvish config
|
||
(after! dirvish
|
||
;; Display icons, file size, timestamps, etc.
|
||
(setq dirvish-attributes
|
||
'(all-the-icons subtree-state file-size file-time))
|
||
|
||
(setq dirvish-default-layout '(0.3 0.15 0.55))
|
||
|
||
;; Use a header line instead of the traditional dired modeline
|
||
(setq dirvish-use-header-line t)
|
||
|
||
;; Show dotfile please, ty
|
||
;; (setq dired-omit-mode nil)
|
||
;; (setq dired-listing-switches "-a")
|
||
|
||
;; Replace default dired mode globally
|
||
(dirvish-override-dired-mode)
|
||
|
||
(require 'dirvish-yank)
|
||
|
||
(setq! dirvish-quick-access-entries
|
||
`(
|
||
("h" "~/" "Home")
|
||
("k" "~/ExoKortex/2-Areas/Meta-Planning/Core" "ExoKortex")
|
||
("p" "~/ExoKortex/1-Projects" "Projects")
|
||
("a" "~/ExoKortex/2-Areas" "Areas")
|
||
("r" "~/ExoKortex/3-Resources" "Resources")
|
||
("i" "~/ExoKortex/4-Archives" "Archives")
|
||
("d" "~/Downloads/" "Downloads")
|
||
("u" "/run/media" "Mounted drives")
|
||
("t" "~/.local/share/Trash/files/" "Trash")
|
||
)))
|
||
|
||
;; Enable previewing of surrounding lines in consult-ripgrep
|
||
(setq consult-ripgrep-preview t)
|
||
|
||
;; Weather
|
||
(after! wttrin
|
||
(setq wttrin-default-cities '("Blainville" "Canada"))
|
||
)
|
||
;; Not working..
|
||
;; (setq weather-metno-location-name "Blainville, Canada"
|
||
;; weather-metno-location-latitude 45
|
||
;; weather-metno-location-longitude 73)
|
||
|
||
;; Modern look for org
|
||
(use-package! org-modern
|
||
:after org
|
||
:config
|
||
(global-org-modern-mode)
|
||
(setq org-modern-todo-faces
|
||
'(
|
||
;; Framework
|
||
("EPIC" :foreground "#b16286" :inverse-video t :weight bold) ; large project
|
||
("AREA" :foreground "#83a598" :inverse-video t :weight bold) ; domain
|
||
("PROJ" :foreground "#458588" :inverse-video t :weight bold) ; specific project
|
||
|
||
;; Tasks
|
||
("TODO" :foreground "#fb4934") ; to do
|
||
("STRT" :foreground "#fe8019" :weight bold :inverse-video t) ; started
|
||
("NEXT" :foreground "#b8bb26" :weight bold :underline t) ; next action
|
||
("LOOP" :foreground "#fabd2f") ; recurring
|
||
("DELG" :foreground "#8ec07c") ; delegated
|
||
("WAIT" :foreground "#fabd2f" :slant italic) ; waiting
|
||
("HOLD" :foreground "#7c6f64" :slant italic) ; on hold
|
||
("IDEA" :foreground "#d65d0e" :slant italic) ; idea
|
||
|
||
;; Done / Cancelled
|
||
("DONE" :foreground "#98971a") ; done
|
||
("CNCL" :foreground "#928374" :strike-through t) ; cancelled
|
||
|
||
;; Checkboxes
|
||
("[ ]" :foreground "#fb4934") ; unchecked
|
||
("[-]" :foreground "#fabd2f") ; partial
|
||
("[X]" :foreground "#98971a") ; checked
|
||
|
||
;; Special indicators
|
||
("[!]" :foreground "#fb4934" :weight bold :inverse-video t) ; urgent
|
||
("[?]" :foreground "#d79921" :weight bold) ; uncertain
|
||
("[i]" :foreground "#b16286" :slant italic) ; info
|
||
|
||
;; Binary questions
|
||
("Y/N" :foreground "#fe8019" :weight bold) ; question
|
||
("YES" :foreground "#b8bb26") ; yes
|
||
("NOP" :foreground "#928374") ; no
|
||
)
|
||
)
|
||
)
|
||
|
||
;; Flash the point (cursor) when moving between window
|
||
(use-package! beacon
|
||
:config
|
||
(beacon-mode 1))
|
||
|
||
;; Deleted file go to trash instead of been destroyed for ever... (rm -r / --do-it)
|
||
(setq delete-by-moving-to-trash t
|
||
trash-directory "~/.local/share/Trash/files/")
|
||
|
||
(after! org-msg
|
||
(setq mail-user-agent 'mu4e-user-agent)
|
||
(require 'org-msg)
|
||
(setq org-msg-options "html-postamble:nil H:5 num:nil ^:{} toc:nil author:nil email:nil \\n:t"
|
||
org-msg-startup "hidestars indent inlineimages"
|
||
org-msg-greeting-fmt "\nBonjour%s,\n\n"
|
||
;; org-msg-recipient-names '(("jeremy.compostella@gmail.com" . "Jérémy"))
|
||
org-msg-greeting-name-limit 1
|
||
org-msg-default-alternatives '((new . (text html))
|
||
(reply-to-html . (text html))
|
||
(reply-to-text . (text)))
|
||
org-msg-convert-citation t
|
||
org-msg-signature "
|
||
|
||
Cdlt,
|
||
|
||
#+begin_signature
|
||
--
|
||
|
||
*Thierry Pouplier*
|
||
|
||
1319 rue Bergar, Laval, Qc, Canada \\\\
|
||
H7L 4Z7 \\\\
|
||
Tél. : +1 (450) 667-1884 \\\\
|
||
Cell. : +1 (514) 887-1674 \\\\
|
||
tpouplier@tdnde.com \\\\
|
||
www.tdnde.com \\\\
|
||
#+end_signature")
|
||
)
|
||
|
||
(after! mu4e
|
||
;; Each path is relative to the path of the maildir you passed to mu
|
||
(set-email-account! "tpouplier@tdnde.com"
|
||
'(
|
||
(mu4e-change-filenames-when-moving t)
|
||
(mu4e-update-interval 120)
|
||
(mu4e-sent-folder . "/Sent")
|
||
(mu4e-drafts-folder . "/Drafts")
|
||
(mu4e-trash-folder . "/Trash")
|
||
(mu4e-refile-folder . "/Inbox")
|
||
(smtpmail-smtp-user . "tpouplier@tdnde.com")
|
||
(smtpmail-stream-type . ssl)
|
||
(smtpmail-smtp-server . "smtp.hostinger.com")
|
||
(smtpmail-smtp-service . 465)
|
||
|
||
;; (mu4e-maildir-shortcuts
|
||
;; '(
|
||
;; ("Inbox" . ?i)
|
||
;; ("Drafts" . ?d)
|
||
;; ("Sent" . ?s)
|
||
;; ("Trash" . ?t)
|
||
;; ))
|
||
)
|
||
t)
|
||
)
|
||
|
||
(after! circe
|
||
;; set your nick, username, and real name
|
||
(setq circe-nick "gortium"
|
||
circe-user-name "gortium"
|
||
circe-real-name "gortium")
|
||
(setq circe-network-options
|
||
'(("Libera Chat"
|
||
:tls t
|
||
:nick "gortium"
|
||
:sasl-username "gortium"
|
||
:sasl-password (auth-source-passage-get 'secret "irc")
|
||
:channels ("#emacs-circe")
|
||
)))
|
||
)
|
||
|
||
;; Enable midnight mode by default for PDF files
|
||
(after! pdf-tools
|
||
(add-hook 'pdf-view-mode-hook (lambda () (pdf-view-midnight-minor-mode 1))))
|
||
|
||
;; Like Emacs everywhere, but work in hyprland
|
||
(defun thanos/wtype-text (text)
|
||
"Process TEXT for wtype, handling newlines properly."
|
||
(let* ((has-final-newline (string-match-p "\n$" text))
|
||
(lines (split-string text "\n"))
|
||
(last-idx (1- (length lines))))
|
||
(string-join
|
||
(cl-loop for line in lines
|
||
for i from 0
|
||
collect (cond
|
||
;; Last line without final newline
|
||
((and (= i last-idx) (not has-final-newline))
|
||
(format "wtype -s 350 \"%s\""
|
||
(replace-regexp-in-string "\"" "\\\\\"" line)))
|
||
;; Any other line
|
||
(t
|
||
(format "wtype -s 350 \"%s\" && wtype -k Return"
|
||
(replace-regexp-in-string "\"" "\\\\\"" line)))))
|
||
" && ")))
|
||
|
||
(define-minor-mode thanos/type-mode
|
||
"Minor mode for inserting text via wtype."
|
||
:keymap `((,(kbd "C-c C-c") . ,(lambda () (interactive)
|
||
(call-process-shell-command
|
||
(thanos/wtype-text (buffer-string))
|
||
nil 0)
|
||
(delete-frame)))
|
||
(,(kbd "C-c C-k") . ,(lambda () (interactive)
|
||
(kill-buffer (current-buffer))))))
|
||
|
||
(defun thanos/type ()
|
||
"Launch a temporary frame with a clean buffer for typing."
|
||
(interactive)
|
||
(let ((frame (make-frame '((name . "emacs-float")
|
||
(fullscreen . 0)
|
||
(undecorated . t)
|
||
(width . 70)
|
||
(height . 20))))
|
||
(buf (get-buffer-create "emacs-float")))
|
||
(select-frame frame)
|
||
(switch-to-buffer buf)
|
||
(with-current-buffer buf
|
||
(erase-buffer)
|
||
(org-mode)
|
||
(flyspell-mode)
|
||
(thanos/type-mode)
|
||
(setq-local header-line-format
|
||
(format " %s to insert text or %s to cancel."
|
||
(propertize "C-c C-c" 'face 'help-key-binding)
|
||
(propertize "C-c C-k" 'face 'help-key-binding)))
|
||
;; Make the frame more temporary-like
|
||
(set-frame-parameter frame 'delete-before-kill-buffer t)
|
||
(set-window-dedicated-p (selected-window) t))))
|
||
|
||
;;Refile in datetree function
|
||
(defun org-refile-to-datetree (&optional file)
|
||
"Refile a subtree to a datetree corresponding to its timestamp.
|
||
|
||
The current time is used if the entry has no timestamp.
|
||
If FILE is nil, refile in the current file."
|
||
(interactive
|
||
(list (read-file-name "Refile to file: "
|
||
nil ; directory
|
||
(buffer-file-name) ; default filename
|
||
t))) ; mustmatch
|
||
(let* ((datetree-date (or (org-entry-get nil "TIMESTAMP" t)
|
||
(org-read-date t nil "now")))
|
||
(date (org-date-to-gregorian datetree-date)))
|
||
(with-current-buffer (current-buffer)
|
||
(save-excursion
|
||
(org-cut-subtree)
|
||
(when file
|
||
(find-file file))
|
||
(org-datetree-find-date-create date)
|
||
(org-narrow-to-subtree)
|
||
(show-subtree)
|
||
(org-end-of-subtree t)
|
||
(newline)
|
||
(goto-char (point-max))
|
||
(org-paste-subtree 4)
|
||
(widen)))))
|
||
|
||
;; Open all org fold in ediff
|
||
(defun org-ediff-prepare-buffer ()
|
||
(when (memq major-mode '(org-mode emacs-lisp-mode))
|
||
(outline-show-all)))
|
||
|
||
(add-hook 'ediff-prepare-buffer-hook 'org-ediff-prepare-buffer)
|
||
|
||
(defun gortium/add-trigger-scheduling-next ()
|
||
"Add scheduled chain for this entry."
|
||
(interactive)
|
||
(org-set-property "TRIGGER" "next-sibling scheduled!(\"++0d\") todo!(NEXT) chain!(\"TRIGGER\")"))
|
||
|
||
(defun gortium/org-schedule-after-previous-sibling ()
|
||
"Schedule the current task right after its previous sibling.
|
||
If the sibling is DONE, use its CLOSED time.
|
||
Otherwise, use SCHEDULED + EFFORT."
|
||
(interactive)
|
||
(unless (org-at-heading-p)
|
||
(org-back-to-heading t))
|
||
(let ((current-level (org-current-level))
|
||
(current-point (point))
|
||
prev-end-time)
|
||
(save-excursion
|
||
;; Find previous sibling of same level
|
||
(let ((found nil))
|
||
(while (and (not found) (outline-previous-heading))
|
||
(when (= (org-current-level) current-level)
|
||
(setq found t)))
|
||
(unless found
|
||
(user-error "No previous sibling found"))
|
||
;; At previous sibling now
|
||
(let* ((state (org-get-todo-state))
|
||
(scheduled (org-entry-get nil "SCHEDULED"))
|
||
(closed (org-entry-get nil "CLOSED"))
|
||
(effort (org-entry-get nil "EFFORT")))
|
||
(cond
|
||
;; If task is DONE and CLOSED exists
|
||
((and (member state org-done-keywords) closed)
|
||
(setq prev-end-time (org-time-string-to-time closed)))
|
||
;; Else use SCHEDULED + EFFORT
|
||
((and scheduled effort)
|
||
(let* ((sched-time (org-time-string-to-time scheduled))
|
||
(duration-min (org-duration-to-minutes effort)))
|
||
(setq prev-end-time
|
||
(time-add sched-time (seconds-to-time (* 60 duration-min))))))
|
||
(t
|
||
(user-error "Previous sibling missing SCHEDULED/CLOSED or EFFORT"))))))
|
||
;; Schedule current task
|
||
(goto-char current-point)
|
||
(org-schedule nil (format-time-string (org-time-stamp-format t) prev-end-time))))
|
||
|
||
(defun gortium/org-schedule-siblings-chain ()
|
||
"Schedule each sibling task sequentially starting from a selected date.
|
||
DONE tasks use their CLOSED time as reference.
|
||
Pending tasks are scheduled based on the previous end time (SCHEDULED + EFFORT).
|
||
Skips weekends and respects max daily effort starting at `gortium/org-day-start-hour`.
|
||
Starting at `gortium/org-day-start-hour` (e.g., 9 means 09:00 AM)."
|
||
(interactive)
|
||
(let ((gortium/org-day-start-hour 8) ;; Change to choose start of work day
|
||
(max-daily-effort-min (* 8 60))) ;; Change to choose the max work hours per day
|
||
(let* ((time-str (org-read-date nil nil nil "Start date and time for scheduling:"))
|
||
(parsed-time (org-parse-time-string time-str))
|
||
(hour (nth 2 parsed-time))
|
||
(min (nth 1 parsed-time))
|
||
(sec (nth 0 parsed-time))
|
||
(final-time (apply #'encode-time
|
||
(list (or sec 0)
|
||
(or min 0)
|
||
;; if hour is nil, use `gortium/org-day-start-hour`
|
||
(if hour hour gortium/org-day-start-hour)
|
||
(nth 3 parsed-time)
|
||
(nth 4 parsed-time)
|
||
(nth 5 parsed-time)))))
|
||
(save-excursion
|
||
(unless (org-at-heading-p)
|
||
(org-back-to-heading t))
|
||
(let* ((level (org-current-level))
|
||
(siblings (list (point)))
|
||
(daily-effort 0)
|
||
(prev-end (gortium/org--skip-weekend final-time)))
|
||
|
||
;; Collect sibling positions
|
||
(while (outline-get-next-sibling)
|
||
(when (= (org-current-level) level)
|
||
(push (point) siblings)))
|
||
(setq siblings (nreverse siblings))
|
||
|
||
;; Helpers
|
||
(cl-labels
|
||
((get-task-end (pos)
|
||
(goto-char pos)
|
||
(let* ((state (org-get-todo-state))
|
||
(closed (org-entry-get nil "CLOSED"))
|
||
(scheduled (org-entry-get nil "SCHEDULED"))
|
||
(effort (org-entry-get nil "EFFORT")))
|
||
(cond
|
||
((and (member state org-done-keywords) closed)
|
||
(org-time-string-to-time closed))
|
||
((and scheduled effort)
|
||
(let* ((sched-time (org-time-string-to-time scheduled))
|
||
(duration-min (org-duration-to-minutes effort)))
|
||
(time-add sched-time (seconds-to-time (* 60 duration-min)))))
|
||
(t nil))))
|
||
(task-effort-seconds (pos)
|
||
(goto-char pos)
|
||
(let ((effort (org-entry-get nil "EFFORT")))
|
||
(if effort
|
||
(* 60 (org-duration-to-minutes effort))
|
||
0)))
|
||
(next-day-start (time)
|
||
"Return next working day's start time at `gortium/org-day-start-hour`, skipping weekends."
|
||
(let ((next (time-add time (days-to-time 1))))
|
||
(gortium/org--skip-weekend
|
||
(apply #'encode-time `(0 0 ,gortium/org-day-start-hour
|
||
,(nth 3 (decode-time next))
|
||
,(nth 4 (decode-time next))
|
||
,(nth 5 (decode-time next)))))))
|
||
(schedule-task (pos time)
|
||
(goto-char pos)
|
||
(org-schedule nil (format-time-string (org-time-stamp-format t) time))))
|
||
|
||
;; Schedule first task
|
||
(setq daily-effort (task-effort-seconds (car siblings)))
|
||
(schedule-task (car siblings) prev-end)
|
||
(setq prev-end (time-add prev-end (seconds-to-time daily-effort)))
|
||
|
||
;; Process the rest
|
||
(dolist (pos (cdr siblings))
|
||
(let ((effort-sec (task-effort-seconds pos)))
|
||
(when (> (+ daily-effort effort-sec) (* 60 max-daily-effort-min))
|
||
(setq prev-end (next-day-start prev-end))
|
||
(setq daily-effort 0))
|
||
|
||
(schedule-task pos prev-end)
|
||
(setq daily-effort (+ daily-effort effort-sec))
|
||
(setq prev-end (time-add prev-end (seconds-to-time effort-sec)))))))))))
|
||
|
||
(defun gortium/org--skip-weekend (time)
|
||
"Advance TIME to the next weekday if it's Saturday or Sunday."
|
||
(let ((dow (nth 6 (decode-time time))))
|
||
(cond
|
||
((= dow 6) (time-add time (days-to-time 2))) ; Saturday → Monday
|
||
((= dow 0) (time-add time (days-to-time 1))) ; Sunday → Monday
|
||
(t time))))
|
||
|
||
;; Custom function to shift projects
|
||
(defun gortium/org-shift-subtree-schedules (days)
|
||
"Shift all SCHEDULED dates in the current subtree by DAYS."
|
||
(interactive "nDays to shift: ")
|
||
(org-map-entries
|
||
(lambda ()
|
||
(let ((scheduled (org-entry-get nil "SCHEDULED")))
|
||
(when scheduled
|
||
(let* ((time (org-time-string-to-time scheduled))
|
||
(new-time (time-add time (days-to-time days)))
|
||
(new-date (format-time-string (org-time-stamp-format) new-time)))
|
||
(org-entry-put nil "SCHEDULED" new-date)))))
|
||
nil 'tree))
|
||
|
||
;; Open link in another frame
|
||
(defun gortium/org-open-link-in-other-frame ()
|
||
"Open the Org link at point in another frame."
|
||
(interactive)
|
||
(let ((org-link-frame-setup '((file . find-file-other-frame)
|
||
(id . find-file-other-frame))))
|
||
(org-open-at-point)))
|
||
|
||
(map! :after org
|
||
:map org-mode-map
|
||
:n "gF" #'gortium/org-open-link-in-other-frame)
|
||
|
||
(defvar my/main-frame nil
|
||
"The main Emacs frame where buffers should be toggled to/from.")
|
||
|
||
(add-hook 'emacs-startup-hook
|
||
(lambda () (setq my/main-frame (selected-frame))))
|
||
|
||
(defun my--rightmost-non-minibuffer-window (frame)
|
||
"Return the rightmost non-minibuffer window in FRAME, or nil if none."
|
||
(let ((best-win nil)
|
||
(best-right -1))
|
||
(dolist (w (window-list frame))
|
||
(unless (window-minibuffer-p w)
|
||
(let ((right (nth 2 (window-edges w))))
|
||
(when (> right best-right)
|
||
(setq best-right right
|
||
best-win w)))))
|
||
best-win))
|
||
|
||
(defun my--non-minibuffer-window-count (frame)
|
||
"Return number of non-minibuffer windows in FRAME."
|
||
(let ((count 0))
|
||
(dolist (w (window-list frame))
|
||
(unless (window-minibuffer-p w)
|
||
(setq count (1+ count))))
|
||
count))
|
||
|
||
(defun my/toggle-buffer-to-frame ()
|
||
"Toggle the current buffer between `my/main-frame' and a new frame."
|
||
(interactive)
|
||
(unless (frame-live-p my/main-frame)
|
||
(setq my/main-frame (selected-frame)))
|
||
|
||
(let* ((buf (current-buffer))
|
||
(old-frame (selected-frame))
|
||
(old-win (selected-window)))
|
||
(if (eq old-frame my/main-frame)
|
||
;; === Move from main frame to new frame ===
|
||
(let ((new-frame (make-frame '((name . "Toggled Buffer")))))
|
||
(with-selected-frame new-frame
|
||
(switch-to-buffer buf))
|
||
(select-frame-set-input-focus new-frame)
|
||
(with-selected-frame old-frame
|
||
(with-selected-window old-win
|
||
(if (> (my--non-minibuffer-window-count old-frame) 1)
|
||
(delete-window old-win)
|
||
(switch-to-buffer (other-buffer buf t))))))
|
||
;; === Move from secondary frame back to main ===
|
||
(with-selected-frame my/main-frame
|
||
(let ((target (my--rightmost-non-minibuffer-window my/main-frame)))
|
||
(if target
|
||
(with-selected-window target
|
||
(condition-case nil
|
||
(let ((new-win (split-window-right)))
|
||
(select-window new-win)
|
||
(switch-to-buffer buf))
|
||
(error ;; too small to split, just reuse the window
|
||
(switch-to-buffer buf))))
|
||
;; fallback: no window found, just use current one
|
||
(switch-to-buffer buf))))
|
||
|
||
;; Close secondary frame or just the window
|
||
(with-selected-frame old-frame
|
||
(if (= (my--non-minibuffer-window-count old-frame) 1)
|
||
(delete-frame old-frame)
|
||
(when (window-live-p old-win)
|
||
(delete-window old-win)))))))
|
||
|
||
;; >=== ExoKortex System ===<
|
||
|
||
(defvar gortium/org-repo "~/ExoKortex/2-Areas/Meta-Planning/Projects/"
|
||
"Path to your main Org repository where real .org files are stored.")
|
||
|
||
(defvar gortium/projects-root "~/ExoKortex/1-Projects/"
|
||
"Root directory where new projects will be created.")
|
||
|
||
(defun gortium/create-project (project-name)
|
||
"Create a new project with PROJECT-NAME in `gortium/projects-root`.
|
||
The .org file is created in `gortium/org-repo` and symlinked into the project folder."
|
||
(interactive "sProject name: ")
|
||
(let* ((project-dir (expand-file-name project-name gortium/projects-root))
|
||
(org-file (concat project-name ".org"))
|
||
(org-real (expand-file-name org-file gortium/org-repo))
|
||
(org-link (expand-file-name org-file project-dir)))
|
||
(make-directory project-dir t)
|
||
(unless (file-exists-p org-real)
|
||
(with-temp-file org-real
|
||
(insert (format "#+TITLE: %s\n#+DATE: %s\n\n" project-name (format-time-string "%Y-%m-%d")))))
|
||
(make-symbolic-link org-real org-link t)
|
||
(find-file org-link)))
|
||
|
||
(defun gortium/create-symlinked-org (file-name)
|
||
"Create a symlinked org FILE-NAME in current dir, real file in `gortium/org-repo`."
|
||
(interactive "sOrg file name (without .org): ")
|
||
(let* ((org-file (concat file-name ".org"))
|
||
(org-real (expand-file-name org-file gortium/org-repo))
|
||
(org-link (expand-file-name org-file default-directory)))
|
||
(unless (file-exists-p org-real)
|
||
(with-temp-file org-real
|
||
(insert (format "#+TITLE: %s\n#+DATE: %s\n\n" file-name (format-time-string "%Y-%m-%d")))))
|
||
(make-symbolic-link org-real org-link t)
|
||
(find-file org-link)))
|
||
|
||
(defun gortium/convert-marked-org-to-symlink ()
|
||
"Convert all marked org files in Dirvish/Dired to symlinks in `gortium/org-repo`."
|
||
(interactive)
|
||
(let ((files (dired-get-marked-files)))
|
||
(dolist (file-path files)
|
||
(when (and (file-regular-p file-path)
|
||
(string= (file-name-extension file-path) "org"))
|
||
(let* ((file-name (file-name-nondirectory file-path))
|
||
(org-real (expand-file-name file-name gortium/org-repo)))
|
||
(unless (file-exists-p org-real)
|
||
(rename-file file-path org-real))
|
||
(make-symbolic-link org-real file-path t)
|
||
(message "Converted %s to symlink -> %s" file-path org-real))))))
|