#+TITLE: Gortium Doom Emacs Configuration
#+PROPERTY: header-args:emacs-lisp :tangle config.el

* Doom Emacs

Whenever you reconfigure a package, make sure to wrap your config in an
~after!~ block, otherwise Doom's defaults may override your settings. E.g.

#+BEGIN_SRC elisp :tangle no
  (after! PACKAGE
    (setq x y))
#+END_SRC

The exceptions to this rule:

  - Setting file/directory variables (like ~org-directory~)
  - Setting variables which explicitly tell you to set them before their
    package is loaded (see ~C-h v VARIABLE~ to look up their documentation).
  - Setting doom variables (which start with ~doom-~ or ~+~).

Here are some additional functions/macros that will help you configure Doom.

- ~load!~ for loading external =*.el= files relative to this one
- ~use-package!~ for configuring packages
- ~after!~ for running code after a package has loaded
- ~add-load-path!~ for adding directories to the ~load-path~, relative to
  this file. Emacs searches the ~load-path~ when you load packages with
  ~require~ or ~use-package~.
- ~map!~ for binding new keys

To get information about any of these functions/macros, move the cursor over
the highlighted symbol at press 'K' (non-evil users must press ~C-c c k~).
This will open documentation for it, including demos of how they are used.
Alternatively, use ~C-h o~ to look up a symbol (functions, variables, faces,
etc).

You can also try ~gd~ (or ~C-c c d~) to jump to their definition and see how
they are implemented.

** Config File Header

#+begin_src emacs-lisp
;;; $DOOMDIR/config.el -*- lexical-binding: t; -*-
#+end_src

** User Info

Place your private configurations. It is optional.

#+begin_src emacs-lisp
(setq user-full-name "Thierry Pouplier"
      user-mail-address "tpouplier@tdnde.com")
#+end_src

** Fonts

 Doom exposes five (optional) variables for controlling fonts in Doom:
 - ~doom-font~ -- the primary font to use
 - ~doom-variable-pitch-font~ -- a non-monospace font (where applicable)
 - ~doom-big-font~ -- used for ~doom-big-font-mode~; use this for
   presentations or streaming.
 - ~doom-symbol-font~ -- for symbols
 - ~doom-serif-font~ -- for the ~fixed-pitch-serif~ face

 See ~C-h v doom-font~ for documentation and more examples of what they
 accept. For example:

#+begin_src emacs-lisp
(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))
#+end_src

** Theme

If you or Emacs can't find your font, use ~M-x describe-font~ to look them
up, ~M-x eval-region~ to execute elisp code, and ~M-x doom/reload-font~ to
refresh your font settings. If Emacs still can't find your font, it likely
wasn't installed correctly. Font issues are rarely Doom issues!

There are two ways to load a theme. Both assume the theme is installed and
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)
#+end_src

** Bookmark

#+begin_src emacs-lisp
(setq bookmark-save-flag 1)
#+end_src

** Buffer auto reload
Detect changes from disk and reload buffers
To prevent saving a old buffer content after a git branch switch!

#+begin_src emacs-lisp
(setq global-auto-revert-mode 1)
#+end_src

** Minimize window

#+begin_src emacs-lisp
(map! :leader
      (:prefix ("w" . "window")
       :desc "Minimize window" "O" #'minimize-window))
#+end_src

** Super-Save

#+begin_src emacs-lisp
;; 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))
#+end_src

** Fix

#+begin_src emacs-lisp
;; Fixes..
(require 'cl-lib)
(unless (fboundp 'find-if)
  (defalias 'find-if #'cl-find-if))
(unless (fboundp 'getf)
  (defalias 'getf #'cl-getf))
#+end_src

* Org Mode

If you use ~org~ and don't want your org files in the default location below,
change ~org-directory~. It must be set before org loads!

** Static Paths

#+begin_src emacs-lisp
(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$")
 )
#+end_src

** Refiling

#+begin_src emacs-lisp
(after! org
  (setq org-refile-targets
        '((nil :maxlevel . 6)            ;; Current buffer
          (org-agenda-files :maxlevel . 6)))  ;; All agenda files
)
#+end_src

** Headers

#+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

  ;; 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))
)
#+end_src

** Clock-in

#+begin_src emacs-lisp
(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))
)
#+end_src

** Org Agenda

#+begin_src emacs-lisp
(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")))
           )
          )
        )
)
#+end_src

** Rust

