diff --git a/README.org b/README.org index 6c0194d..3e2d16c 100644 --- a/README.org +++ b/README.org @@ -2,14 +2,14 @@ :ID: 75f35b3b-f46f-42b6-ac4b-3988f15c46e1 :END: #+title: Gortium Dotfiles -#+subtitle: My attempt at having my computer do what I want, not the contrary +#+subtitle: My attempt at having my computer do what I want, the way I want it. * Welcome I use stow to deploy all this to where it need to go (mostly ~/.confg). * Apps -** emacs (Doom) ** nvim +** emacs (Doom) ** btop ** hyprland ** kitty @@ -21,3 +21,6 @@ I use stow to deploy all this to where it need to go (mostly ~/.confg). ** wofi ** yazi ** zsh +* Archives +** I3 +** Terminator diff --git a/doom/.config/doom/README.org b/doom/.config/doom/README.org index f679ee9..dd07dba 100644 --- a/doom/.config/doom/README.org +++ b/doom/.config/doom/README.org @@ -6,7 +6,7 @@ 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 +#+BEGIN_SRC emacs-lisp :tangle no (after! PACKAGE (setq x y)) #+END_SRC @@ -52,6 +52,9 @@ Place your private configurations. It is optional. user-mail-address "tpouplier@tdnde.com") #+end_src +#+RESULTS: +: tpouplier@tdnde.com + ** Fonts Doom exposes five (optional) variables for controlling fonts in Doom: @@ -104,6 +107,7 @@ To prevent saving a old buffer content after a git branch switch! #+end_src ** Minimize window +My current workflow consist in having the 3-5 files I work on open in vertical split and enlarging one at the time. but sometime I want to see 2 side by side. So I first open all windows with equal size, then collapse all but the 2 I want with this keymap. It's not perfect and I'm thinking about of a buffer based workflow that would give me more screen space. #+begin_src emacs-lisp (map! :leader @@ -111,6 +115,19 @@ To prevent saving a old buffer content after a git branch switch! :desc "Minimize window" "O" #'minimize-window)) #+end_src +** Ultra-scroll +#+begin_src emacs-lisp +(use-package! ultra-scroll + :init + (setq scroll-margin 0 ; Required for ultra-scroll + scroll-conservatively 101 ; Prevents jumping to center + ultra-scroll-acceleration nil ; Essential for Miryoku keys + ultra-scroll-mouse-scale 0.1 ; Lower for higher precision + pixel-scroll-precision-large-scroll-height 20) + :config + (ultra-scroll-mode 1)) +#+end_src + ** Super-Save #+begin_src emacs-lisp @@ -147,11 +164,12 @@ change ~org-directory~. It must be set before org loads! #+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-todo-file "~/ExoKortex/2-Areas/Meta-Planning/Org/Core/master_list.org" + +org-capture-notes-file "~/ExoKortex/2-Areas/Meta-Planning/Org/Core/master_list.org" + +org-capture-journal-file "~/ExoKortex/2-Areas/Meta-Planning/Org/Core/master_list.org" + +org-capture-emails-file "~/ExoKortex/2-Areas/Meta-Planning/Org/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$") @@ -262,7 +280,7 @@ change ~org-directory~. It must be set before org loads! (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"))) + ("ASSIGNEE_ALL" . "Thierry.P Cath.F Martin.K Michel.B Gabriel.C Silvie.L George.G Miguel.A Réjean.S Dominique Adnane Helper1 Helper2"))) (setq org-stuck-projects '("TODO=\"PROJ\"" ("NEXT") nil "")) @@ -275,6 +293,7 @@ change ~org-directory~. It must be set before org loads! org-agenda-skip-timestamp-if-done nil org-journal-enable-agenda-integration t org-log-done 'time + org-agenda-span 'day org-log-into-drawer t org-log-redeadline 'time org-log-reschedule 'time @@ -303,37 +322,6 @@ change ~org-directory~. It must be set before org loads! ) ) ;; 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 @@ -378,7 +366,7 @@ change ~org-directory~. It must be set before org loads! ("p" . "Personal commands") ("wg" "GTD view" ( - (agenda "" ((org-agenda-span 'day))) + (agenda "" ((org-agenda-span 'week))) (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))) @@ -391,7 +379,7 @@ change ~org-directory~. It must be set before org loads! ) ("pg" "GTD view" ( - (agenda "" ((org-agenda-span 'day))) + (agenda "" ((org-agenda-span 'week))) (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))) @@ -529,7 +517,7 @@ change ~org-directory~. It must be set before org loads! "* %? \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") + ("j" "Journal" entry (file+olp +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: /") @@ -538,13 +526,21 @@ change ~org-directory~. It must be set before org loads! ("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) + ("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 +** +Org Superstar mode+ +org-modern instead + +#+begin_src emacs-lisp :tangle no +(add-hook 'org-mode-hook (lambda () (org-superstar-mode 1))) +#+end_src + * PlantUML -#+begin_src elisp +#+begin_src emacs-lisp ;; Enable plantuml-mode for PlantUML files (add-to-list 'auto-mode-alist '("\\.plantuml\\'" . plantuml-mode)) #+end_src @@ -565,7 +561,232 @@ fc --> UC3 #+end_src #+RESULTS: -[[file:/tmp/babel-rO7BdB/plantuml-3SghXt.png]] +[[/tmp/babel-pg1Nry/plantuml-SHtP1g.png][/tmp/babel-pg1Nry/plantuml-SHtP1g.png]] + +* Elgantt +#+begin_src emacs-lisp +(require 'cl-lib) +(require 'dash) +(unless (fboundp 'first) (defalias 'first #'car)) + +;; Clear rules to ensure the new global color logic takes effect immediately +(setq elgantt--display-rules nil) + +(defface gortium/elgantt-weekend-face + '((t (:background "#332222" :extend nil))) + "Face for weekend vertical columns in ElGantt.") + +(defun gortium/elgantt-draw-weekend-guides () + "Draw weekend guides for the ENTIRE buffer once to prevent scroll lag." + (interactive) + (when (derived-mode-p 'elgantt-mode) + (let* ((inhibit-modification-hooks t) + (header-line-1 (save-excursion + (goto-char (point-min)) + (buffer-substring-no-properties (line-beginning-position) (line-end-position)))) + (col-indices '()) + (search-pos 0)) + + (save-excursion + (save-restriction + (widen) + ;; 1. Clear ALL weekend overlays in the entire buffer + (remove-overlays (point-min) (point-max) 'gortium-weekend t) + + ;; 2. Parse header once to find column indexes (Fast) + (while (string-match "|[[:space:]]*\\([[:alpha:]]+\\)[[:space:]]+\\([0-9]\\{4\\}\\)" header-line-1 search-pos) + (let* ((month-start-col (match-beginning 0)) + (month-name (match-string 1 header-line-1)) + (year (string-to-number (match-string 2 header-line-1))) + (month-num (gortium/internal--month-to-num month-name)) + (next-pipe (string-match "|" header-line-1 (1+ month-start-col))) + (month-width (if next-pipe (- next-pipe month-start-col 1) 31))) + (dotimes (d month-width) + (let* ((day (1+ d)) + (time (condition-case nil (encode-time 0 0 12 day month-num year) (error nil)))) + (when time + (let ((dow (nth 6 (decode-time time))) + (actual-col (+ month-start-col 1 d))) + (when (member dow '(0 6)) + (push actual-col col-indices)))))) + (setq search-pos (or next-pipe (length header-line-1))))) + + ;; 3. Apply to the WHOLE buffer line by line + (unless (null col-indices) + (goto-char (point-min)) + (forward-line 2) ;; Skip headers + (while (not (eobp)) + (let ((line-end (line-end-position))) + (dolist (col col-indices) + (move-to-column col) + (let ((p (point))) + ;; Ensure we are still on the same line and at the correct column + (when (and (< p line-end) (= (current-column) col)) + (let ((ov (make-overlay p (1+ p)))) + (overlay-put ov 'face 'gortium/elgantt-weekend-face) + (overlay-put ov 'gortium-weekend t) + (overlay-put ov 'priority 100) + (overlay-put ov 'evaporate t)))))) + (forward-line 1))))) + (message "Weekend guides rendered for the whole buffer.")))) + +;; Run it only once when the buffer is loaded +(add-hook 'elgantt-mode-hook #'gortium/elgantt-draw-weekend-guides) + +(use-package! elgantt + :commands (elgantt-open elgantt-open-current-org-file) + :config + ;; --- 1. Environment & UI --- + (add-hook 'elgantt-mode-hook + (lambda () + (setq-local org-phscroll-mode nil) + (setq-local image-roll-mode nil) + (setq truncate-lines t))) + + (setq elgantt-start-date "2026-01-01") + + (setq elgantt-header-column-offset 40 + elgantt-header-type 'outline + elgantt-show-header-depth t + elgantt-insert-blank-line-between-top-level-header t + elgantt-startup-folded nil + elgantt-draw-overarching-headers nil + elgantt-scroll-to-current-month-at-startup t) + + (setq elgantt-user-set-color-priority-counter 0) + + ;; --- 2. Effort Rule (With Weekend Extension) --- + (elgantt-create-display-rule draw-scheduled-to-effort-end + :parser ((override-color . ((when-let ((colors (org-entry-get (point) "ELGANTT-COLOR"))) + (split-string colors " ")))) + (elgantt-effort . ((org-entry-get (point) "EFFORT"))) + (wknd-days . ((when-let ((val (org-entry-get (point) "WEEKEND_DAYS"))) + (string-to-number val))))) + :args (elgantt-scheduled elgantt-effort elgantt-org-id) + :body ((when (and elgantt-scheduled elgantt-effort) + (let* ((start-ts (ts-parse elgantt-scheduled)) + (raw-mins (org-duration-to-minutes elgantt-effort)) + ;; Add the weekend jump days to the visual length + (total-days (+ (ceiling (/ (float raw-mins) 1440.0)) (or wknd-days 0))) + (p1 (save-excursion + (elgantt--goto-date (ts-format "%Y-%m-%d" start-ts)) + (point))) + (colors (or override-color '("#8ec07c" "#458588")))) + (when (numberp p1) + (if (<= total-days 1) + (elgantt--create-overlay (truncate p1) (1+ (truncate p1)) + `(face (:background ,(car colors)) + priority ,(setq elgantt-user-set-color-priority-counter + (1- elgantt-user-set-color-priority-counter)) + :elgantt-user-overlay ,elgantt-org-id)) + ;; FIX 1: compute p2 by date (handles "|" separators) + ;; FIX 2: keep original "Rule of 2" behavior to avoid +1 day overshoot + (let* ((end-ts (ts-adjust 'day (- total-days 2) start-ts)) + (p2 (save-excursion + (elgantt--goto-date (ts-format "%Y-%m-%d" end-ts)) + (point)))) + (when (numberp p2) + (elgantt--draw-gradient + (car colors) (cadr colors) + (truncate p1) (1+ (truncate p2)) nil + `(priority ,(setq elgantt-user-set-color-priority-counter + (1- elgantt-user-set-color-priority-counter)) + :elgantt-user-overlay ,elgantt-org-id)))))))))) + + ;; --- 3. Progress Bar --- + (elgantt-create-display-rule pages-read-progress + :parser ((total-pages . ((--when-let (org-entry-get (point) "TOTAL_PAGES") (string-to-number it)))) + (pages-read . ((--when-let (org-entry-get (point) "PAGES_READ") (string-to-number it))))) + :args (elgantt-deadline elgantt-scheduled) + :body ((when (and elgantt-deadline elgantt-scheduled total-pages pages-read) + (let* ((start (save-excursion (elgantt--goto-date elgantt-scheduled) (point))) + (end (save-excursion (elgantt--goto-date elgantt-deadline) (point))) + (percent (/ (float pages-read) (float total-pages)))) + (when (and (numberp start) (numberp end)) + (elgantt--draw-progress-bar "#98be65" "#ff6c6b" + (truncate start) (truncate end) percent)))))) + + ;; --- 4. Blocker Interaction (Smart Append) --- + (require 'elgantt-interaction) + (elgantt--selection-rule :name mark-blocker + :selection-number 2 + :selection-messages ((1 . "Select the BLOCKING task (Cause)") + (2 . "Select the BLOCKED task (Effect)")) + :execution-functions + ((1 . ((elgantt-with-point-at-orig-entry nil (org-id-get-create)))) + (2 . ((let* ((new-id return-val) + (current (elgantt-with-point-at-orig-entry nil (org-entry-get (point) "BLOCKER")))) + (elgantt-with-point-at-orig-entry nil + (if (and current (string-match "ids(\\(.*?\\))" current)) + (let ((existing (match-string 1 current))) + (org-set-property "BLOCKER" (format "ids(%s %s)" existing new-id))) + (org-set-property "BLOCKER" (format "ids(%s)" new-id))) + (message "Added blocker: %s" new-id))))))) + + ;; --- 5. Blocker Lines (Multi-ID & Sync Support) --- + (elgantt-create-display-rule draw-blocker-lines + :parser ((blocker-raw . ((org-entry-get (point) "BLOCKER")))) + :args (elgantt-org-id) + :body ((when (and blocker-raw (not (string-empty-p blocker-raw))) + (let* ((p-blocked (point)) + (ids-string (if (string-match "ids(\\(.*?\\))" blocker-raw) + (match-string 1 blocker-raw) + blocker-raw)) + ;; Supports spaces or commas as separators + (id-list (split-string ids-string "[ ,]+" t))) + (dolist (blocker-id id-list) + (save-excursion + (when (elgantt--goto-id blocker-id) + (let* ((blocker-data (elgantt-with-point-at-orig-entry nil + (list (org-entry-get (point) "SCHEDULED") + (org-entry-get (point) "EFFORT")))) + (b-sched (car blocker-data)) + (b-effort (cadr blocker-data))) + (when (and b-sched b-effort) + (let* ((start-ts (ts-parse b-sched)) + (raw-mins (org-duration-to-minutes b-effort)) + (days-count (ceiling (/ (float raw-mins) 1440.0))) + (p-start-base (save-excursion + (elgantt--goto-date (ts-format "%Y-%m-%d" start-ts)) + (point)))) + (when (and (numberp p-start-base) (numberp p-blocked)) + ;; FIX 1: compute p-line-start by date (handles "|" separators) + ;; FIX 2: keep original "Rule of 2" behavior to avoid +1 day overshoot + (let* ((end-ts (ts-adjust 'day (- days-count 2) start-ts)) + (p-line-start (save-excursion + (elgantt--goto-date (ts-format "%Y-%m-%d" end-ts)) + (point)))) + (when (numberp p-line-start) + (elgantt--draw-line (truncate p-line-start) + (truncate p-blocked) + "#b8bb26")))))))))))))) + + ;; --- 6. Hashtag Navigation --- + (elgantt-create-action follow-hashtag-link-forward + :args (elgantt-alltags) :binding "C-M-f" + :body ((when-let* ((hashtag (--first (s-starts-with-p "#" it) elgantt-alltags)) + (match (elgantt--next-match :elgantt-alltags hashtag))) + (goto-char (car match))))) + + (elgantt-create-action follow-hashtag-link-backward + :args (elgantt-alltags) :binding "C-M-b" + :body ((when-let* ((hashtag (--first (s-starts-with-p "#" it) elgantt-alltags)) + (match (elgantt--previous-match :elgantt-alltags hashtag))) + (goto-char (car match))))) + ) + +(defun elgantt-open-current-org-file () + (interactive) + (if-let ((file (buffer-file-name))) + (progn + (setq elgantt-agenda-files (list file)) + (elgantt--reset-org-ql-cache) + (elgantt-open)) + (message "No file!"))) +#+end_src + +#+RESULTS: +: elgantt--action-rule-follow-hashtag-link-backward * Org Roam @@ -575,7 +796,7 @@ fc --> UC3 (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") + org-roam-dailies-directory "~/ExoKortex/2-Areas/Meta-Planning/Org/Journal/Daily") #+end_src ** Custom config @@ -632,9 +853,9 @@ fc --> UC3 ("Journal")) :immediate-finish t :empty-lines 1) - ) ) ) +) #+end_src ** Custom function @@ -868,9 +1089,10 @@ I had to work on C# Framework 4.8.. ** Scroll-Margin To keep my eye in the center of the screen while scrolling. Like in my nvim x) +Deactivated for ultra-scroll #+begin_src emacs-lisp -(setq scroll-margin 10) +;; (setq scroll-margin 10) #+end_src ** Visual Line Mode @@ -1011,10 +1233,10 @@ Im not working in WorkVisual. Thank you. (Kuka Robot stuff) #+end_src ** KRL formater -Im a formating nazi now xD +I'm a formatting nazi now xD #+begin_src emacs-lisp -(defcustom krl-formatter-script-path "~/ExoKortex/1-Projects/Exit_strat/exit_strat/scripts/krl_formatter.py" +(defcustom krl-formatter-script-path "~/ExoKortex/1-Projects/Exit_strat/robot_program_formater/scripts/krl_formatter.py" "Path to the KRL formatter script." :type 'string :group 'krl) @@ -1051,7 +1273,7 @@ Im a formating nazi now xD (krl-format-buffer))) ;; Uncomment the next line to enable auto-formatting on save -(add-hook 'before-save-hook 'krl-format-before-save) +;; (add-hook 'before-save-hook 'krl-format-before-save) #+end_src ** Hledger Mode @@ -1181,6 +1403,13 @@ Allow retrieval of password from age file formatted like passwordstore (setenv "PASSAGE_DIR" (expand-file-name "~/ExoKortex/2-Areas/IT/dotfiles/secrets")) ) #+end_src +** Diff HL Mode +Show git changes in the sidebar + +#+begin_src emacs-lisp +(diff-hl-mode +1) +#+end_src + * Terminal ** +EEE+ @@ -1203,6 +1432,7 @@ Allow retrieval of password from age file formatted like passwordstore #+end_src * AI +GPTel and opencode are the winner for now. ** GPTel The best LLM integration I found. Works with MCP server for more functionnalities. And Rewrite is awesome. @@ -1235,16 +1465,20 @@ The best LLM integration I found. Works with MCP server for more functionnalitie google/gemini-2.0-flash-exp:free deepseek/deepseek-chat-v3-0324:free meta-llama/llama-4-maverick:free + mistralai/devstral-2512:free qwen/qwen3-coder:free)) (gptel-make-gemini "Gemini" :key (auth-source-passage-get 'secret "gemini") :stream t - :models '(gemini-2.5-pro + :models '(gemini-3-pro-preview + gemini-2.5-pro + gemini-3-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 + (gptel-make-ollama "Ollama" + :host "localhost:11434" + :stream t :models '(deepseek-r1:1.5b + gemini-3-pro-preview orieg/gemma3-tools:4b)) ;List of models (gptel-make-openai "OpenWebUI" :host "ai.aziworkhorse.duckdns.org" @@ -1253,7 +1487,14 @@ The best LLM integration I found. Works with MCP server for more functionnalitie :endpoint "/api/chat/completions" :stream t :models '("orieg/gemma3-tools:1b")) - ) + (gptel-make-openai "MistralLeChat" + :host "api.mistral.ai" + :endpoint "/v1/chat/completions" + :protocol "https" + :key (auth-source-passage-get 'secret "mistral") + :models '("devstral-2512")) + (setq-default gptel-backend (gptel-get-backend "MistralLeChat") + gptel-model 'devstral-2512)) #+end_src *** Rewrite @@ -1270,6 +1511,51 @@ Really useful to do stuff you dont want to do )) #+end_src +** Magit-gptel +#+begin_src emacs-lisp +(defun gortium/magit-gptel-generate-commit () + "Generate a professional Git commit message from staged changes." + (interactive) + + (unless (and buffer-file-name + (string-match-p "COMMIT_EDITMSG" buffer-file-name)) + (user-error "Not in a commit message buffer")) + + (let ((diff (magit-git-string "diff" "--cached")) + (buf (current-buffer))) + (when (or (null diff) (string-empty-p diff)) + (user-error "No staged changes")) + + (gptel-request + (format + "You are a professional software engineer writing a Git commit message. + +IMPORTANT RULES: +- You MUST only use information contained in the diff below. +- Do NOT guess or invent changes. +- If the diff is unclear, write a generic message such as: + 'Update files with staged changes' and do NOT mention specific file details. +- The first line must be imperative and <= 50 characters. +- Provide a short bullet list in the body (2–4 bullets max). + +DIFF: +%s" + diff) + :stream nil + :callback + (lambda (response _info) + (with-current-buffer buf + (erase-buffer) + (insert (string-trim response) "\n")))))) + +(map! :after magit + :map git-commit-mode-map + :localleader + :desc "GPT generate commit" + "g" #'gortium/magit-gptel-generate-commit) + +#+end_src + ** +MCP+ May reuse later with opencode or gptel, but for now, deactivated @@ -1456,13 +1742,6 @@ Org-AI (org-ai-install-yasnippets)) ; if you are using yasnippet and want `ai` snippets #+end_src -* Diff HL Mode -Show git changes in the sidebar - -#+begin_src emacs-lisp -(diff-hl-mode +1) -#+end_src - * Tramp Remote connection to server inside emacs (with all my config) <3 @@ -1474,6 +1753,16 @@ Remote connection to server inside emacs (with all my config) <3 (connection-local-set-profiles '(:application tramp :protocol "ssh") 'remote-direct-async-process) + +(after! tramp + (setq tramp-verbose 10) + ;; Custom TRAMP configuration for Windows SSH ;; (add-to-list 'tramp-connection-properties + ;; (list "ssh" + ;; "Ingenuity-win" + ;; "tramp-shell-prompt-pattern" + ;; "\\$ $")) + ) + #+end_src * Dirvish @@ -1485,7 +1774,10 @@ Recently added the quick access entries. Game changer. (after! dirvish ;; Display icons, file size, timestamps, etc. (setq dirvish-attributes - '(all-the-icons subtree-state file-size file-time)) + '(nerd-icons + subtree-state + file-size + file-time)) (setq dirvish-default-layout '(0.3 0.15 0.55)) @@ -1504,7 +1796,7 @@ Recently added the quick access entries. Game changer. (setq! dirvish-quick-access-entries `( ("h" "~/" "Home") - ("k" "~/ExoKortex/2-Areas/Meta-Planning/Core" "ExoKortex") + ("k" "~/ExoKortex/2-Areas/Meta-Planning/Org/Core" "ExoKortex") ("p" "~/ExoKortex/1-Projects" "Projects") ("a" "~/ExoKortex/2-Areas" "Areas") ("r" "~/ExoKortex/3-Resources" "Resources") @@ -1513,8 +1805,16 @@ Recently added the quick access entries. Game changer. ("u" "/run/media" "Mounted drives") ("t" "~/.local/share/Trash/files/" "Trash") ))) + +;; Use `p` to open the yank menu +(map! :after dirvish-yank + :map dirvish-mode-map + :n "p" #'dirvish-yank-menu) + #+end_src +#+RESULTS: + * Consult-ripgrep #+begin_src emacs-lisp @@ -1544,6 +1844,7 @@ Make org task, agenda, tables look nicer, but you need to deactivate it sometime :after org :config (global-org-modern-mode) + (setq org-modern-star 'replace) (setq org-modern-todo-faces '( ;; Framework @@ -1554,7 +1855,7 @@ Make org task, agenda, tables look nicer, but you need to deactivate it sometime ;; Tasks ("TODO" :foreground "#fb4934") ; to do ("STRT" :foreground "#fe8019" :weight bold :inverse-video t) ; started - ("NEXT" :foreground "#b8bb26" :weight bold :underline t) ; next action + ("NEXT" :foreground "#fb4934" :weight bold :underline t) ; next action ("LOOP" :foreground "#fabd2f") ; recurring ("DELG" :foreground "#8ec07c") ; delegated ("WAIT" :foreground "#fabd2f" :slant italic) ; waiting @@ -1585,7 +1886,7 @@ Make org task, agenda, tables look nicer, but you need to deactivate it sometime #+end_src * Cursor Beacon -Useful for the user, but also when you have someone over your sholder trying to follow what is going on x) +Useful for the user, but also when you have someone over your shoulder trying to follow what is going on x) #+begin_src emacs-lisp ;; Flash the point (cursor) when moving between window @@ -1642,29 +1943,88 @@ www.tdnde.com \\\\ #+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) + (setq mu4e-maildir-shortcuts mu4e-maildir-list mu4e-maildir-initial-input mu4e-maildir-info-delimiter) + (setq mu4e-contexts + (list + (make-mu4e-context + :name "TDNDE" + :match-func + (lambda (msg) + (when msg + (string-prefix-p + "/TDNDE/" + (mu4e-message-field msg :maildir)))) + :vars + '((user-mail-address . "tpouplier@tdnde.com") + (user-full-name . "Thierry Pouplier") - ;; (mu4e-maildir-shortcuts - ;; '( - ;; ("Inbox" . ?i) - ;; ("Drafts" . ?d) - ;; ("Sent" . ?s) - ;; ("Trash" . ?t) - ;; )) - ) - t) + ;; Folders + (mu4e-sent-folder . "/TDNDE/Sent") + (mu4e-drafts-folder . "/TDNDE/Drafts") + (mu4e-trash-folder . "/TDNDE/Trash") + (mu4e-refile-folder . "/TDNDE/Archive") + + ;; Shortcuts + (mu4e-maildir-shortcuts + . + (("/TDNDE/Inbox" . ?i) + ("/TDNDE/Sent" . ?s) + ("/TDNDE/Drafts" . ?d) + ("/TDNDE/Trash" . ?t) + ("/TDNDE/Archive" . ?a))) + + ;; Bookmarks + (mu4e-bookmarks + . + ((:name "Airbus-6825" + :query "maildir:/TDNDE/* AND label:proj:6825 AND label:client:airbus" + :key ?A) + (:name "Bombardier-3154" + :query "maildir:/TDNDE/* AND label:proj:3154 AND label:client:bombardier" + :key ?B) + (:name "Daher-5304" + :query "maildir:/TDNDE/* AND label:proj:5304 AND label:client:daher" + :key ?D) + (:name "TDNDE" + :query "maildir:/TDNDE/* AND label:tdnde" + :key ?T))) + + ;; SMTP + (smtpmail-smtp-user . "tpouplier@tdnde.com") + (smtpmail-stream-type . ssl) + (smtpmail-smtp-server . "smtp.hostinger.com") + (smtpmail-smtp-service . 465))) + ;; Other context here + ;; (make-mu4e-context ...) + ) + ) + + (setq mu4e-context-policy 'pick-first + mu4e-compose-context-policy 'ask) + + (require 'mu4e-icalendar) + (mu4e-icalendar-setup) + + (setq mu4e-icalendar-diary-file nil) + + (setq gnus-icalendar-org-capture-file +org-capture-journal-file + gnus-icalendar-org-capture-headline '("Journal")) + + (gnus-icalendar-org-setup) + ) + +(after! mu4e + (setq mu4e-modeline-support nil)) + +(after! mu4e + ;; restore label key + (evil-define-key 'normal mu4e-headers-mode-map"l" #'mu4e-headers-mark-for-label) + (evil-define-key 'visual mu4e-headers-mode-map "l" #'mu4e-headers-mark-for-label) + (evil-define-key 'emacs mu4e-headers-mode-map "l" #'mu4e-headers-mark-for-label) + + (evil-define-key 'normal mu4e-view-mode-map "l" #'mu4e-view-mark-for-label) + (evil-define-key 'visual mu4e-view-mode-map "l" #'mu4e-view-mark-for-label) + (evil-define-key 'emacs mu4e-view-mode-map "l" #'mu4e-view-mark-for-label) ) #+end_src @@ -1672,6 +2032,9 @@ www.tdnde.com \\\\ ** Circe #+begin_src emacs-lisp +(defun gortium-circe-nickserv-password (server) + (auth-source-passage-get 'secret "irc")) + (after! circe ;; set your nick, username, and real name (setq circe-nick "gortium" @@ -1682,7 +2045,7 @@ www.tdnde.com \\\\ :tls t :nick "gortium" :sasl-username "gortium" - :sasl-password (auth-source-passage-get 'secret "irc") + :nickserv-password gortium-circe-nickserv-password :channels ("#emacs-circe") ))) ) @@ -1712,8 +2075,16 @@ Really useful to be able to have a buffer with notes, and another with the offic #+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)))) +(use-package! pdf-tools + :magic ("%PDF" . pdf-view-mode) + :config + ;; Install the server binary if needed (permanent) + (pdf-tools-install :no-query) + + ;; Hooks + (add-hook 'pdf-view-mode-hook #'pdf-view-midnight-minor-mode) + (add-hook 'pdf-view-mode-hook #'pdf-view-roll-minor-mode) + ) #+end_src * Custom Emacs-Everywhere for Hyprland @@ -1817,7 +2188,7 @@ If FILE is nil, refile in the current file." (add-hook 'ediff-prepare-buffer-hook 'org-ediff-prepare-buffer) #+end_src -* Custom function for reshedulling +* Custom function for reschedulling #+begin_src emacs-lisp (defun gortium/add-trigger-scheduling-next () @@ -1864,113 +2235,195 @@ Otherwise, use SCHEDULED + EFFORT." (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))))) +;; --- Helper: Snap to working hours (8-16) --- +;; --- Helper: Snap to working hours (8-16) --- +(defun gortium/internal--snap-to-working-hours (time) + (let* ((day-start 8) (day-end 16) + (t1 (gortium/org--skip-weekend time)) + (d (decode-time t1)) (h (nth 2 d))) + (cond + ((< h day-start) (apply #'encode-time (append (list 0 0 day-start) (nthcdr 3 d)))) + ((>= h day-end) + (let ((next (time-add t1 (days-to-time 1)))) + (gortium/org--skip-weekend (apply #'encode-time (append (list 0 0 day-start) (nthcdr 3 (decode-time next))))))) + (t t1)))) + +;; --- Helper: Calculate End Time & Weekend Jump (THE WALKER FIX) --- +(defun gortium/internal--calculate-task-span (start-time effort-str) + (if (or (null effort-str) (string-empty-p effort-str)) + (list start-time 0) + (let* ((eff-mins (org-duration-to-minutes effort-str)) + ;; SMART MATH: If string has "d", divide by 1440 (24h). If only "h", divide by 480 (8h). + (divisor (if (string-match-p "d" effort-str) 1440.0 480.0)) + (eff-days (max 1 (ceiling (/ (float eff-mins) divisor)))) + (cursor start-time) + (days-worked 0) + (wknd-count 0)) + + ;; THE WALKER: Simulate the task day by day + (while (< days-worked eff-days) + (let* ((dow (nth 6 (decode-time cursor)))) + (cond + ;; If Sat (6) or Sun (0), it's a weekend. Count it, but don't advance work. + ((or (= dow 6) (= dow 0)) + (setq wknd-count (1+ wknd-count)) + (setq cursor (time-add cursor (days-to-time 1)))) + ;; If Mon-Fri, it's a work day. Advance work count. + (t + (setq days-worked (1+ days-worked)) + ;; Move cursor to start of next day + (setq cursor (time-add cursor (days-to-time 1))))))) + + ;; Return [End-Time, Weekend-Days-Count] + (list cursor wknd-count)))) + +;; --- Helper: Find Blocker End Time --- +(defun gortium/internal--get-blocker-end (blocker-str current-pos task-end-map) + (let ((clean (s-trim blocker-str)) (latest-time nil)) + (cond + ((string-match-p "previous-sibling" clean) (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))) + (goto-char current-pos) + (let ((found nil)) + (while (and (not found) (org-get-last-sibling)) + (let* ((sid (org-id-get)) (end (when sid (gethash sid task-end-map)))) + (when end (setq latest-time end) (setq found t))))))) + ((string-match-p "parent" clean) + (save-excursion (goto-char current-pos) + (when (org-up-heading-safe) + (setq latest-time (gethash (org-id-get) task-end-map))))) + ((string-match "ids(\\(.*?\\))" clean) + (dolist (tid (split-string (match-string 1 clean) "[ ,]+" t)) + (let ((tend (gethash (replace-regexp-in-string "[\"']\\|id:" "" tid) task-end-map))) + (when (and tend (or (null latest-time) (time-less-p latest-time tend))) + (setq latest-time tend)))))) + latest-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))))))))))) +;; --- Helper: Write Properties (Safe) --- +(defun gortium/internal--update-properties (pos start wknd id end task-end-map) + (save-excursion + (goto-char pos) ;; POS is a MARKER + (if (and wknd (> wknd 0)) + (org-entry-put (point) "WEEKEND_DAYS" (number-to-string wknd)) + (org-entry-put (point) "WEEKEND_DAYS" nil)) + (org-schedule nil (format-time-string "<%Y-%m-%d %a %H:%M>" start)) + (puthash id end task-end-map))) (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)))) + "Advance TIME to the next Monday morning if it falls on a weekend." + (let* ((decoded (decode-time time)) + (dow (nth 6 decoded))) (cond - ((= dow 6) (time-add time (days-to-time 2))) ; Saturday → Monday - ((= dow 0) (time-add time (days-to-time 1))) ; Sunday → Monday + ((= dow 6) ;; Saturday + (let ((next (time-add time (days-to-time 2)))) + (apply #'encode-time (append '(0 0 8) (nthcdr 3 (decode-time next)))))) + ((= dow 0) ;; Sunday + (let ((next (time-add time (days-to-time 1)))) + (apply #'encode-time (append '(0 0 8) (nthcdr 3 (decode-time next)))))) (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)) +;; --- MAIN FUNCTION --- +(defun gortium/org-schedule-subtree-chains () + "Schedule tasks using MARKERS and WALKER logic." + (interactive) + (save-excursion + (message "--- Starting Gantt Scheduler ---") + (let* ((all-tasks '()) (task-end-times (make-hash-table :test 'equal))) + + ;; Collection + (org-map-entries + (lambda () + (when (org-get-todo-state) ;; Strict TODO check + (let ((effort (org-entry-get (point) "EFFORT" nil)) + (blocker (org-entry-get (point) "BLOCKER" nil)) + (fixed (org-entry-get (point) "FIXED" nil)) + (scheduled (org-entry-get (point) "SCHEDULED"))) + (when (or effort blocker fixed) + (push (list (point-marker) (org-id-get-create) (org-get-heading t t t t) + effort (when blocker (s-trim blocker)) fixed scheduled + (org-entry-get (point) "OFFSET_DAYS" nil)) all-tasks))))) + nil (if (org-at-heading-p) 'tree nil)) + (setq all-tasks (nreverse all-tasks)) + + ;; Pass 1: FIXED + (dolist (task all-tasks) + (pcase-let ((`(,pos ,id ,heading ,effort ,blocker ,fixed ,scheduled ,offset) task)) + (when (and fixed (member (downcase fixed) '("t" "true" "yes"))) + (if scheduled + (let* ((start (org-time-string-to-time scheduled)) + (span (gortium/internal--calculate-task-span start effort))) + (save-excursion + (goto-char pos) + (if (and (cadr span) (> (cadr span) 0)) + (org-entry-put (point) "WEEKEND_DAYS" (number-to-string (cadr span))) + (org-entry-put (point) "WEEKEND_DAYS" nil)) + (puthash id (car span) task-end-times))) + (message "WARNING: Fixed task '%s' missing date." heading))))) + + ;; Pass 2: CHAINS + (let* ((remaining (cl-remove-if (lambda (tk) (or (nth 5 tk) (not (nth 4 tk)))) all-tasks)) + (iter 0) (limit (* 5 (length all-tasks)))) + (while (and remaining (< iter limit)) + (cl-incf iter) + (let ((scheduled-this-loop '())) + (dolist (task remaining) + (pcase-let ((`(,pos ,id ,heading ,effort ,blocker ,fixed ,scheduled ,offset) task)) + (let ((dep-end (gortium/internal--get-blocker-end blocker pos task-end-times))) + (when dep-end + (let* ((off-val (if offset (string-to-number offset) 0)) + (base-start (time-add dep-end (days-to-time off-val))) + (start (gortium/internal--snap-to-working-hours base-start)) + (span (gortium/internal--calculate-task-span start effort))) + (gortium/internal--update-properties pos start (cadr span) id (car span) task-end-times) + (push task scheduled-this-loop)))))) + (setq remaining (cl-remove-if (lambda (tk) (member tk scheduled-this-loop)) remaining)))) + + (dolist (task all-tasks) (set-marker (car task) nil)) ;; Clean markers + (message "--- Scheduler Finished ---"))))) + + ;; USAGE: + ;; 1. Set up your tasks with either: + ;; - :FIXED: t and a SCHEDULED date, OR + ;; - :BLOCKER: previous-sibling / ids(UUID) / ids("id:UUID") / parent + ;; 2. Put cursor on "Planning" heading + ;; 3. M-x gortium/org-schedule-subtree-chains + ;; + ;; The function will: + ;; - Use FIXED tasks as anchors + ;; - Calculate all other tasks from their dependencies + ;; - Warn about tasks without BLOCKER or FIXED + ;; - Detect circular dependencies + ;; - Respect 8-hour workday limits + ;; - Skip weekends + ;; + ;; Supported BLOCKER types (standard EDNA format): + ;; - previous-sibling + ;; - ids(UUID) - e.g., ids(70d8f844-1952-43f0-8043-da51e8c8bc69) + ;; - ids("id:UUID") - e.g., ids("id:70d8f844-1952-43f0-8043-da51e8c8bc69") + ;; - parent + + (defun gortium/add-ids-to-subtree () + "Add IDs to all headings in current subtree." + (interactive) + (save-excursion + (org-back-to-heading t) + (org-map-entries + (lambda () (org-id-get-create)) + nil 'tree))) + + ;; 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 @@ -1992,13 +2445,13 @@ Starting at `gortium/org-day-start-hour` (e.g., 9 means 09:00 AM)." * Custom function to open current buffer to new frame #+begin_src emacs-lisp -(defvar my/main-frame nil +(defvar gortium/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)))) + (lambda () (setq gortium/main-frame (selected-frame)))) -(defun my--rightmost-non-minibuffer-window (frame) +(defun gortium--rightmost-non-minibuffer-window (frame) "Return the rightmost non-minibuffer window in FRAME, or nil if none." (let ((best-win nil) (best-right -1)) @@ -2010,7 +2463,7 @@ Starting at `gortium/org-day-start-hour` (e.g., 9 means 09:00 AM)." best-win w))))) best-win)) -(defun my--non-minibuffer-window-count (frame) +(defun gortium--non-minibuffer-window-count (frame) "Return number of non-minibuffer windows in FRAME." (let ((count 0)) (dolist (w (window-list frame)) @@ -2018,16 +2471,16 @@ Starting at `gortium/org-day-start-hour` (e.g., 9 means 09:00 AM)." (setq count (1+ count)))) count)) -(defun my/toggle-buffer-to-frame () - "Toggle the current buffer between `my/main-frame' and a new frame." +(defun gortium/toggle-buffer-to-frame () + "Toggle the current buffer between `gortium/main-frame' and a new frame." (interactive) - (unless (frame-live-p my/main-frame) - (setq my/main-frame (selected-frame))) + (unless (frame-live-p gortium/main-frame) + (setq gortium/main-frame (selected-frame))) (let* ((buf (current-buffer)) (old-frame (selected-frame)) (old-win (selected-window))) - (if (eq old-frame my/main-frame) + (if (eq old-frame gortium/main-frame) ;; === Move from main frame to new frame === (let ((new-frame (make-frame '((name . "Toggled Buffer"))))) (with-selected-frame new-frame @@ -2035,12 +2488,12 @@ Starting at `gortium/org-day-start-hour` (e.g., 9 means 09:00 AM)." (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) + (if (> (gortium--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))) + (with-selected-frame gortium/main-frame + (let ((target (gortium--rightmost-non-minibuffer-window gortium/main-frame))) (if target (with-selected-window target (condition-case nil @@ -2054,7 +2507,7 @@ Starting at `gortium/org-day-start-hour` (e.g., 9 means 09:00 AM)." ;; Close secondary frame or just the window (with-selected-frame old-frame - (if (= (my--non-minibuffer-window-count old-frame) 1) + (if (= (gortium--non-minibuffer-window-count old-frame) 1) (delete-frame old-frame) (when (window-live-p old-win) (delete-window old-win))))))) @@ -2065,7 +2518,7 @@ Starting at `gortium/org-day-start-hour` (e.g., 9 means 09:00 AM)." #+begin_src emacs-lisp ;; >=== ExoKortex System ===< -(defvar gortium/org-repo "~/ExoKortex/2-Areas/Meta-Planning/Projects/" +(defvar gortium/org-repo-projects "~/ExoKortex/2-Areas/Meta-Planning/Org/Projects" "Path to your main Org repository where real .org files are stored.") (defvar gortium/projects-root "~/ExoKortex/1-Projects/" @@ -2073,11 +2526,11 @@ Starting at `gortium/org-day-start-hour` (e.g., 9 means 09:00 AM)." (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." +The .org file is created in `gortium/org-repo-projects` 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-real (expand-file-name org-file gortium/org-repo-projects)) (org-link (expand-file-name org-file project-dir))) (make-directory project-dir t) (unless (file-exists-p org-real) @@ -2087,10 +2540,10 @@ The .org file is created in `gortium/org-repo` and symlinked into the project fo (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`." + "Create a symlinked org FILE-NAME in current dir, real file in `gortium/org-repo-projoects`." (interactive "sOrg file name (without .org): ") (let* ((org-file (concat file-name ".org")) - (org-real (expand-file-name org-file gortium/org-repo)) + (org-real (expand-file-name org-file gortium/org-repo-projects)) (org-link (expand-file-name org-file default-directory))) (unless (file-exists-p org-real) (with-temp-file org-real @@ -2099,14 +2552,14 @@ The .org file is created in `gortium/org-repo` and symlinked into the project fo (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`." + "Convert all marked org files in Dirvish/Dired to symlinks in `gortium/org-repo-projects`." (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))) + (org-real (expand-file-name file-name gortium/org-repo-projects))) (unless (file-exists-p org-real) (rename-file file-path org-real)) (make-symbolic-link org-real file-path t) @@ -2115,44 +2568,86 @@ The .org file is created in `gortium/org-repo` and symlinked into the project fo * Custom function to refile task to dailies -#+begin_src elisp -(defun my/refile-to-roam-daily () +#+begin_src emacs-lisp +(defun gortium/refile-to-today-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." +This version uses `org-roam-dailies-capture-today` to ensure the daily note +is created with Org-roam’s templates (including :ID:)." (interactive) (require 'org-roam-dailies) - (let* ((filename (format-time-string "%Y-%m-%d.org")) - (daily-file (expand-file-name filename org-roam-dailies-directory)) + (let ((headline "Journal")) + + ;; Step 1: Ensure today’s daily exists via org-roam (guarantees :ID:) + (let ((daily-file (org-roam-dailies-capture-today nil))) + ;; Step 2: Cut the subtree (use org-copy-subtree if you prefer duplication) + (org-cut-subtree) + + ;; Step 3: Paste under the Journal headline + (with-current-buffer (find-file-noselect daily-file) + (save-excursion + (goto-char (point-min)) + (when (re-search-forward (concat "^\\*+ " (regexp-quote headline) "\\b") nil t) + (let ((parent-level (org-current-level))) + (end-of-line) + (insert "\n") + (org-paste-subtree (+ 1 parent-level)))))) + (message "Task refiled under '%s' in today’s daily note." headline)))) +#+end_src + +#+begin_src emacs-lisp +(defun gortium/refile-to-daily-of-close () + "Refile the current subtree under the 'Journal' headline in the daily note +corresponding to the task's CLOSED date. If no CLOSED property is found, +fallback to today's daily note. Ensures the daily has an Org-roam ID." + (interactive) + (require 'org-roam-dailies) + + ;; Step 1: Get CLOSED property timestamp + (let* ((closed-prop (org-entry-get (point) "CLOSED")) + (time (when closed-prop + (ignore-errors (org-time-string-to-time closed-prop)))) + (date (or time (current-time))) (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: Ensure daily file exists using org-roam (this guarantees :ID: etc.) + (let ((daily-file (org-roam-dailies-capture-date date nil))) + ;; Step 3: Cut the subtree. + (org-cut-subtree) - ;; Step 2: Cut the subtree. - (org-cut-subtree) + ;; Step 4: Paste into the proper daily file + (with-current-buffer (find-file-noselect daily-file) + (save-excursion + (goto-char (point-min)) + ;; Find the 'Journal' headline + (when (re-search-forward (concat "^\\*+ " (regexp-quote headline) "\\b") nil t) + (let ((parent-level (org-current-level))) + (end-of-line) + (insert "\n") + (org-paste-subtree (+ 1 parent-level)))))) - ;; 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 %s" + headline + (file-name-nondirectory daily-file))))) +#+end_src +* LifeOS - (message "Task refiled under '%s' in today's daily note." headline))) +WIP, deactivated for now. +#+begin_src emacs-lisp :tangle no +;; Define the path to the root of your project repository. +(defvar lifeos-project-dir "/home/tpouplier/ExoKortex/1-Projects/Perso/lifeOS") + +(defvar lifeos-org-directory "~/org/" + "The root directory for the LifeOS org files.") + +;; Construct the path to the sub-directory containing your .el files. +(defvar lifeos-elisp-dir (expand-file-name "emacs/" lifeos-project-dir)) + +;; Add this directory to Emacs's load-path so it can find your file. +(add-to-list 'load-path lifeos-elisp-dir) + +;; Load the library using `require`. +(require 'lifeos) #+end_src * +Whisper+ @@ -2244,16 +2739,15 @@ Need chrome... :( * +Org-Edna+ -#+begin_src emacs-lisp :tangle no +#+begin_src emacs-lisp ;; Org-edna for advanced task dependencies (use-package! org-edna + :after org :config (setq org-edna-use-inheritance t) - (map! :after org - :map org-mode-map + (org-edna-mode 1) + (map! :map org-mode-map :localleader - :desc "Edit Edna rules" "E" #'org-edna-edit) - (org-edna-mode) - ) + :desc "Edit Edna rules" "E" #'org-edna-edit)) #+end_src diff --git a/doom/.config/doom/config.el b/doom/.config/doom/config.el index b000f6b..83d9b87 100644 --- a/doom/.config/doom/config.el +++ b/doom/.config/doom/config.el @@ -20,6 +20,16 @@ (:prefix ("w" . "window") :desc "Minimize window" "O" #'minimize-window)) +(use-package! ultra-scroll + :init + (setq scroll-margin 0 ; Required for ultra-scroll + scroll-conservatively 101 ; Prevents jumping to center + ultra-scroll-acceleration nil ; Essential for Miryoku keys + ultra-scroll-mouse-scale 0.1 ; Lower for higher precision + pixel-scroll-precision-large-scroll-height 20) + :config + (ultra-scroll-mode 1)) + ;; Super- auto -save (use-package! super-save :config @@ -40,11 +50,12 @@ (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-todo-file "~/ExoKortex/2-Areas/Meta-Planning/Org/Core/master_list.org" + +org-capture-notes-file "~/ExoKortex/2-Areas/Meta-Planning/Org/Core/master_list.org" + +org-capture-journal-file "~/ExoKortex/2-Areas/Meta-Planning/Org/Core/master_list.org" + +org-capture-emails-file "~/ExoKortex/2-Areas/Meta-Planning/Org/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$") @@ -139,7 +150,7 @@ (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"))) + ("ASSIGNEE_ALL" . "Thierry.P Cath.F Martin.K Michel.B Gabriel.C Silvie.L George.G Miguel.A Réjean.S Dominique Adnane Helper1 Helper2"))) (setq org-stuck-projects '("TODO=\"PROJ\"" ("NEXT") nil "")) @@ -152,6 +163,7 @@ org-agenda-skip-timestamp-if-done nil org-journal-enable-agenda-integration t org-log-done 'time + org-agenda-span 'day org-log-into-drawer t org-log-redeadline 'time org-log-reschedule 'time @@ -180,37 +192,6 @@ ) ) ;; 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 @@ -255,7 +236,7 @@ ("p" . "Personal commands") ("wg" "GTD view" ( - (agenda "" ((org-agenda-span 'day))) + (agenda "" ((org-agenda-span 'week))) (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))) @@ -268,7 +249,7 @@ ) ("pg" "GTD view" ( - (agenda "" ((org-agenda-span 'day))) + (agenda "" ((org-agenda-span 'week))) (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))) @@ -394,7 +375,7 @@ "* %? \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") + ("j" "Journal" entry (file+olp +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: /") @@ -403,15 +384,238 @@ ("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) + ("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) ) + ) ) -) + +;; Enable plantuml-mode for PlantUML files +(add-to-list 'auto-mode-alist '("\\.plantuml\\'" . plantuml-mode)) + +(require 'cl-lib) +(require 'dash) +(unless (fboundp 'first) (defalias 'first #'car)) + +;; Clear rules to ensure the new global color logic takes effect immediately +(setq elgantt--display-rules nil) + +(defface gortium/elgantt-weekend-face + '((t (:background "#332222" :extend nil))) + "Face for weekend vertical columns in ElGantt.") + +(defun gortium/elgantt-draw-weekend-guides () + "Draw weekend guides for the ENTIRE buffer once to prevent scroll lag." + (interactive) + (when (derived-mode-p 'elgantt-mode) + (let* ((inhibit-modification-hooks t) + (header-line-1 (save-excursion + (goto-char (point-min)) + (buffer-substring-no-properties (line-beginning-position) (line-end-position)))) + (col-indices '()) + (search-pos 0)) + + (save-excursion + (save-restriction + (widen) + ;; 1. Clear ALL weekend overlays in the entire buffer + (remove-overlays (point-min) (point-max) 'gortium-weekend t) + + ;; 2. Parse header once to find column indexes (Fast) + (while (string-match "|[[:space:]]*\\([[:alpha:]]+\\)[[:space:]]+\\([0-9]\\{4\\}\\)" header-line-1 search-pos) + (let* ((month-start-col (match-beginning 0)) + (month-name (match-string 1 header-line-1)) + (year (string-to-number (match-string 2 header-line-1))) + (month-num (gortium/internal--month-to-num month-name)) + (next-pipe (string-match "|" header-line-1 (1+ month-start-col))) + (month-width (if next-pipe (- next-pipe month-start-col 1) 31))) + (dotimes (d month-width) + (let* ((day (1+ d)) + (time (condition-case nil (encode-time 0 0 12 day month-num year) (error nil)))) + (when time + (let ((dow (nth 6 (decode-time time))) + (actual-col (+ month-start-col 1 d))) + (when (member dow '(0 6)) + (push actual-col col-indices)))))) + (setq search-pos (or next-pipe (length header-line-1))))) + + ;; 3. Apply to the WHOLE buffer line by line + (unless (null col-indices) + (goto-char (point-min)) + (forward-line 2) ;; Skip headers + (while (not (eobp)) + (let ((line-end (line-end-position))) + (dolist (col col-indices) + (move-to-column col) + (let ((p (point))) + ;; Ensure we are still on the same line and at the correct column + (when (and (< p line-end) (= (current-column) col)) + (let ((ov (make-overlay p (1+ p)))) + (overlay-put ov 'face 'gortium/elgantt-weekend-face) + (overlay-put ov 'gortium-weekend t) + (overlay-put ov 'priority 100) + (overlay-put ov 'evaporate t)))))) + (forward-line 1))))) + (message "Weekend guides rendered for the whole buffer.")))) + +;; Run it only once when the buffer is loaded +(add-hook 'elgantt-mode-hook #'gortium/elgantt-draw-weekend-guides) + +(use-package! elgantt + :commands (elgantt-open elgantt-open-current-org-file) + :config + ;; --- 1. Environment & UI --- + (add-hook 'elgantt-mode-hook + (lambda () + (setq-local org-phscroll-mode nil) + (setq-local image-roll-mode nil) + (setq truncate-lines t))) + + (setq elgantt-start-date "2026-01-01") + + (setq elgantt-header-column-offset 40 + elgantt-header-type 'outline + elgantt-show-header-depth t + elgantt-insert-blank-line-between-top-level-header t + elgantt-startup-folded nil + elgantt-draw-overarching-headers nil + elgantt-scroll-to-current-month-at-startup t) + + (setq elgantt-user-set-color-priority-counter 0) + + ;; --- 2. Effort Rule (With Weekend Extension) --- + (elgantt-create-display-rule draw-scheduled-to-effort-end + :parser ((override-color . ((when-let ((colors (org-entry-get (point) "ELGANTT-COLOR"))) + (split-string colors " ")))) + (elgantt-effort . ((org-entry-get (point) "EFFORT"))) + (wknd-days . ((when-let ((val (org-entry-get (point) "WEEKEND_DAYS"))) + (string-to-number val))))) + :args (elgantt-scheduled elgantt-effort elgantt-org-id) + :body ((when (and elgantt-scheduled elgantt-effort) + (let* ((start-ts (ts-parse elgantt-scheduled)) + (raw-mins (org-duration-to-minutes elgantt-effort)) + ;; Add the weekend jump days to the visual length + (total-days (+ (ceiling (/ (float raw-mins) 1440.0)) (or wknd-days 0))) + (p1 (save-excursion + (elgantt--goto-date (ts-format "%Y-%m-%d" start-ts)) + (point))) + (colors (or override-color '("#8ec07c" "#458588")))) + (when (numberp p1) + (if (<= total-days 1) + (elgantt--create-overlay (truncate p1) (1+ (truncate p1)) + `(face (:background ,(car colors)) + priority ,(setq elgantt-user-set-color-priority-counter + (1- elgantt-user-set-color-priority-counter)) + :elgantt-user-overlay ,elgantt-org-id)) + ;; FIX 1: compute p2 by date (handles "|" separators) + ;; FIX 2: keep original "Rule of 2" behavior to avoid +1 day overshoot + (let* ((end-ts (ts-adjust 'day (- total-days 2) start-ts)) + (p2 (save-excursion + (elgantt--goto-date (ts-format "%Y-%m-%d" end-ts)) + (point)))) + (when (numberp p2) + (elgantt--draw-gradient + (car colors) (cadr colors) + (truncate p1) (1+ (truncate p2)) nil + `(priority ,(setq elgantt-user-set-color-priority-counter + (1- elgantt-user-set-color-priority-counter)) + :elgantt-user-overlay ,elgantt-org-id)))))))))) + + ;; --- 3. Progress Bar --- + (elgantt-create-display-rule pages-read-progress + :parser ((total-pages . ((--when-let (org-entry-get (point) "TOTAL_PAGES") (string-to-number it)))) + (pages-read . ((--when-let (org-entry-get (point) "PAGES_READ") (string-to-number it))))) + :args (elgantt-deadline elgantt-scheduled) + :body ((when (and elgantt-deadline elgantt-scheduled total-pages pages-read) + (let* ((start (save-excursion (elgantt--goto-date elgantt-scheduled) (point))) + (end (save-excursion (elgantt--goto-date elgantt-deadline) (point))) + (percent (/ (float pages-read) (float total-pages)))) + (when (and (numberp start) (numberp end)) + (elgantt--draw-progress-bar "#98be65" "#ff6c6b" + (truncate start) (truncate end) percent)))))) + + ;; --- 4. Blocker Interaction (Smart Append) --- + (require 'elgantt-interaction) + (elgantt--selection-rule :name mark-blocker + :selection-number 2 + :selection-messages ((1 . "Select the BLOCKING task (Cause)") + (2 . "Select the BLOCKED task (Effect)")) + :execution-functions + ((1 . ((elgantt-with-point-at-orig-entry nil (org-id-get-create)))) + (2 . ((let* ((new-id return-val) + (current (elgantt-with-point-at-orig-entry nil (org-entry-get (point) "BLOCKER")))) + (elgantt-with-point-at-orig-entry nil + (if (and current (string-match "ids(\\(.*?\\))" current)) + (let ((existing (match-string 1 current))) + (org-set-property "BLOCKER" (format "ids(%s %s)" existing new-id))) + (org-set-property "BLOCKER" (format "ids(%s)" new-id))) + (message "Added blocker: %s" new-id))))))) + + ;; --- 5. Blocker Lines (Multi-ID & Sync Support) --- + (elgantt-create-display-rule draw-blocker-lines + :parser ((blocker-raw . ((org-entry-get (point) "BLOCKER")))) + :args (elgantt-org-id) + :body ((when (and blocker-raw (not (string-empty-p blocker-raw))) + (let* ((p-blocked (point)) + (ids-string (if (string-match "ids(\\(.*?\\))" blocker-raw) + (match-string 1 blocker-raw) + blocker-raw)) + ;; Supports spaces or commas as separators + (id-list (split-string ids-string "[ ,]+" t))) + (dolist (blocker-id id-list) + (save-excursion + (when (elgantt--goto-id blocker-id) + (let* ((blocker-data (elgantt-with-point-at-orig-entry nil + (list (org-entry-get (point) "SCHEDULED") + (org-entry-get (point) "EFFORT")))) + (b-sched (car blocker-data)) + (b-effort (cadr blocker-data))) + (when (and b-sched b-effort) + (let* ((start-ts (ts-parse b-sched)) + (raw-mins (org-duration-to-minutes b-effort)) + (days-count (ceiling (/ (float raw-mins) 1440.0))) + (p-start-base (save-excursion + (elgantt--goto-date (ts-format "%Y-%m-%d" start-ts)) + (point)))) + (when (and (numberp p-start-base) (numberp p-blocked)) + ;; FIX 1: compute p-line-start by date (handles "|" separators) + ;; FIX 2: keep original "Rule of 2" behavior to avoid +1 day overshoot + (let* ((end-ts (ts-adjust 'day (- days-count 2) start-ts)) + (p-line-start (save-excursion + (elgantt--goto-date (ts-format "%Y-%m-%d" end-ts)) + (point)))) + (when (numberp p-line-start) + (elgantt--draw-line (truncate p-line-start) + (truncate p-blocked) + "#b8bb26")))))))))))))) + + ;; --- 6. Hashtag Navigation --- + (elgantt-create-action follow-hashtag-link-forward + :args (elgantt-alltags) :binding "C-M-f" + :body ((when-let* ((hashtag (--first (s-starts-with-p "#" it) elgantt-alltags)) + (match (elgantt--next-match :elgantt-alltags hashtag))) + (goto-char (car match))))) + + (elgantt-create-action follow-hashtag-link-backward + :args (elgantt-alltags) :binding "C-M-b" + :body ((when-let* ((hashtag (--first (s-starts-with-p "#" it) elgantt-alltags)) + (match (elgantt--previous-match :elgantt-alltags hashtag))) + (goto-char (car match))))) + ) + +(defun elgantt-open-current-org-file () + (interactive) + (if-let ((file (buffer-file-name))) + (progn + (setq elgantt-agenda-files (list file)) + (elgantt--reset-org-ql-cache) + (elgantt-open)) + (message "No file!"))) (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") + org-roam-dailies-directory "~/ExoKortex/2-Areas/Meta-Planning/Org/Journal/Daily") (after! org-roam org-roam-completion-everywhere t @@ -464,9 +668,9 @@ ("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/ @@ -620,8 +824,7 @@ Handles org-clock and context link capture for tasks." (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) +;; (setq scroll-margin 10) ;; Enable visual line wrapping globally ;; (global-visual-line-mode 1) @@ -636,7 +839,6 @@ Handles org-clock and context link capture for tasks." (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" @@ -658,7 +860,6 @@ Handles org-clock and context link capture for tasks." (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 @@ -686,7 +887,6 @@ Handles org-clock and context link capture for tasks." (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) @@ -703,7 +903,6 @@ Handles org-clock and context link capture for tasks." (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 @@ -719,12 +918,7 @@ Handles org-clock and context link capture for tasks." 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" +(defcustom krl-formatter-script-path "~/ExoKortex/1-Projects/Exit_strat/robot_program_formater/scripts/krl_formatter.py" "Path to the KRL formatter script." :type 'string :group 'krl) @@ -737,7 +931,7 @@ Handles org-clock and context link capture for tasks." (shell-command-on-region (point-min) (point-max) - (concat krl-formatter-command " " krl-formatter-script-path) + (concat "python " krl-formatter-script-path) t t) ;; Try to restore cursor position (goto-char (point-min)) @@ -750,7 +944,7 @@ Handles org-clock and context link capture for tasks." (shell-command-on-region start end - (concat krl-formatter-command " " krl-formatter-script-path) + (concat "python " krl-formatter-script-path) t t) (message "KRL region formatted")) @@ -761,7 +955,7 @@ Handles org-clock and context link capture for tasks." (krl-format-buffer))) ;; Uncomment the next line to enable auto-formatting on save -(add-hook 'before-save-hook 'krl-format-before-save) +;; (add-hook 'before-save-hook 'krl-format-before-save) (use-package! hledger-mode :config @@ -871,10 +1065,7 @@ Handles org-clock and context link capture for tasks." (setenv "PASSAGE_DIR" (expand-file-name "~/ExoKortex/2-Areas/IT/dotfiles/secrets")) ) -;; TUI tools in emacs -(after! eee - (setq ee-terminal-command "kitty") - ) +(diff-hl-mode +1) ;; Ensure C-j works properly in insert state in vterm (after! vterm @@ -909,16 +1100,20 @@ Handles org-clock and context link capture for tasks." google/gemini-2.0-flash-exp:free deepseek/deepseek-chat-v3-0324:free meta-llama/llama-4-maverick:free + mistralai/devstral-2512:free qwen/qwen3-coder:free)) (gptel-make-gemini "Gemini" :key (auth-source-passage-get 'secret "gemini") :stream t - :models '(gemini-2.5-pro + :models '(gemini-3-pro-preview + gemini-2.5-pro + gemini-3-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 + (gptel-make-ollama "Ollama" + :host "localhost:11434" + :stream t :models '(deepseek-r1:1.5b + gemini-3-pro-preview orieg/gemma3-tools:4b)) ;List of models (gptel-make-openai "OpenWebUI" :host "ai.aziworkhorse.duckdns.org" @@ -927,18 +1122,65 @@ Handles org-clock and context link capture for tasks." :endpoint "/api/chat/completions" :stream t :models '("orieg/gemma3-tools:1b")) - ) + (gptel-make-openai "MistralLeChat" + :host "api.mistral.ai" + :endpoint "/v1/chat/completions" + :protocol "https" + :key (auth-source-passage-get 'secret "mistral") + :models '("devstral-2512")) + (setq-default gptel-backend (gptel-get-backend "MistralLeChat") + gptel-model 'devstral-2512)) (map! :after gptel :leader - (:prefix ("r" . "GPTel Rewrite") + ( + :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) +(defun gortium/magit-gptel-generate-commit () + "Generate a professional Git commit message from staged changes." + (interactive) + + (unless (and buffer-file-name + (string-match-p "COMMIT_EDITMSG" buffer-file-name)) + (user-error "Not in a commit message buffer")) + + (let ((diff (magit-git-string "diff" "--cached")) + (buf (current-buffer))) + (when (or (null diff) (string-empty-p diff)) + (user-error "No staged changes")) + + (gptel-request + (format + "You are a professional software engineer writing a Git commit message. + +IMPORTANT RULES: +- You MUST only use information contained in the diff below. +- Do NOT guess or invent changes. +- If the diff is unclear, write a generic message such as: + 'Update files with staged changes' and do NOT mention specific file details. +- The first line must be imperative and <= 50 characters. +- Provide a short bullet list in the body (2–4 bullets max). + +DIFF: +%s" + diff) + :stream nil + :callback + (lambda (response _info) + (with-current-buffer buf + (erase-buffer) + (insert (string-trim response) "\n")))))) + +(map! :after magit + :map git-commit-mode-map + :localleader + :desc "GPT generate commit" + "g" #'gortium/magit-gptel-generate-commit) ;; set `tramp-direct-async-process' locally in all ssh connections (connection-local-set-profile-variables @@ -948,11 +1190,23 @@ Handles org-clock and context link capture for tasks." '(:application tramp :protocol "ssh") 'remote-direct-async-process) +(after! tramp + (setq tramp-verbose 10) + ;; Custom TRAMP configuration for Windows SSH ;; (add-to-list 'tramp-connection-properties + ;; (list "ssh" + ;; "Ingenuity-win" + ;; "tramp-shell-prompt-pattern" + ;; "\\$ $")) + ) + ;; Dirvish config (after! dirvish ;; Display icons, file size, timestamps, etc. (setq dirvish-attributes - '(all-the-icons subtree-state file-size file-time)) + '(nerd-icons + subtree-state + file-size + file-time)) (setq dirvish-default-layout '(0.3 0.15 0.55)) @@ -971,7 +1225,7 @@ Handles org-clock and context link capture for tasks." (setq! dirvish-quick-access-entries `( ("h" "~/" "Home") - ("k" "~/ExoKortex/2-Areas/Meta-Planning/Core" "ExoKortex") + ("k" "~/ExoKortex/2-Areas/Meta-Planning/Org/Core" "ExoKortex") ("p" "~/ExoKortex/1-Projects" "Projects") ("a" "~/ExoKortex/2-Areas" "Areas") ("r" "~/ExoKortex/3-Resources" "Resources") @@ -981,6 +1235,11 @@ Handles org-clock and context link capture for tasks." ("t" "~/.local/share/Trash/files/" "Trash") ))) +;; Use `p` to open the yank menu +(map! :after dirvish-yank + :map dirvish-mode-map + :n "p" #'dirvish-yank-menu) + ;; Enable previewing of surrounding lines in consult-ripgrep (setq consult-ripgrep-preview t) @@ -998,6 +1257,7 @@ Handles org-clock and context link capture for tasks." :after org :config (global-org-modern-mode) + (setq org-modern-star 'replace) (setq org-modern-todo-faces '( ;; Framework @@ -1008,7 +1268,7 @@ Handles org-clock and context link capture for tasks." ;; Tasks ("TODO" :foreground "#fb4934") ; to do ("STRT" :foreground "#fe8019" :weight bold :inverse-video t) ; started - ("NEXT" :foreground "#b8bb26" :weight bold :underline t) ; next action + ("NEXT" :foreground "#fb4934" :weight bold :underline t) ; next action ("LOOP" :foreground "#fabd2f") ; recurring ("DELG" :foreground "#8ec07c") ; delegated ("WAIT" :foreground "#fabd2f" :slant italic) ; waiting @@ -1077,31 +1337,93 @@ www.tdnde.com \\\\ ) (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) + (setq mu4e-maildir-shortcuts mu4e-maildir-list mu4e-maildir-initial-input mu4e-maildir-info-delimiter) + (setq mu4e-contexts + (list + (make-mu4e-context + :name "TDNDE" + :match-func + (lambda (msg) + (when msg + (string-prefix-p + "/TDNDE/" + (mu4e-message-field msg :maildir)))) + :vars + '((user-mail-address . "tpouplier@tdnde.com") + (user-full-name . "Thierry Pouplier") - ;; (mu4e-maildir-shortcuts - ;; '( - ;; ("Inbox" . ?i) - ;; ("Drafts" . ?d) - ;; ("Sent" . ?s) - ;; ("Trash" . ?t) - ;; )) - ) - t) + ;; Folders + (mu4e-sent-folder . "/TDNDE/Sent") + (mu4e-drafts-folder . "/TDNDE/Drafts") + (mu4e-trash-folder . "/TDNDE/Trash") + (mu4e-refile-folder . "/TDNDE/Archive") + + ;; Shortcuts + (mu4e-maildir-shortcuts + . + (("/TDNDE/Inbox" . ?i) + ("/TDNDE/Sent" . ?s) + ("/TDNDE/Drafts" . ?d) + ("/TDNDE/Trash" . ?t) + ("/TDNDE/Archive" . ?a))) + + ;; Bookmarks + (mu4e-bookmarks + . + ((:name "Airbus-6825" + :query "maildir:/TDNDE/* AND label:proj:6825 AND label:client:airbus" + :key ?A) + (:name "Bombardier-3154" + :query "maildir:/TDNDE/* AND label:proj:3154 AND label:client:bombardier" + :key ?B) + (:name "Daher-5304" + :query "maildir:/TDNDE/* AND label:proj:5304 AND label:client:daher" + :key ?D) + (:name "TDNDE" + :query "maildir:/TDNDE/* AND label:tdnde" + :key ?T))) + + ;; SMTP + (smtpmail-smtp-user . "tpouplier@tdnde.com") + (smtpmail-stream-type . ssl) + (smtpmail-smtp-server . "smtp.hostinger.com") + (smtpmail-smtp-service . 465))) + ;; Other context here + ;; (make-mu4e-context ...) + ) + ) + + (setq mu4e-context-policy 'pick-first + mu4e-compose-context-policy 'ask) + + (require 'mu4e-icalendar) + (mu4e-icalendar-setup) + + (setq mu4e-icalendar-diary-file nil) + + (setq gnus-icalendar-org-capture-file +org-capture-journal-file + gnus-icalendar-org-capture-headline '("Journal")) + + (gnus-icalendar-org-setup) ) +(after! mu4e + (setq mu4e-modeline-support nil)) + +(after! mu4e + ;; restore label key + (evil-define-key 'normal mu4e-headers-mode-map"l" #'mu4e-headers-mark-for-label) + (evil-define-key 'visual mu4e-headers-mode-map "l" #'mu4e-headers-mark-for-label) + (evil-define-key 'emacs mu4e-headers-mode-map "l" #'mu4e-headers-mark-for-label) + + (evil-define-key 'normal mu4e-view-mode-map "l" #'mu4e-view-mark-for-label) + (evil-define-key 'visual mu4e-view-mode-map "l" #'mu4e-view-mark-for-label) + (evil-define-key 'emacs mu4e-view-mode-map "l" #'mu4e-view-mark-for-label) + ) + +(defun gortium-circe-nickserv-password (server) + (auth-source-passage-get 'secret "irc")) + (after! circe ;; set your nick, username, and real name (setq circe-nick "gortium" @@ -1112,14 +1434,22 @@ www.tdnde.com \\\\ :tls t :nick "gortium" :sasl-username "gortium" - :sasl-password (auth-source-passage-get 'secret "irc") + :nickserv-password gortium-circe-nickserv-password :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)))) +(use-package! pdf-tools + :magic ("%PDF" . pdf-view-mode) + :config + ;; Install the server binary if needed (permanent) + (pdf-tools-install :no-query) + + ;; Hooks + (add-hook 'pdf-view-mode-hook #'pdf-view-midnight-minor-mode) + (add-hook 'pdf-view-mode-hook #'pdf-view-roll-minor-mode) + ) ;; Like Emacs everywhere, but work in hyprland (defun thanos/wtype-text (text) @@ -1254,113 +1584,195 @@ Otherwise, use SCHEDULED + EFFORT." (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))))) +;; --- Helper: Snap to working hours (8-16) --- +;; --- Helper: Snap to working hours (8-16) --- +(defun gortium/internal--snap-to-working-hours (time) + (let* ((day-start 8) (day-end 16) + (t1 (gortium/org--skip-weekend time)) + (d (decode-time t1)) (h (nth 2 d))) + (cond + ((< h day-start) (apply #'encode-time (append (list 0 0 day-start) (nthcdr 3 d)))) + ((>= h day-end) + (let ((next (time-add t1 (days-to-time 1)))) + (gortium/org--skip-weekend (apply #'encode-time (append (list 0 0 day-start) (nthcdr 3 (decode-time next))))))) + (t t1)))) + +;; --- Helper: Calculate End Time & Weekend Jump (THE WALKER FIX) --- +(defun gortium/internal--calculate-task-span (start-time effort-str) + (if (or (null effort-str) (string-empty-p effort-str)) + (list start-time 0) + (let* ((eff-mins (org-duration-to-minutes effort-str)) + ;; SMART MATH: If string has "d", divide by 1440 (24h). If only "h", divide by 480 (8h). + (divisor (if (string-match-p "d" effort-str) 1440.0 480.0)) + (eff-days (max 1 (ceiling (/ (float eff-mins) divisor)))) + (cursor start-time) + (days-worked 0) + (wknd-count 0)) + + ;; THE WALKER: Simulate the task day by day + (while (< days-worked eff-days) + (let* ((dow (nth 6 (decode-time cursor)))) + (cond + ;; If Sat (6) or Sun (0), it's a weekend. Count it, but don't advance work. + ((or (= dow 6) (= dow 0)) + (setq wknd-count (1+ wknd-count)) + (setq cursor (time-add cursor (days-to-time 1)))) + ;; If Mon-Fri, it's a work day. Advance work count. + (t + (setq days-worked (1+ days-worked)) + ;; Move cursor to start of next day + (setq cursor (time-add cursor (days-to-time 1))))))) + + ;; Return [End-Time, Weekend-Days-Count] + (list cursor wknd-count)))) + +;; --- Helper: Find Blocker End Time --- +(defun gortium/internal--get-blocker-end (blocker-str current-pos task-end-map) + (let ((clean (s-trim blocker-str)) (latest-time nil)) + (cond + ((string-match-p "previous-sibling" clean) (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))) + (goto-char current-pos) + (let ((found nil)) + (while (and (not found) (org-get-last-sibling)) + (let* ((sid (org-id-get)) (end (when sid (gethash sid task-end-map)))) + (when end (setq latest-time end) (setq found t))))))) + ((string-match-p "parent" clean) + (save-excursion (goto-char current-pos) + (when (org-up-heading-safe) + (setq latest-time (gethash (org-id-get) task-end-map))))) + ((string-match "ids(\\(.*?\\))" clean) + (dolist (tid (split-string (match-string 1 clean) "[ ,]+" t)) + (let ((tend (gethash (replace-regexp-in-string "[\"']\\|id:" "" tid) task-end-map))) + (when (and tend (or (null latest-time) (time-less-p latest-time tend))) + (setq latest-time tend)))))) + latest-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))))))))))) +;; --- Helper: Write Properties (Safe) --- +(defun gortium/internal--update-properties (pos start wknd id end task-end-map) + (save-excursion + (goto-char pos) ;; POS is a MARKER + (if (and wknd (> wknd 0)) + (org-entry-put (point) "WEEKEND_DAYS" (number-to-string wknd)) + (org-entry-put (point) "WEEKEND_DAYS" nil)) + (org-schedule nil (format-time-string "<%Y-%m-%d %a %H:%M>" start)) + (puthash id end task-end-map))) (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)))) + "Advance TIME to the next Monday morning if it falls on a weekend." + (let* ((decoded (decode-time time)) + (dow (nth 6 decoded))) (cond - ((= dow 6) (time-add time (days-to-time 2))) ; Saturday → Monday - ((= dow 0) (time-add time (days-to-time 1))) ; Sunday → Monday + ((= dow 6) ;; Saturday + (let ((next (time-add time (days-to-time 2)))) + (apply #'encode-time (append '(0 0 8) (nthcdr 3 (decode-time next)))))) + ((= dow 0) ;; Sunday + (let ((next (time-add time (days-to-time 1)))) + (apply #'encode-time (append '(0 0 8) (nthcdr 3 (decode-time next)))))) (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)) +;; --- MAIN FUNCTION --- +(defun gortium/org-schedule-subtree-chains () + "Schedule tasks using MARKERS and WALKER logic." + (interactive) + (save-excursion + (message "--- Starting Gantt Scheduler ---") + (let* ((all-tasks '()) (task-end-times (make-hash-table :test 'equal))) + + ;; Collection + (org-map-entries + (lambda () + (when (org-get-todo-state) ;; Strict TODO check + (let ((effort (org-entry-get (point) "EFFORT" nil)) + (blocker (org-entry-get (point) "BLOCKER" nil)) + (fixed (org-entry-get (point) "FIXED" nil)) + (scheduled (org-entry-get (point) "SCHEDULED"))) + (when (or effort blocker fixed) + (push (list (point-marker) (org-id-get-create) (org-get-heading t t t t) + effort (when blocker (s-trim blocker)) fixed scheduled + (org-entry-get (point) "OFFSET_DAYS" nil)) all-tasks))))) + nil (if (org-at-heading-p) 'tree nil)) + (setq all-tasks (nreverse all-tasks)) + + ;; Pass 1: FIXED + (dolist (task all-tasks) + (pcase-let ((`(,pos ,id ,heading ,effort ,blocker ,fixed ,scheduled ,offset) task)) + (when (and fixed (member (downcase fixed) '("t" "true" "yes"))) + (if scheduled + (let* ((start (org-time-string-to-time scheduled)) + (span (gortium/internal--calculate-task-span start effort))) + (save-excursion + (goto-char pos) + (if (and (cadr span) (> (cadr span) 0)) + (org-entry-put (point) "WEEKEND_DAYS" (number-to-string (cadr span))) + (org-entry-put (point) "WEEKEND_DAYS" nil)) + (puthash id (car span) task-end-times))) + (message "WARNING: Fixed task '%s' missing date." heading))))) + + ;; Pass 2: CHAINS + (let* ((remaining (cl-remove-if (lambda (tk) (or (nth 5 tk) (not (nth 4 tk)))) all-tasks)) + (iter 0) (limit (* 5 (length all-tasks)))) + (while (and remaining (< iter limit)) + (cl-incf iter) + (let ((scheduled-this-loop '())) + (dolist (task remaining) + (pcase-let ((`(,pos ,id ,heading ,effort ,blocker ,fixed ,scheduled ,offset) task)) + (let ((dep-end (gortium/internal--get-blocker-end blocker pos task-end-times))) + (when dep-end + (let* ((off-val (if offset (string-to-number offset) 0)) + (base-start (time-add dep-end (days-to-time off-val))) + (start (gortium/internal--snap-to-working-hours base-start)) + (span (gortium/internal--calculate-task-span start effort))) + (gortium/internal--update-properties pos start (cadr span) id (car span) task-end-times) + (push task scheduled-this-loop)))))) + (setq remaining (cl-remove-if (lambda (tk) (member tk scheduled-this-loop)) remaining)))) + + (dolist (task all-tasks) (set-marker (car task) nil)) ;; Clean markers + (message "--- Scheduler Finished ---"))))) + + ;; USAGE: + ;; 1. Set up your tasks with either: + ;; - :FIXED: t and a SCHEDULED date, OR + ;; - :BLOCKER: previous-sibling / ids(UUID) / ids("id:UUID") / parent + ;; 2. Put cursor on "Planning" heading + ;; 3. M-x gortium/org-schedule-subtree-chains + ;; + ;; The function will: + ;; - Use FIXED tasks as anchors + ;; - Calculate all other tasks from their dependencies + ;; - Warn about tasks without BLOCKER or FIXED + ;; - Detect circular dependencies + ;; - Respect 8-hour workday limits + ;; - Skip weekends + ;; + ;; Supported BLOCKER types (standard EDNA format): + ;; - previous-sibling + ;; - ids(UUID) - e.g., ids(70d8f844-1952-43f0-8043-da51e8c8bc69) + ;; - ids("id:UUID") - e.g., ids("id:70d8f844-1952-43f0-8043-da51e8c8bc69") + ;; - parent + + (defun gortium/add-ids-to-subtree () + "Add IDs to all headings in current subtree." + (interactive) + (save-excursion + (org-back-to-heading t) + (org-map-entries + (lambda () (org-id-get-create)) + nil 'tree))) + + ;; 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 () @@ -1374,13 +1786,13 @@ Starting at `gortium/org-day-start-hour` (e.g., 9 means 09:00 AM)." :map org-mode-map :n "gF" #'gortium/org-open-link-in-other-frame) -(defvar my/main-frame nil +(defvar gortium/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)))) + (lambda () (setq gortium/main-frame (selected-frame)))) -(defun my--rightmost-non-minibuffer-window (frame) +(defun gortium--rightmost-non-minibuffer-window (frame) "Return the rightmost non-minibuffer window in FRAME, or nil if none." (let ((best-win nil) (best-right -1)) @@ -1392,7 +1804,7 @@ Starting at `gortium/org-day-start-hour` (e.g., 9 means 09:00 AM)." best-win w))))) best-win)) -(defun my--non-minibuffer-window-count (frame) +(defun gortium--non-minibuffer-window-count (frame) "Return number of non-minibuffer windows in FRAME." (let ((count 0)) (dolist (w (window-list frame)) @@ -1400,16 +1812,16 @@ Starting at `gortium/org-day-start-hour` (e.g., 9 means 09:00 AM)." (setq count (1+ count)))) count)) -(defun my/toggle-buffer-to-frame () - "Toggle the current buffer between `my/main-frame' and a new frame." +(defun gortium/toggle-buffer-to-frame () + "Toggle the current buffer between `gortium/main-frame' and a new frame." (interactive) - (unless (frame-live-p my/main-frame) - (setq my/main-frame (selected-frame))) + (unless (frame-live-p gortium/main-frame) + (setq gortium/main-frame (selected-frame))) (let* ((buf (current-buffer)) (old-frame (selected-frame)) (old-win (selected-window))) - (if (eq old-frame my/main-frame) + (if (eq old-frame gortium/main-frame) ;; === Move from main frame to new frame === (let ((new-frame (make-frame '((name . "Toggled Buffer"))))) (with-selected-frame new-frame @@ -1417,12 +1829,12 @@ Starting at `gortium/org-day-start-hour` (e.g., 9 means 09:00 AM)." (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) + (if (> (gortium--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))) + (with-selected-frame gortium/main-frame + (let ((target (gortium--rightmost-non-minibuffer-window gortium/main-frame))) (if target (with-selected-window target (condition-case nil @@ -1436,14 +1848,14 @@ Starting at `gortium/org-day-start-hour` (e.g., 9 means 09:00 AM)." ;; Close secondary frame or just the window (with-selected-frame old-frame - (if (= (my--non-minibuffer-window-count old-frame) 1) + (if (= (gortium--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/" +(defvar gortium/org-repo-projects "~/ExoKortex/2-Areas/Meta-Planning/Org/Projects" "Path to your main Org repository where real .org files are stored.") (defvar gortium/projects-root "~/ExoKortex/1-Projects/" @@ -1451,11 +1863,11 @@ Starting at `gortium/org-day-start-hour` (e.g., 9 means 09:00 AM)." (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." +The .org file is created in `gortium/org-repo-projects` 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-real (expand-file-name org-file gortium/org-repo-projects)) (org-link (expand-file-name org-file project-dir))) (make-directory project-dir t) (unless (file-exists-p org-real) @@ -1465,10 +1877,10 @@ The .org file is created in `gortium/org-repo` and symlinked into the project fo (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`." + "Create a symlinked org FILE-NAME in current dir, real file in `gortium/org-repo-projoects`." (interactive "sOrg file name (without .org): ") (let* ((org-file (concat file-name ".org")) - (org-real (expand-file-name org-file gortium/org-repo)) + (org-real (expand-file-name org-file gortium/org-repo-projects)) (org-link (expand-file-name org-file default-directory))) (unless (file-exists-p org-real) (with-temp-file org-real @@ -1477,15 +1889,84 @@ The .org file is created in `gortium/org-repo` and symlinked into the project fo (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`." + "Convert all marked org files in Dirvish/Dired to symlinks in `gortium/org-repo-projects`." (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))) + (org-real (expand-file-name file-name gortium/org-repo-projects))) (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)))))) + +(defun gortium/refile-to-today-daily () + "Refile the current subtree under the 'Journal' headline in today's daily note. +This version uses `org-roam-dailies-capture-today` to ensure the daily note +is created with Org-roam’s templates (including :ID:)." + (interactive) + (require 'org-roam-dailies) + + (let ((headline "Journal")) + + ;; Step 1: Ensure today’s daily exists via org-roam (guarantees :ID:) + (let ((daily-file (org-roam-dailies-capture-today nil))) + ;; Step 2: Cut the subtree (use org-copy-subtree if you prefer duplication) + (org-cut-subtree) + + ;; Step 3: Paste under the Journal headline + (with-current-buffer (find-file-noselect daily-file) + (save-excursion + (goto-char (point-min)) + (when (re-search-forward (concat "^\\*+ " (regexp-quote headline) "\\b") nil t) + (let ((parent-level (org-current-level))) + (end-of-line) + (insert "\n") + (org-paste-subtree (+ 1 parent-level)))))) + (message "Task refiled under '%s' in today’s daily note." headline)))) + +(defun gortium/refile-to-daily-of-close () + "Refile the current subtree under the 'Journal' headline in the daily note +corresponding to the task's CLOSED date. If no CLOSED property is found, +fallback to today's daily note. Ensures the daily has an Org-roam ID." + (interactive) + (require 'org-roam-dailies) + + ;; Step 1: Get CLOSED property timestamp + (let* ((closed-prop (org-entry-get (point) "CLOSED")) + (time (when closed-prop + (ignore-errors (org-time-string-to-time closed-prop)))) + (date (or time (current-time))) + (headline "Journal")) + + ;; Step 2: Ensure daily file exists using org-roam (this guarantees :ID: etc.) + (let ((daily-file (org-roam-dailies-capture-date date nil))) + ;; Step 3: Cut the subtree. + (org-cut-subtree) + + ;; Step 4: Paste into the proper daily file + (with-current-buffer (find-file-noselect daily-file) + (save-excursion + (goto-char (point-min)) + ;; Find the 'Journal' headline + (when (re-search-forward (concat "^\\*+ " (regexp-quote headline) "\\b") nil t) + (let ((parent-level (org-current-level))) + (end-of-line) + (insert "\n") + (org-paste-subtree (+ 1 parent-level)))))) + + (message "Task refiled under '%s' in %s" + headline + (file-name-nondirectory daily-file))))) + +;; Org-edna for advanced task dependencies +(use-package! org-edna + :after org + :config + (setq org-edna-use-inheritance t) + (org-edna-mode 1) + (map! :map org-mode-map + :localleader + :desc "Edit Edna rules" "E" #'org-edna-edit)) diff --git a/doom/.config/doom/init.el b/doom/.config/doom/init.el index e355f47..eb5db38 100644 --- a/doom/.config/doom/init.el +++ b/doom/.config/doom/init.el @@ -53,7 +53,8 @@ zen ; distraction-free coding or writing :editor - (evil +everywhere); come to the dark side, we have cookies + (evil +everywhere + +numbers); come to the dark side, we have cookies file-templates ; auto-snippets for empty files fold ; (nigh) universal code folding (format +onsave) ; automated prettiness @@ -70,7 +71,8 @@ (dired +dirvish + icons) ; making dired pretty [functional] electric ; smarter, keyword-based electric-indent eww ; the internet is gross - ;;ibuffer ; interactive buffer management + + (ibuffer +icons) ; interactive buffer management undo ; persistent, smarter undo for your inevitable mistakes vc ; version-control and Emacs, sitting in a tree @@ -109,7 +111,7 @@ :os (:if (featurep :system 'macos) macos) ; improve compatibility with macOS - ;;tty ; improve the terminal Emacs experience + tty ; improve the terminal Emacs experience :lang ;;agda ; types of types of types of types... @@ -179,7 +181,9 @@ (rust +lsp) ; Fe2O3.unwrap().unwrap().unwrap().unwrap() ;;scala ; java, but good ;;(scheme +guile) ; a fully conniving family of lisps - sh ; she sells {ba,z,fi}sh shells on the C xor + (sh ; she sells {ba,z,fi}sh shells on the C xor + +lsp + +powershell) ;;sml ;;solidity ; do you need a blockchain? No. ;;swift ; who asked for emoji variables? diff --git a/doom/.config/doom/packages.el b/doom/.config/doom/packages.el index ec3fb2b..a890e3c 100644 --- a/doom/.config/doom/packages.el +++ b/doom/.config/doom/packages.el @@ -52,6 +52,9 @@ ;; Open-With behavior (package! openwith) +;; Org-Superstart-mode +;; (package! org-superstar) + ;; Drag-stuff with alt-jk (package! drag-stuff) @@ -100,27 +103,43 @@ (package! peg) (package! citeproc) +;; For lifeOS +(package! json-rpc) + ;; (package! emigo :recipe ;; (:host github ;; :repo "MatthewZMD/emigo" :files (:defaults "*.py" "*.el") ;; ) ;; ) -;; (package! image-roll :recipe -;; (:host github -;; :repo "dalanicolai/image-roll.el")) +;; +(package! image-roll :recipe + (:host github + :repo "dalanicolai/image-roll.el" + :protocol ssh)) -;; (package! org-edna) +(package! org-edna) +;; elgant dependencies bellow: +(package! org-ql) +(package! ts) +(package! s) +(package! dash) +(package! elgantt + :recipe (:host github :repo "legalnonsense/elgantt")) (package! age) +(package! ultra-scroll + :recipe (:host github :repo "jdtsmith/ultra-scroll" :protocol ssh)) + (package! passage :recipe (:host github :repo "anticomputer/passage.el" :protocol ssh)) -(package! krl-mode - :recipe (:host nil - :repo "ssh://code.lazyworkhorse.net:2222/gortium/krl-mode.git" - :files ("*.el"))) +;; Server is down, deactivated for now +;; (package! krl-mode +;; :recipe (:host nil +;; :repo "ssh://code.lazyworkhorse.net:2222/gortium/krl-mode.git" +;; :files ("*.el"))) (package! auto-highlight-symbol) @@ -128,6 +147,8 @@ (package! all-the-icons) +(package! magit-gptcommit) + ;; To install a package directly from a remote git repo, you must specify a ;; ``:recipe' accepts here: ;; https://github.com/radian-software/straight.el#the-recipe-format diff --git a/hypr/.config/hypr/hyprland.conf b/hypr/.config/hypr/hyprland.conf index f737b5c..e95d1cd 100644 --- a/hypr/.config/hypr/hyprland.conf +++ b/hypr/.config/hypr/hyprland.conf @@ -14,6 +14,12 @@ source = ~/.cache/wal/colors-hyprland.conf ### MONITORS ### ################ +$LAPTOP=eDP-1 +$EXTERNAL_L1=DP-4 +$EXTERNAL_R1=DP-5 +$EXTERNAL_L2=DP-6 +$EXTERNAL_R2=DP-7 + # See https://wiki.hyprland.org/Configuring/Monitors/ # Useful command: # hyprctl monitors all @@ -21,18 +27,18 @@ source = ~/.cache/wal/colors-hyprland.conf # monitor=eDP-1,highres,auto,2 #3200x1800,0x0,2 # monitor=HDMI-A-2,2560x1600,auto,1 #1600x0,1 #x-370,1,transform,1 ## Ingenuity setup -monitor=eDP-1,1920x1080,0x0,1 -monitor=DP-3,1920x1080,1920x0,1 # HDMI screen +monitor=$LAPTOP, 1920x1080, 0x0, 1 +monitor=DP-3, 1920x1080, 1920x0, 1 # HDMI screen # Horizontal setup #monitor=DP-4,1920x1080,-960x-1080,1 # Left screen #monitor=DP-6,1920x1080,-960x-1080,1 # Left screen #monitor=DP-5,1920x1080,960x-1080,1 # Right screen #monitor=DP-7,1920x1080,960x-1080,1 # Right screen # Vertical setup -monitor=DP-4,1920x1080,0x-1080,1,transform,0 # Left screen -monitor=DP-6,1920x1080,0x-1080,1,transform,0 # Left screen -monitor=DP-5,1920x1080,1920x-1480,1,transform,1 # Right screen -monitor=DP-7,1920x1080,1920x-1480,1,transform,1 # Right screen +monitor=$EXTERNAL_L1, 1920x1080, 0x-1080, 1, transform, 0 # Left screen +monitor=$EXTERNAL_L2, 1920x1080,0x-1080,1,transform,0 # Left screen +monitor=$EXTERNAL_R1, 1920x1080, 1920x-1480, 1, transform, 1 # Right screen +monitor=$EXTERNAL_R2, 1920x1080,1920x-1480,1,transform,1 # Right screen ################### ### MY PROGRAMS ### @@ -42,7 +48,7 @@ monitor=DP-7,1920x1080,1920x-1480,1,transform,1 # Right screen # Set programs that you use $terminal = kitty -$fileManager = emacsclient -c -a emacs --eval '(dirvish "$HOME")' +$editor = emacsclient -c -a emacs $menu = wofi --show drun $zen = zen-browser $music = kitty ncspot @@ -61,6 +67,11 @@ exec = hyprshade auto exec-once = udiskie exec-once = waybar exec-once = swww-daemon +exec-once = swww img ~/.config/wallpapers/green_yellow_forest.jpg --outputs $LAPTOP +exec-once = swww img ~/.config/wallpapers/green_yellow_forest.jpg --outputs $EXTERNAL_L1 +exec-once = swww img ~/.config/wallpapers/green_yellow_forest.jpg --outputs $EXTERNAL_L2 +exec-once = swww img ~/.config/wallpapers/buck_head.jpg --outputs $EXTERNAL_R1 +exec-once = swww img ~/.config/wallpapers/buck_head.jpg --outputs $EXTERNAL_R2 exec-once = swaync exec-once = hypridle exec-once = hyprlock @@ -115,16 +126,6 @@ decoration { # Change transparency of focused and unfocused windows active_opacity = 0.9 inactive_opacity = 0.75 - - # https://wiki.hyprland.org/Configuring/Variables/#blur - blur { - enabled = false - # enabled = true - # size = 16 - # passes = 2 - # new_optimizations = true - } - layerrule = blur, waybar } # https://wiki.hyprland.org/Configuring/Variables/#animations @@ -135,13 +136,13 @@ animations { bezier = myBezier, 0.05, 0.9, 0.1, 1.05 - animation = windows, 1, 2, myBezier - animation = windowsOut, 1, 2, default, popin 80% - animation = border, 1, 2, default - animation = borderangle, 1, 2, default - animation = fade, 1, 2, default - animation = workspaces, 1, 2, default - animation = specialWorkspace, 1, 2, myBezier, slidefadevert -10% + animation = windows, 1, 1.6, myBezier + animation = windowsOut, 1, 1.6, default, popin 80% + animation = border, 1, 1.6, default + animation = borderangle, 1, 1.6, default + animation = fade, 1, 1.6, default + animation = workspaces, 1, 1.6, default + animation = specialWorkspace, 1, 1.6, myBezier, slidefadevert -10% } # See https://wiki.hyprland.org/Configuring/Dwindle-Layout/ for more @@ -207,9 +208,9 @@ $mainMod = SUPER # Sets "Windows" key as main modifier bind = $mainMod, T, exec, $terminal bind = $mainMod, D, killactive, bind = $mainMod SHIFT, Q, exit, -bind = $mainMod, U, exec, ~/ExoKortex/2-Areas/IT/config/scripts/make_all.sh +bind = $mainMod, U, exec, ~/ExoKortex/2-Areas/IT/dotfiles/scripts/make_all.sh bind = $mainMod, M, exec, $monitoring -bind = $mainMod, W, exec, $fileManager +bind = $mainMod, E, exec, $editor bind = $mainMod, V, togglefloating, bind = $mainMod, R, exec, $menu bind = $mainMod, Z, exec, $zen @@ -221,7 +222,7 @@ bind = $mainMod, X, swapwindow bind = $mainMod, B, exec, nerd-dictation begin bind = $mainMod, G, exec, nerd-dictation end bind = $mainMod, A, exec, $music -# + # Full screen bind = SUPER, F, fullscreen @@ -312,8 +313,12 @@ bindl = , XF86AudioPlay, exec, playerctl play-pause bindl = , XF86AudioPrev, exec, playerctl previous # Emacs-everywhere -bind = $mainMod, E, exec, emacsclient --eval '(thanos/type)' -windowrulev2 = float,title:^(emacs-float)$ +bind = $mainMod, W, exec, emacsclient --eval '(thanos/type)' +windowrule { + name = float-emacs + match:title = ^emacs-float$ + float = true +} # Play/Pause # bind = $mainMod, P, exec, playerctl play-pause @@ -344,13 +349,26 @@ windowrulev2 = float,title:^(emacs-float)$ # windowrule = float, ^(kitty)$ # Example windowrule v2 -# windowrulev2 = float,class:^(kitty)$,title:^(kitty)$ +# windowrule = float,class:^(kitty)$,title:^(kitty)$ # Ignore maximize requests from apps. You'll probably like this. -windowrulev2 = suppressevent maximize, class:.* +windowrule { + name = suppress-maximize-events + match:class = .* + suppress_event = maximize +} # Fix some dragging issues with XWayland -windowrulev2 = nofocus,class:^$,title:^$,xwayland:1,floating:1,fullscreen:0,pinned:0 +windowrule { + name = nofocus-xwayland-float + match:class = ^$ + match:title = ^$ + match:xwayland = true + match:float = true + match:fullscreen = false + match:pin = false + no_focus = true +} workspace = 1, name:short-1, monitor:DP-4 # Left screen workspace = 2, name:short-2, monitor:DP-4 # Left screen diff --git a/miryoku-kle-cover.png b/miryoku-kle-cover.png new file mode 100644 index 0000000..e0793c7 Binary files /dev/null and b/miryoku-kle-cover.png differ diff --git a/scripts/make_all.sh b/scripts/make_all.sh index d0f8c4e..4450fb5 100755 --- a/scripts/make_all.sh +++ b/scripts/make_all.sh @@ -1,5 +1,5 @@ #!/bin/bash -cd ~/ExoKortex/2-Areas/IT/config +cd ~/ExoKortex/2-Areas/IT/dotfiles/ make all if [ $? -eq 0 ]; then notify-send "Dotfiles" "Configuration reloaded successfully!" diff --git a/secrets/mistral.age b/secrets/mistral.age new file mode 100644 index 0000000..28139df --- /dev/null +++ b/secrets/mistral.age @@ -0,0 +1,10 @@ +-----BEGIN AGE ENCRYPTED FILE----- +YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IEdoTUQ4QSBKU2FK +SkJzTFhnVFJvY2Z2cHNwdkcySlEyM251bFNFajBqd25zeEljWTBFCjJnQnN3VGVU +WmxFa24zN3lTWDQ3aXRUemNKTTNUcWhTREFxdjYvUEVPUGsKLT4gOHUuYXpSLWdy +ZWFzZSBcbU9pIjZ5IHVRTUc0LiBkeytLRiUkWSAodzgvPjxbCnhkUDFQTVl3OG04 +bm9iTGJrQU1VMHVpRG40aWdGaXV5RVlkL3k3dytjbmVERSs1WWJGYwotLS0gZTFv +ZVhUNG01eFhBZUFOa2txbXo3VGJHUlJqd2lsMWJrZEJnOFFJMkg0bwod0MqLq/Gg +IscU4tieNTcpvdveTROZbzsDXmfpYuw7VMphifpW7HAori6WgTSuPuR64ger+Ckn +9DBFrTWBvOWlsg== +-----END AGE ENCRYPTED FILE----- diff --git a/secrets/tdnde-mail.age b/secrets/tdnde-mail.age new file mode 100644 index 0000000..2403d25 --- /dev/null +++ b/secrets/tdnde-mail.age @@ -0,0 +1,8 @@ +age-encryption.org/v1 +-> ssh-ed25519 GhMD8A N3YTw5zlKBrt8wFNUKsp5whWTA6gHxk/dnK0qfpVjWs +3Wl+FdDp7DUa41RM3WtfYD652FO+JrKHI0IbR8JP008 +-> Zz)frY-grease [#m +U4f90YQTuVBGpDosn311ZOxkvuIrVxRqtkLpdvspDpvJSBuGfjL6ocqutGnVAS+9 +9cd5Bqzpf82+z+IIIark +--- CCIsIOQ2lRmT3vKpYBeFU2PrFesiH5CPljUTzYARRVE +þED dàVß+¼'Ú+šå>5anͨc¡ÑØ­´Å²bï4š5©${Ý \ No newline at end of file diff --git a/wallpapers/.config/wallpapers/buck_head.jpg b/wallpapers/.config/wallpapers/buck_head.jpg new file mode 100644 index 0000000..2d2134d Binary files /dev/null and b/wallpapers/.config/wallpapers/buck_head.jpg differ diff --git a/yazi/.config/yazi/yazi.toml b/yazi/.config/yazi/yazi.toml index cb9a63c..c30c541 100644 --- a/yazi/.config/yazi/yazi.toml +++ b/yazi/.config/yazi/yazi.toml @@ -87,68 +87,6 @@ image_alloc = 536870912 # 512MB image_bound = [ 0, 0 ] suppress_preload = false -[plugin] -fetchers = [ - # Mimetype - { id = "mime", name = "*", run = "mime", prio = "high" }, -] -z = "zoxide query {args}" -spotters = [ - { name = "*/", run = "folder" }, - # Code - { mime = "text/*", run = "code" }, - { mime = "application/{mbox,javascript,wine-extension-ini}", run = "code" }, - # Image - { mime = "image/{avif,hei?,jxl,svg+xml}", run = "magick" }, - { mime = "image/*", run = "image" }, - # Video - { mime = "video/*", run = "video" }, - # Fallback - { name = "*", run = "file" }, -] -preloaders = [ - # Image - { mime = "image/{avif,hei?,jxl,svg+xml}", run = "magick" }, - { mime = "image/*", run = "image" }, - # Video - { mime = "video/*", run = "video" }, - # PDF - { mime = "application/pdf", run = "pdf" }, - # Font - { mime = "font/*", run = "font" }, - { mime = "application/ms-opentype", run = "font" }, -] -previewers = [ - { name = "*/", run = "folder", sync = true }, - # Code - { mime = "text/*", run = "code" }, - { mime = "application/{mbox,javascript,wine-extension-ini}", run = "code" }, - # JSON - { mime = "application/{json,ndjson}", run = "json" }, - # Image - { mime = "image/{avif,hei?,jxl,svg+xml}", run = "magick" }, - { mime = "image/*", run = "image" }, - # Video - { mime = "video/*", run = "video" }, - # PDF - { mime = "application/pdf", run = "pdf" }, - # Archive - { mime = "application/{zip,rar,7z*,tar,gzip,xz,zstd,bzip*,lzma,compress,archive,cpio,arj,xar,ms-cab*}", run = "archive" }, - { mime = "application/{debian*-package,redhat-package-manager,rpm,android.package-archive}", run = "archive" }, - { name = "*.{AppImage,appimage}", run = "archive" }, - # Virtual Disk / Disk Image - { mime = "application/{iso9660-image,qemu-disk,ms-wim,apple-diskimage}", run = "archive" }, - { mime = "application/virtualbox-{vhd,vhdx}", run = "archive" }, - { name = "*.{img,fat,ext,ext2,ext3,ext4,squashfs,ntfs,hfs,hfsx}", run = "archive" }, - # Font - { mime = "font/*", run = "font" }, - { mime = "application/ms-opentype", run = "font" }, - # Empty file - { mime = "inode/empty", run = "empty" }, - # Fallback - { name = "*", run = "file" }, -] - [input] cursor_blink = false diff --git a/zsh/.zshrc b/zsh/.zshrc index b49575e..0a652e4 100644 --- a/zsh/.zshrc +++ b/zsh/.zshrc @@ -1,16 +1,19 @@ # NO FUCKING BEEP unsetopt BEEP -# Run pywal only on WSL +# Dark GTK for pwvucontrol +export GTK_THEME=Adwaita:dark + +# Always run pywal (only on WSL) if grep -qE "(Microsoft|WSL)" /proc/version &> /dev/null && [ -z "$TMUX" ]; then cat ~/.cache/wal/sequences clear fi -# hledger +# Hledger export LEDGER_FILE=~/ExoKortex/2-Areas/Finances/finance_vault/tpouplier.hledger -# emacs client +# Emacs client alias emacs="emacsclient -c -a emacs" # Opencode @@ -27,7 +30,7 @@ function yy() { rm -f -- "$tmp" } -# nvim +# Nvim export LANG=en_CA.UTF-8 export LC_ALL=en_CA.UTF-8 export EDITOR="nvim" @@ -39,7 +42,7 @@ source <(fzf --zsh) autoload -Uz compinit && compinit zstyle ':completion:*' matcher-list 'm:{a-zA-Z}={A-Za-z}' -# bat +# Bat alias cat=bat # aichat @@ -48,7 +51,8 @@ alias aido="aichat -e" aitts() { paplay --raw --rate=22050 --format=s16le --channels=1 <(aichat "Answer only with speech. Your answer will be read outlound. No title, no bold or no special character. Only the short and direct answer. $*" | piper-tts --model en_US-amy-medium.onnx --output-raw) } -# tmux + +# Tmux alias t='tmux attach || tmux new' # Docker @@ -58,7 +62,7 @@ alias dpa="docker ps -a" alias dl="docker ps -l -q" alias dx="docker exec -it" -# cd +# Cd alias ..="cd .." alias ...="cd ../.." alias ....="cd ../../.." @@ -83,7 +87,7 @@ alias gr='git remote' alias gre='git reset' alias gcl='git clone' -# lazygit +# Lazygit alias lg="lazygit" # NVIM @@ -94,7 +98,7 @@ alias l="eza -l --icons --git -a" alias lt="eza --tree --level=2 --icons --git" alias ltree="eza --tree --level=2 --long --icons --git" -# navigation +# Navigation function cx() { cd "$@" && l; } function fcd() { cd "$(find . -type d -not -path '' | fzf)" && l; } #function f() { echo "$(find . -type f -not -path '*/.*' | fzf)" | pbcopy } @@ -112,10 +116,10 @@ eval "$(starship init zsh)" # Zoxide eval "$(zoxide init zsh)" -# hyprshot +# Hyprshot export HYPRSHOT_DIR="Images" -# direnv +# Direnv # eval "$(direnv hook zsh)" # Atuin