#+begin_src emacs-lisp
(after! org
  ;; Rust code block setting
  (setq rustic-babel-display-error-popup nil)
)
#+end_src

** Latex

#+begin_src emacs-lisp
(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" "$" "$$" "\\(" "\\["))
   )
)
#+end_src

** Org Capture

#+begin_src emacs-lisp
(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)
     )
  )
)
#+end_src
* PlantUML
#+begin_src elisp
;; Enable plantuml-mode for PlantUML files
(add-to-list 'auto-mode-alist '("\\.plantuml\\'" . plantuml-mode))
#+end_src

** Exemple:

#+begin_src plantuml :tangle no
left to right direction
actor "Food Critic" as fc
rectangle Restaurant {
  usecase "Eat Food" as UC1
  usecase "Pay for Food" as UC2
  usecase "Drink" as UC3
}
fc --> UC1
fc --> UC2
fc --> UC3
#+end_src

#+RESULTS:
[[file:/tmp/babel-rO7BdB/plantuml-3SghXt.png]]

* Org Roam

** Static Path

#+begin_src emacs-lisp
(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")
#+end_src

** Custom config

#+begin_src emacs-lisp
(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)
          )
        )
  )
#+end_src

** Custom function

*** New Entry

#+begin_src emacs-lisp
;; 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))))
#+end_src

*** Connecteam

#+begin_src emacs-lisp
;; 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)
#+end_src

*** Old config

#+begin_src emacs-lisp :tangle no
#+end_;; (use-package org-journal
;;   :defer t
;;   :custom
;;   (org-journal-dir "~/notes/journal/")
;;   (org-journal-enable-agenda-integration t))
#+end_src

* IDE

** Dap Mode

#+begin_src emacs-lisp
(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")))
#+end_src

** Dape

#+begin_src emacs-lisp :tangle no
;; (use-package dape
;;   :preface
;;   ;; By default dape shares the same keybinding prefix as `gud'
;;   ;; If you do not want to use any prefix, set it to nil.
;;   ;; (setq dape-key-prefix "\C-x\C-a")

;;   :hook
;;   ;; Save breakpoints on quit
;;   (kill-emacs . dape-breakpoint-save)
;;   ;; Load breakpoints on startup
;;   (after-init . dape-breakpoint-load)

;;   :custom
;;   ;; Turn on global bindings for setting breakpoints with mouse
;;   (dape-breakpoint-global-mode +1)

;;   ;; Info buffers to the right
;;   ;; (dape-buffer-window-arrangement 'right)
;;   ;; Info buffers like gud (gdb-mi)
;;   ;; (dape-buffer-window-arrangement 'gud)
;;   ;; (dape-info-hide-mode-line nil)

;;   ;; Projectile users
;;   (dape-cwd-function 'projectile-project-root)

;;   :config
;;   ;; Pulse source line (performance hit)
;;   (add-hook 'dape-display-source-hook 'pulse-momentary-highlight-one-line)

;;   ;; Save buffers on startup, useful for interpreted languages
;;   (add-hook 'dape-start-hook (lambda () (save-some-buffers t t)))

;;   ;; Kill compile buffer on build success
;;   ;; (add-hook 'dape-compile-hook 'kill-buffer)
;;   )

;; ;; For a more ergonomic Emacs and `dape' experience
;; (use-package repeat
;;   :custom
;;   (repeat-mode +1))
#+end_src

** LSP

#+begin_src emacs-lisp
;; LSP BABY
(after! lsp-mode
  (setq lsp-csharp-server-path "~/.local/tools/omnisharp-mono/omnisharp.sh")
  )
#+end_src

** Scroll-Margin

#+begin_src emacs-lisp
;; To keep my eye in the center of the screen while scrolling. Like in my nvim x)
(setq scroll-margin 10)
#+end_src

** Visual Line Mode

#+begin_src emacs-lisp
;; 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))
#+end_src

** Org-phscroll
Load org-phscroll after org. This allow horizontal scroll in tables. (No line wrap in table)

#+begin_src emacs-lisp
(use-package! org-phscroll
  :after org)
#+end_src

** Spellchecking

*** Ispell

#+begin_src emacs-lisp
;; 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"))
#+end_src

*** Writegood Mode

#+begin_src emacs-lisp
;; Let me write like a broken engineer, thank you.
(add-hook 'writegood-mode-hook 'writegood-passive-voice-turn-off)
#+end_src

** Drag-Stuff

#+begin_src emacs-lisp
;; 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))
#+end_src

** Line Number Type

This determines the style of line numbers in effect. If set to ~nil~, line
numbers are disabled. For relative line numbers, set this to ~relative~.

#+begin_src emacs-lisp
(setq display-line-numbers-type 'relative)
#+end_src

** Evile-Escape (JK)

#+begin_src emacs-lisp
;; 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)
  )
#+end_src

** Nix-Mode

#+begin_src emacs-lisp
(after! nix-mode
  (setq lsp-nix-nil-formatter ["nixpkgs-fmt"])
  (add-hook 'nix-mode-hook #'lsp!))

(after! lsp-mode
  (setq lsp-nix-server 'nil))
#+end_src

** KRL Mode

#+begin_src emacs-lisp
;; 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)
#+end_src

** Hledger Mode

#+begin_src emacs-lisp
(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))
  )
#+end_src

** Fragtog

#+begin_src emacs-lisp
;; 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)
#+end_src

** Age
Allow me to edit age file directly in emacs buffer.

#+begin_src emacs-lisp
(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))
#+end_src

#+begin_src emacs-lisp
(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)
#+end_src

** Passage
Allow retrieval of password from age file formatted like passwordstore

#+begin_src emacs-lisp
(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"))
  )
#+end_src
* Terminal

** EEE

#+begin_src emacs-lisp
;; TUI tools in emacs
(after! eee
  (setq ee-terminal-command "kitty")
  )
#+end_src

** Vterm

#+begin_src emacs-lisp
;; 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))))
#+end_src

* AI

** GPTel

#+begin_src emacs-lisp
;; 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))
#+end_src

** +MCP+

#+begin_src emacs-lisp :tangle no
#+end_(use-package! mcp
  :ensure t
  :after gptel
  ;; :custom (mcp-hub-servers
  ;;          `(
  ;;            ("serena" . (
  ;;                         :command "uvx"
  ;;                         :args ("--from", "git+https://github.com/oraios/serena", "serena-mcp-server")))
  ;;            ("codemcp" . (:command "uvx"
  ;;                          :args ("--from", "git+https://github.com/ezyang/codemcp@prod", "codemcp")))
  ;;            ))
  :config (require 'mcp-hub)
  :hook (after-init . mcp-hub-start-all-server))
#+end_src

** +Emigo+

#+begin_src emacs-lisp :tangle no
Emigo - Aidermacs 2.0
(use-package! emigo
  :config
  (emigo-enable) ;; Starts the background process automatically
  :custom
  ;; Encourage using OpenRouter with Deepseek
  (emigo-model "openrouter/deepseek/deepseek-chat-v3-0324:free")
  (emigo-base-url "https://openrouter.ai/api/v1")
  (emigo-api-key (auth-source-passage-get 'secret "openrouter")))

;; Enable line wrapping in Emigo chat buffers
(add-hook 'emigo-mode-hook
          (lambda ()
            (setq-local truncate-lines nil)
            (visual-line-mode 1)))

;; Python venv for emacs (just emigo req for now)
(use-package pyvenv
  :after python
  :config
  (pyvenv-activate "~/.venvs/emacs"))
#+end_src

** +Aidermacs+

#+begin_src emacs-lisp :tangle no
;; Aidermacs - aider for emacs - peer progrmming
(use-package! aidermacs
  :bind (("C-c a" . aidermacs-transient-menu))
  :config
  ;; Set API_KEY in .bashrc, that will automatically picked up by aider or in elisp
  ;; (setenv "ANTHROPIC_API_KEY" "sk-...")
  ;; defun my-get-openrouter-api-key yourself elsewhere for security reasons
  (setenv "OPENROUTER_API_KEY" (auth-source-passage-get 'secret "openrouter"))
  :custom
  ;; See the Configuration section below
  (aidermacs-use-architect-mode t)
  ;; Default model used for all modes unless overridden
  (aidermacs-default-model "deepseek/deepseek-chat-v3-0324:free")
  ;; Optional: Set specific model for architect reasoning
  (aidermacs-architect-model "deepseek/deepseek-r1-0528-qwen3-8b:free")
  ;; Optional: Set specific model for code generation
  (aidermacs-editor-model "deepseek/deepseek-chat-v3-0324:free")
  ;; default to nil
  (aidermacs-weak-model "deepseek/deepseek-chat-v3-0324:free")
  ;; Use vterm backend (default is comint)
  (aidermacs-backend 'vterm)
  ;; Multiline keybind
  (aidermacs-vterm-multiline-newline-key "S-<return>"))
#+end_src

** +Ellama+

#+begin_src emacs-lisp :tangle no
(use-package ellama
  :ensure t
  :bind ("C-c e" . ellama)
  ;; send last message in chat buffer with C-c C-c
  :hook (org-ctrl-c-ctrl-c-final . ellama-chat-send-last-message)
  :init
  (setq llm-warn-on-nonfree nil)
  (setopt ellama-auto-scroll t)
  (require 'llm-ollama)
  (setopt ellama-provider
          (make-llm-ollama
           ;; this model should be pulled to use it
           ;; value should be the same as you print in terminal during pull
           :chat-model "deepseek-r1:1.5b"
           :embedding-model "nomic-embed-text:v1.5"))
  (require 'llm-openai)
  (setopt ellama-providers
          '(
            ("deepseek-r1" . (make-llm-openai-compatible
                              :key (auth-source-passage-get 'secret "openrouter")
                              :url "https://openrouter.ai/api/v1"
                              :chat-model "deepseek/deepseek-chat-v3-0324:free"))
            ("gemini-2.0-flash" . (make-llm-openai-compatible
                                   :key (auth-source-passage-get 'secret "openrouter")
                                   :url "https://openrouter.ai/api/v1"
                                   :chat-model "google/gemini-2.0-flash-exp:free"))
            )
          )
  :config
  ;; show ellama context in header line in all buffers
  (ellama-context-header-line-global-mode +1)
  ;; show ellama session id in header line in all buffers
  (ellama-session-header-line-global-mode +1))
#+end_src

** +Elisa+

#+begin_src emacs-lisp :tangle no
(use-package elisa
  :init
  (setopt elisa-limit 5)
  ;; reranker increases answer quality significantly
  (setopt elisa-reranker-enabled nil)
  ;; prompt rewriting may increase quality of answers
  ;; disable it if you want direct control over prompt
  (setopt elisa-prompt-rewriting-enabled t)
  (require 'llm-ollama)
  ;; gemma 2 works very good in my use cases
  ;; it also boasts strong multilingual capabilities
  ;; (setopt elisa-chat-provider
  ;; 	  (make-llm-ollama
  ;; 	   :chat-model "gemma2:9b-instruct-q6_K"
  ;; 	   :embedding-model "snowflake-arctic-embed2"
  ;; 	   ;; set context window to 8k
  ;; 	   :default-chat-non-standard-params '(("num_ctx" . 8192))))
  ;;
  ;; qwen 2.5 3b works good in my test cases and provide longer context
  ;; (setopt elisa-chat-provider
  ;;         (make-llm-ollama
  ;;          :chat-model "qwen2.5:3b"
  ;;          :embedding-model "snowflake-arctic-embed2"
  ;;          :default-chat-temperature 0.1
  ;;          :default-chat-non-standard-params '(("num_ctx" . 32768))))
  (require 'llm-ollama)
  (setopt elisa-chat-provider
          (make-llm-ollama
           ;; this model should be pulled to use it
           ;; value should be the same as you print in terminal during pull
           :chat-model "deepseek-r1:1.5b"
           :embedding-model "nomic-embed-text:v1.5"))
  (require 'llm-openai)
  (setopt elisa-chat-provider
          (make-llm-openai-compatible
           :key (auth-source-passage-get 'secret "openrouter")
           :url "https://openrouter.ai/api/v1"
           :chat-model "deepseek/deepseek-chat-v3-0324:free")
          llm-warn-on-nonfree nil)
  (setopt elisa-embeddings-provider (make-llm-ollama :embedding-model "nomic-embed-text:v1.5"))
  ;; this embedding model has stong multilingual capabilities
  ;; (setopt elisa-embeddings-provider (make-llm-ollama :embedding-model "snowflake-arctic-embed2"))
  ;; enable batch embeddings for faster processing
  (setopt elisa-batch-embeddings-enabled t)
  :config
  ;; searxng works better than duckduckgo in my tests
  (setopt elisa-web-search-function 'elisa-search-searxng))
#+end_src

** +Org-AI+

#+begin_src emacs-lisp :tangle no
Org-AI
(setq org-ai-openai-api-token "")

(use-package! org-ai
  :ensure t
  :commands (org-ai-mode
             org-ai-global-mode)
  :init
  (add-hook 'org-mode-hook #'org-ai-mode) ; enable org-ai in org-mode
  (org-ai-global-mode) ; installs global keybindings on C-c M-a
  :config
  (setq org-ai-auto-fill t) ;; for automatic word wrap in fill-column length
  (setq org-ai-jump-to-end-of-block nil) ;; to make it not auto-jump to the end of the respose
  ;; Select your ai model
  ;; (setq org-ai-default-chat-model "gpt-3.5-turbo")
  (setq org-ai-default-chat-model "gpt-4o-mini")
  ;; (setq org-ai-default-chat-model "gpt-4")
  (org-ai-install-yasnippets)) ; if you are using yasnippet and want `ai` snippets
#+end_src

* Diff HL Mode

#+begin_src emacs-lisp
(diff-hl-mode +1)
#+end_src

* Tramp

#+begin_src emacs-lisp
;; 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)
#+end_src

* Dirvish

#+begin_src emacs-lisp
;; 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")
           )))
#+end_src

* Consult-ripgrep

#+begin_src emacs-lisp
;; Enable previewing of surrounding lines in consult-ripgrep
(setq consult-ripgrep-preview t)
#+end_src

* Weather

#+begin_src emacs-lisp
;; 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)
#+end_src

* Org-Modern

#+begin_src emacs-lisp
;; 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
          )
        )
  )
#+end_src

* Cursor Beacon

#+begin_src emacs-lisp
;; Flash the point (cursor) when moving between window
(use-package! beacon
  :config
  (beacon-mode 1))
#+end_src

* Trash

#+begin_src emacs-lisp
;; 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/")
#+end_src

* Email

** Org-Msg

#+begin_src emacs-lisp
(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")
  )
#+end_src

** Mu4e

#+begin_src emacs-lisp
(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)
  )
#+end_src

* IRC
** Circe

#+begin_src emacs-lisp
(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")
           )))
  )
#+end_src

* Org-Noter

#+begin_src emacs-lisp :tangle no
;; Org-Noter
(use-package! org-noter
  :after org  ;; optional, ensures org is loaded first
  :config
  (setq org-noter-notes-search-path '("~/org/"))
  (setq org-noter-default-notes-file-names '("notes.org"))
  (setq org-noter-notes-window-location 'vertical-split)
  (setq org-noter-notes-window-behavior '(start scroll))
  (setq org-noter-doc-split-fraction '(0.5 . 0.8))
  (setq org-noter-always-create-frame nil)
  (setq org-noter-hide-other t)
  (setq org-noter-kill-frame-at-session-end nil))
#+end_src

** TODO Fix noter if needed

* PDF-Tools

#+begin_src emacs-lisp
;; Enable midnight mode by default for PDF files
(after! pdf-tools
  (add-hook 'pdf-view-mode-hook (lambda () (pdf-view-midnight-minor-mode 1))))
#+end_src

* Custom Emacs-Everywhere for Hyprland

#+begin_src emacs-lisp
;; 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))))
#+end_src

* Custom function refile to datetree

#+begin_src emacs-lisp
;;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)))))
#+end_src

* Custom function ediff org fold

#+begin_src emacs-lisp
;; 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)
#+end_src

* Custom function for reshedulling

#+begin_src emacs-lisp
(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))
#+end_src

* Custom function Open link in other frame

#+begin_src emacs-lisp
;; 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)
#+end_src

* Custom function to open current buffer to new frame

#+begin_src emacs-lisp
(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)))))))
#+end_src

* Custom function for ExoKortex

#+begin_src emacs-lisp
;; >=== 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))))))
#+end_src

* Custom function to refile task to dailies

#+begin_src elisp
(defun my/refile-to-roam-daily ()
  "Refile the current subtree under the 'Journal' headline in today's daily note.
This robust version uses org-paste-subtree to ensure the refiled task
is a direct child of 'Journal' with the correct headline level."
  (interactive)
  (require 'org-roam-dailies)

  (let* ((filename (format-time-string "%Y-%m-%d.org"))
         (daily-file (expand-file-name filename org-roam-dailies-directory))
         (headline "Journal"))

    ;; Step 1: Ensure the target file exists.
    (unless (file-exists-p daily-file)
      (message "Creating today's daily note...")
      (with-temp-file daily-file
        (insert (format-time-string "#+title: %Y-%m-%d\n\n* %s\n" headline)))
      (message "Creating today's daily note... done."))

    ;; Step 2: Cut the subtree.
    (org-cut-subtree)

    ;; Step 3: Find the target and paste with the correct level.
    (with-current-buffer (find-file-noselect daily-file)
      (save-excursion
        (goto-char (point-min))
        ;; Find the target parent headline.
        (when (re-search-forward (concat "^\\*+ " (regexp-quote headline) "\\b") nil t)
          ;; Get the level of the parent ("* Journal" is level 1).
          (let ((parent-level (org-current-level)))
            ;; Go to the end of the parent headline line.
            (end-of-line)
            ;; Insert a newline to make space for the new child.
            (insert "\n")
            ;; Paste the subtree, forcing its level to be parent-level + 1.
            (org-paste-subtree (+ 1 parent-level))))))

    (message "Task refiled under '%s' in today's daily note." headline)))
#+end_src

* +Whisper+

#+begin_src emacs-lisp :tangle no
(use-package! whisper
  :load-path "/home/tpouplier/3-Resources/whisper.el"
  :bind ("C-H-r" . whisper-run)
  :config
  (setq whisper-install-directory "/tmp/"
        whisper-model "base"
        whisper-language "auto"
        whisper-translate nil
        whisper-use-threads (/ (num-processors) 2))
  (setq whisper--ffmpeg-input-device "pulse:whisper-combined.monitor"))
#+end_src

* +Emacs-Everywhere+

#+begin_src emacs-lisp :tangle no
(after! emacs-everywhere
  (setq emacs-everywhere-window-focus-command (list "hyprctl" "dispatch" "focuswindow" "address:%w"))
  (setq emacs-everywhere-app-info-function #'emacs-everywhere--app-info-linux-hyprland)

  (require 'json)
  (defun emacs-everywhere--app-info-linux-hyprland ()
    "Return information on the current active window, on a Linux Hyprland session."
    (let* ((json-string (emacs-everywhere--call "hyprctl" "-j" "activewindow"))
           (json-object (json-read-from-string json-string))
           (window-id (cdr (assoc 'address json-object)))
           (app-name (cdr (assoc 'class json-object)))
           (window-title (cdr (assoc 'title json-object)))
           (window-geometry (list (aref (cdr (assoc 'at json-object)) 0)
                                  (aref (cdr (assoc 'at json-object)) 1)
                                  (aref (cdr (assoc 'size json-object)) 0)
                                  (aref (cdr (assoc 'size json-object)) 1))))
      (make-emacs-everywhere-app
       :id window-id
       :class app-name
       :title window-title
       :geometry window-geometry)))
  )
#+end_src

* +Smerge+

#+begin_src emacs-lisp :tangle no
;; Smerge remap in magit
Not needed
(after! magit
  (map! :after magit
        :leader
        :desc "Keep upper" "m u" #'magit-smerge-keep-upper
        :desc "Keep lower" "m l" #'magit-smerge-keep-lower
        :desc "Keep all"   "m a" #'magit-smerge-keep-all))

;; Smerge function to keep one side for the whole file
;; Not working.. used this instead:
;; git checkout --theirs <filename>
;; or
;; git checkout --ours <filename>
(defun gortium/smerge-keep-lower-all ()
  "Keep the lower part in all smerge conflict hunks in the current buffer."
  (interactive)
  (save-excursion
    (goto-char (point-min))
    (while (not (null (smerge-at-markers)))
      (smerge-keep-lower)
      (ignore-errors (smerge-next)))))

(defun gortium/smerge-keep-upper-all ()
  "Keep the upper part in all smerge conflict hunks in the current buffer."
  (interactive)
  (save-excursion
    (goto-char (point-min))
    (while (not (null (smerge-at-markers)))
      (smerge-keep-upper)
      (ignore-errors (smerge-next)))))
#+end_src

* +Excalidraw+

#+begin_src emacs-lisp :tangle no
Need chrome... :(
(use-package! org-excalidraw
  :config
  (setq org-excalidraw-directory "~/org/drawings"))
#+end_src

* +Org-Edna+

#+begin_src emacs-lisp :tangle no
;; Org-edna for advanced task dependencies
(use-package! org-edna
  :config
  (setq org-edna-use-inheritance t)
  (map! :after org
        :map org-mode-map
        :localleader
        :desc "Edit Edna rules" "E" #'org-edna-edit)
  (org-edna-mode)
  )
#+end_src

