nftable-migration
1;;; init --- vdemeester's emacs configuration -*- lexical-binding: t -*-
2
3;; Copyright (C) 2025 Vincent Demeester
4;; Author: Vincent Demeester <vincent@sbr.pm>
5
6;; This file is NOT part of GNU Emacs.
7;;; Commentary:
8;; This is the "mini" version for now, but aims to become the default one.
9;;; Code:
10
11;;; Some constants I am using across the configuration.
12(defconst org-directory "~/desktop/org/"
13 "`org-mode' directory, where most of the org-mode file lives.")
14(defconst org-notes-directory (expand-file-name "notes" org-directory)
15 "`org-mode' notes directory, for notes obviously, most likely managed by denote.")
16(defconst org-inbox-file (expand-file-name "inbox.org" org-directory)
17 "`org-mode' inbox file, where we collect entries to be triaged.")
18(defconst org-todos-file (expand-file-name "todos.org" org-directory)
19 "`org-mode' file for TODOs. This is the main file for the org angenda entries.")
20(defconst org-habits-file (expand-file-name "habits.org" org-directory)
21 "`org-mode' file for habits. This is the file for habits that I need to
22share elsewhere, with Flat habits on iOS for example.")
23(defconst org-reading-list-file (expand-file-name "reading-list.org" org-directory)
24 "`org-mode' file for list of things to read.
25Most likely these needs to be added to readwise reader or ditch.")
26(defconst org-journelly-file (expand-file-name "Journelly.org" org-directory)
27 "`org-mode' file for journalling.
28It is shared with iOS and replace the deprecated `org-journal-file' below.")
29(defconst org-journal-file (expand-file-name "20250620T144103--journal__journal.org" org-notes-directory)
30 "`org-mode' journal file, for journal-ling.")
31(defconst org-archive-dir (expand-file-name "archive" org-directory)
32 "`org-mode' directory of archived files.")
33(defconst org-people-dir (expand-file-name "people" org-notes-directory)
34 "`org-mode' people files directory, most likely managed by denote.")
35
36;;; The configuration.
37
38;;; Quick access to certain key file using registers
39(set-register ?e `(file . ,(locate-user-emacs-file "init.el")))
40(set-register ?i `(file . ,org-inbox-file))
41(set-register ?t `(file . ,org-todos-file))
42(set-register ?j `(file . ,org-journal-file))
43(set-register ?o `(file . ,org-directory))
44(set-register ?n `(file . ,org-notes-directory))
45(set-register ?P `(file . ,org-people-dir))
46
47;;; Some GC optimizations
48(defun my-minibuffer-setup-hook ()
49 (setq gc-cons-threshold most-positive-fixnum))
50
51(defun my-minibuffer-exit-hook ()
52 (setq gc-cons-threshold 800000000))
53
54(setq gc-cons-threshold most-positive-fixnum)
55
56(run-with-idle-timer 1.2 t 'garbage-collect)
57
58(defconst emacs-start-time (current-time))
59
60(let ((minver 29))
61 (unless (>= emacs-major-version minver)
62 (error "Your Emacs is too old -- this configuration requires v%s or higher" minver)))
63
64(setq inhibit-default-init t) ; Disable the site default settings
65
66(setq confirm-kill-emacs #'y-or-n-p)
67
68(setq custom-file (locate-user-emacs-file "custom.el"))
69(setq
70 custom-buffer-done-kill nil ; Kill when existing
71 custom-buffer-verbose-help nil ; Remove redundant help text
72 custom-unlispify-tag-names nil ; Show me the real variable name
73 custom-unlispify-menu-entries nil)
74;; Create the custom-file if it doesn't exists
75(unless (file-exists-p custom-file)
76 (write-region "" nil custom-file))
77(load custom-file :no-error-if-file-is-missing)
78
79(setq echo-keystrokes 0.1) ;; display command keystrokes quickly
80
81(global-unset-key (kbd "C-z"))
82(global-unset-key (kbd "C-x C-z"))
83(global-unset-key (kbd "C-h h"))
84
85;; Disable owerwrite-mode, iconify-frame and diary
86(mapc
87 (lambda (command)
88 (put command 'disabled t))
89 '(overwrite-mode iconify-frame diary))
90;; And enable those commands (disabled by default)
91(mapc
92 (lambda (command)
93 (put command 'disabled nil))
94 '(list-timers narrow-to-region narrow-to-page upcase-region downcase-region))
95
96(unless noninteractive
97 (defconst font-height 130
98 "Default font-height to use.")
99 ;; 2024-10-05: Switching from Ubuntu Mono to Cascadia Mono
100 ;; 2024-96-06: Switching from Cascadia Mono to JetBrains Mono
101 (defconst font-family-mono "JetBrains Mono"
102 "Default monospace font-family to use.")
103 (defconst font-family-sans "Ubuntu Sans"
104 "Default sans font-family to use.")
105 ;; Middle/Near East: שלום, السّلام عليكم
106 (when (member "Noto Sans Arabic" (font-family-list))
107 (set-fontset-font t 'arabic "Noto Sans Arabic"))
108 (when (member "Noto Sans Hebrew" (font-family-list))
109 (set-fontset-font t 'arabic "Noto Sans Hebrew"))
110 ;; Africa: ሠላም
111 (when (member "Noto Sans Ethiopic" (font-family-list))
112 (set-fontset-font t 'ethiopic "Noto Sans Ethiopic"))
113
114 ;; If font-family-mono or font-family-sans are not available, use the default Emacs face
115 (set-face-attribute 'default nil
116 :family font-family-mono
117 :height font-height
118 :weight 'regular)
119 (set-face-attribute 'fixed-pitch nil
120 :family font-family-mono
121 :weight 'medium
122 :height font-height)
123 (set-face-attribute 'variable-pitch nil
124 :family font-family-sans
125 :weight 'regular)
126
127 (set-fontset-font t 'symbol "Apple Color Emoji")
128 (set-fontset-font t 'symbol "Noto Color Emoji" nil 'append)
129 (set-fontset-font t 'symbol "Segoe UI Emoji" nil 'append)
130 (set-fontset-font t 'symbol "Symbola" nil 'append)
131
132 (require 'modus-themes)
133 (setopt modus-themes-common-palette-overrides
134 `((border-mode-line-active unspecified)
135 (border-mode-line-inactive unspecified)
136 ,@modus-themes-preset-overrides-cooler)
137 modus-themes-to-rotate '(modus-operandi modus-vivendi)
138 modus-themes-mixed-fonts t
139 modus-themes-headings '((0 . (variable-pitch semilight 1.5))
140 (1 . (regular 1.4))
141 (2 . (regular 1.3))
142 (3 . (regular 1.2))
143 (agenda-structure . (variable-pitch light 2.2))
144 (agenda-date . (variable-pitch regular 1.3))
145 (t . (regular 1.15))))
146 ;; Default modus-operandi on GUI and modus-vivendi on CLI
147 (if (display-graphic-p)
148 (load-theme 'modus-operandi :no-confirm)
149 (load-theme 'modus-vivendi :no-confirm)))
150
151(setopt load-prefer-newer t) ; Always load newer compiled files
152(setopt ad-redefinition-action 'accept) ; Silence advice redefinition warnings
153;; (setopt debug-on-error t)
154(setopt byte-compile-debug t)
155
156;; Configure `use-package' prior to loading it.
157(eval-and-compile
158 (setq use-package-always-ensure nil)
159 (setq use-package-always-defer nil)
160 (setq use-package-always-demand nil)
161 (setq use-package-expand-minimally nil)
162 (setq use-package-enable-imenu-support t)
163 (setq use-package-compute-statistics t))
164
165(eval-when-compile
166 (require 'use-package))
167
168(require 'info) ;; XXX ensure the var exists even before loading `info.el'.
169
170(use-package init-func)
171;; TODO: do useful stuff with the macro instead
172(vde/run-and-delete-frame my-greet-and-close ()
173 "Displays a greeting and closes the frame after a short delay."
174 (message "Hello from a macro-defined function! Closing soon...")
175 (sit-for 2))
176
177(use-package emacs
178 :bind
179 ("C-x m" . mark-defun)
180 ("C-x C-b" . bs-show)
181 ("M-o" . other-window)
182 ("M-j" . duplicate-dwim)
183 ;; (:map completion-preview-active-mode-map
184 ;; ("M-n" . #'completion-preview-next-candidate)
185 ;; ("M-p" . #'completion-preview-prev-candidate))
186 :custom
187 (create-lockfiles nil) ; No backup files
188 (make-backup-files nil) ; No backup files
189 (backup-inhibited t) ; No backup files
190 (use-short-answers t "Use short answer y/n")
191 ;; (tab-always-indent 'complete)
192 ;; (tab-first-completion 'word-or-paren-or-punct)
193 (enable-local-variables :all)
194 ;; (select-enable-clipboard t)
195 ;; (select-enable-primary t)
196 (comment-multi-line t)
197 (make-backup-files nil)
198 (read-extended-command-predicate #'command-completion-default-include-p)
199 (mouse-autoselect 1)
200 (completion-cycle-threshold 2)
201 (completion-ignore-case t)
202 (completion-show-inline-help nil)
203 (completions-detailed t)
204 (enable-recursive-minibuffers t)
205 (read-buffer-completion-ignore-case t)
206 (read-file-name-completion-ignore-case t)
207 (find-ls-option '("-exec ls -ldh {} +" . "-ldh")) ; find-dired results with human readable sizes
208 (global-auto-revert-non-file-buffers t "Auto revert non-file buffers")
209 (switch-to-buffer-obey-display-actions t "Don't distuingish automatic and manual window switching")
210 (window-combination-resize t "Resize window proportionally")
211 (isearch-lazy-count t "Show size of search results")
212 (lazy-count-prefix-format "(%s/%s) " "Format of search results")
213 (lazy-highlight-initial-delay 0 "No delay before highlight search matches")
214 (isearch-allow-scroll t "Allow scrolling while searching")
215 (isearch-allow-motion t "Allow movement commands while searching")
216 :hook
217 (after-init . global-hl-line-mode)
218 (after-init . global-completion-preview-mode)
219 (after-init . auto-insert-mode)
220 (after-init . pixel-scroll-mode)
221 :config
222 (with-current-buffer "*Messages*" (emacs-lock-mode 'kill))
223 (with-current-buffer "*scratch*" (emacs-lock-mode 'kill))
224 (display-time-mode -1)
225 (tooltip-mode -1)
226 (blink-cursor-mode -1)
227 (setenv "GIT_EDITOR" (format "emacs --init-dir=%s " (shell-quote-argument user-emacs-directory)))
228 (setenv "EDITOR" (format "emacs --init-dir=%s " (shell-quote-argument user-emacs-directory)))
229 (delete-selection-mode 1)
230 (defun er-keyboard-quit ()
231 "Smater version of the built-in `keyboard-quit'.
232
233The generic `keyboard-quit' does not do the expected thing when
234the minibuffer is open. Whereas we want it to close the
235minibuffer, even without explicitly focusing it."
236 (interactive)
237 (if (active-minibuffer-window)
238 (if (minibufferp)
239 (minibuffer-keyboard-quit)
240 (abort-recursive-edit))
241 (keyboard-quit)))
242 (global-set-key [remap keyboard-quit] #'er-keyboard-quit))
243
244(use-package view
245 :commands (view-mode)
246 :custom
247 (view-read-only t "Enable view-mode when entering read-only")
248 :bind (("C-<escape>" . view-mode)
249 :map view-mode-map
250 ;; Navigation
251 ("n" . next-line)
252 ("p" . previous-line)
253 ("f" . forward-char)
254 ("b" . backward-char)
255
256 ;; Beginning/end of line
257 ("a" . beginning-of-line)
258 ("e" . end-of-line)
259
260 ;; Quick exit to edit mode
261 ("i" . View-exit)
262 ("j" . next-line)
263 ("k" . previous-line)
264 ("h" . backward-char)
265 ("l" . forward-char)
266
267 ;; Page movement
268 ("u" . (lambda()
269 (interactive)
270 (View-scroll-page-backward 3)))
271 ("d" . (lambda()
272 (interactive)
273 (View-scroll-page-forward 3)))
274
275 ;; Beginning/end of line (Vim style)
276 ("0" . beginning-of-line)
277 ("$" . end-of-line)
278
279 ;; Beginning/end of buffers
280 ("g" . beginning-of-buffer)
281 ("G" . end-of-buffer)
282
283 ;; Other bespoke bindings
284 (";" . other-window)
285
286 ("SPC" . nil))
287 :hook
288 ;; Visual feedback - box cursor in view mode, bar when editing
289 (view-mode . view-mode-hookee+)
290 :config
291 (defun view-mode-hookee+ ()
292 (setq cursor-type (if view-mode 'box 'bar))))
293
294(use-package tramp
295 :custom
296 ;; Tramp
297 (remote-file-name-inhibit-locks t "No lock files on remote files")
298 (tramp-use-scp-direct-remote-copying t "Use direct copying between two remote hosts")
299 (remote-file-name-inhibit-auto-save-visited t "Do not auto-save remote files")
300 (tramp-copy-size-limit (* 1024 1024)) ;; 1MB
301 (tramp-verbose 2)
302 :config
303 (connection-local-set-profile-variables
304 'remote-direct-async-process
305 '((tramp-direct-async-process . t)))
306
307 (connection-local-set-profiles
308 '(:application tramp :protocol "scp")
309 'remote-direct-async-process)
310
311 (setq magit-tramp-pipe-stty-settings 'pty)
312 (with-eval-after-load 'tramp
313 (with-eval-after-load 'compile
314 (remove-hook 'compilation-mode-hook #'tramp-compile-disable-ssh-controlmaster-options)))
315
316 (add-to-list 'tramp-remote-path 'tramp-own-remote-path)
317 (add-to-list 'tramp-remote-path "/home/vincent/.local/state/nix/profile/bin/")
318 (add-to-list 'tramp-remote-path "~/bin/")
319 ;; (add-to-list 'tramp-connection-properties
320 ;; (list (regexp-quote "/ssh:aomi.home:")
321 ;; "remote-shell" "/home/vincent/.local/state/nix/profile/bin/zsh"))
322 )
323
324(use-package passage
325 :commands (passage-get))
326
327(use-package find-file
328 :bind ("C-x C-g" . ff-find-other-file))
329
330(use-package icomplete
331 :unless noninteractive
332 :hook
333 (icomplete-minibuffer-setup
334 . (lambda()(interactive)
335 (setq-local completion-styles '(flex partial-completion initials basic))))
336 (after-init . fido-vertical-mode)
337 :custom
338 (icomplete-compute-delay 0.01))
339
340(use-package display-line-numbers
341 :unless noninteractive
342 :hook (prog-mode . display-line-numbers-mode)
343 :config
344 (setq-default display-line-numbers-type 'relative)
345 (defun vde/toggle-line-numbers ()
346 "Toggles the display of line numbers. Applies to all buffers."
347 (interactive)
348 (if (bound-and-true-p display-line-numbers-mode)
349 (display-line-numbers-mode -1)
350 (display-line-numbers-mode)))
351 :bind ("<f7>" . vde/toggle-line-numbers))
352
353(use-package kkp
354 :if (not (display-graphic-p))
355 :hook (after-init . global-kkp-mode))
356
357(use-package helpful
358 :unless noninteractive
359 :bind (("C-h f" . helpful-callable)
360 ("C-h F" . helpful-function)
361 ("C-h M" . helpful-macro)
362 ("C-c h S" . helpful-at-point)
363 ("C-h k" . helpful-key)
364 ("C-h v" . helpful-variable)
365 ("C-h C" . helpful-command)))
366
367(use-package flymake
368 :bind
369 ("C-c f b" . flymake-show-buffer-diagnostics)
370 (:map flymake-mode-map
371 ("M-n" . flymake-goto-next-error)
372 ("M-p" . flymake-goto-prev-error))
373 :hook
374 (prog-mode . flymake-mode))
375
376(use-package treesit-fold
377 :hook
378 (after-init . global-treesit-fold-mode)
379 :config
380 (setq treesit-fold-line-count-show t) ; Show line count in folded regions
381 (setq treesit-fold-line-count-format " <%d lines> ")
382 (global-set-key (kbd "C-c f f") 'treesit-fold-close)
383 (global-set-key (kbd "C-c f o") 'treesit-fold-open))
384
385(use-package scopeline
386 :hook prog-mode
387 :custom-face
388 (scopeline-face ((t (:height 0.8 :inherit shadow)))))
389
390(use-package aggressive-indent
391 :commands (aggressive-indent-mode)
392 :hook
393 (emacs-lisp-mode . aggressive-indent-mode))
394
395(use-package save-place
396 :defer 1
397 :config (save-place-mode 1))
398
399(use-package symbol-overlay
400 :custom
401 (symbol-overlay-idle-time 0.2)
402 :bind
403 ("M-s s i" . symbol-overlay-put)
404 ("M-N" . symbol-overlay-jump-next)
405 ("M-P" . symbol-overlay-jump-prev)
406 ("M-s s r" . symbol-overlay-rename)
407 ("M-s s c" . symbol-overlay-remove-all)
408 :hook
409 (prog-mode . symbol-overlay-mode))
410
411(use-package savehist
412 :unless noninteractive
413 :hook (after-init . savehist-mode)
414 :custom
415 (history-length 10000)
416 (savehist-save-minibuffer-history t)
417 (savehist-delete-duplicates t)
418 (savehist-autosave-interval 180)
419 (savehist-additional-variables '(extended-command-history
420 search-ring
421 regexp-search-ring
422 comint-input-ring
423 compile-history
424 last-kbd-macro
425 shell-command-history)))
426
427(use-package newcomment
428 :unless noninteractive
429 :custom
430 (comment-empty-lines t)
431 (comment-fill-column nil)
432 (comment-multi-line t)
433 (comment-style 'multi-line)
434 :config
435 (defun prot/comment-dwim (&optional arg)
436 "Alternative to `comment-dwim': offers a simple wrapper
437 around `comment-line' and `comment-dwim'.
438
439 If the region is active, then toggle the comment status of the
440 region or, if the major mode defines as much, of all the lines
441 implied by the region boundaries.
442
443 Else toggle the comment status of the line at point."
444 (interactive "*P")
445 (if (use-region-p)
446 (comment-dwim arg)
447 (save-excursion
448 (comment-line arg))))
449 :bind (("C-;" . prot/comment-dwim)
450 ("C-:" . comment-kill)
451 ("M-;" . comment-indent)
452 ("C-x C-;" . comment-box)))
453
454(use-package dired
455 :custom
456 (dired-hide-details-hide-information-lines 'nil)
457 (dired-kill-when-opening-new-dired-buffer 't)
458 (dired-dwim-target t)
459 :bind
460 (:map dired-mode-map
461 ("E" . wdired-change-to-wdired-mode)
462 ("l" . dired-find-file))
463 :custom
464 (dired-vc-rename-file t)
465 :hook
466 (dired-mode . dired-omit-mode)
467 (dired-mode . dired-hide-details-mode)
468 ;; (dired-mode . dired-sort-toggle-or-edit) ; I don't like the "default by date" behavior
469 )
470
471(use-package alert
472 :defer 2
473 :init
474 (defun alert-after-finish-in-background (buf str)
475 (when (or (not (get-buffer-window buf 'visible)) (not (frame-focus-state)))
476 (alert str :buffer buf)))
477 :config
478 (setq alert-default-style 'libnotify))
479
480(use-package elec-pair
481 :hook (after-init-hook . electric-pair-mode))
482
483(use-package uniquify
484 :custom
485 (uniquify-buffer-name-style 'forward)
486 (uniquify-strip-common-suffix t)
487 (uniquify-after-kill-buffer-p t))
488
489(use-package compile
490 :unless noninteractive
491 :commands (compile)
492 :custom
493 (compilation-always-kill t)
494 (compilation-scroll-output t)
495 (ansi-color-for-compilation-mode t)
496 :config
497 (add-hook 'compilation-finish-functions #'alert-after-finish-in-background))
498
499(use-package subword
500 :diminish
501 :hook (prog-mode-hook . subword-mode))
502
503;; Recentf
504(use-package recentf
505 :defer t
506 :hook
507 (after-nit . recentf-mode)
508 :bind (("C-x C-r" . recentf-open)))
509
510(use-package prog-mode
511 :hook
512 (prog-mode . eldoc-mode)
513 :custom
514 (eldoc-idle-delay 0.2))
515
516(use-package eglot
517 :bind
518 (:map eglot-mode-map
519 ("C-c e a" . eglot-code-actions)
520 ("C-c e r" . eglot-reconnect)
521 ("<f2>" . eglot-rename)
522 ("C-c e ?" . eldoc-print-current-symbol-info))
523 :custom
524 (eglot-autoshutdown t)
525 (eglot-confirm-server-initiated-edits nil)
526 :config
527 (add-to-list 'eglot-ignored-server-capabilities :documentHighlightProvider)
528 (add-to-list 'eglot-server-programs `(json-mode "vscode-json-language-server" "--stdio"))
529 (add-to-list 'eglot-server-programs '(nix-mode . ("nil")))
530 ;; (add-to-list 'eglot-server-programs
531 ;; '(go-mode . ("harper-ls" "--stdio")))
532 ;; (add-to-list 'eglot-server-programs
533 ;; '(text-mode . ("harper-ls" "--stdio")))
534 ;; (add-to-list 'eglot-server-programs
535 ;; '(org-mode . ("harper-ls" "--stdio")))
536 (add-to-list 'eglot-server-programs
537 '(markdown-mode . ("harper-ls" "--stdio")))
538 (setq-default eglot-workspace-configuration
539 '(
540 :gopls (
541 :usePlaceholders t
542 ;; See https://github.com/golang/tools/blob/master/gopls/doc/analyzers.md
543 :analyses (
544 :QF1006 t
545 :QF1007 t
546 :S1002 t
547 :S1005 t
548 :S1006 t
549 :S1008 t
550 :S1025 t
551 :SA1003 t
552 :SA1014 t
553 :SA1015 t
554 :SA1023 t
555 :SA1032 t
556 :SA2002 t
557 :SA4023 t
558 :SA4031 t
559 :SA5000 t
560 :SA5010 t
561 :SA5000 t
562 :SA6000 t
563 :SA6001 t
564 :SA6002 t
565 :SA6003 t
566 :SA9003 t
567 :SA9007 t
568 :ST1000 t
569 :ST1001 t
570 :ST1005 t
571 :ST1013 t
572 :ST1015 t
573 :ST1016 t
574 :ST1017 t
575 :ST1019 t
576 :ST1020 t
577 :ST1021 t
578 :ST1022 t
579 :ST1023 t
580 :shadow t
581 )
582 ;; See https://github.com/golang/tools/blob/master/gopls/doc/inlayHints.md
583 :hints (:constantValues t :compositeLiteralTypes t :compositeLiteralFields t))
584 :nil (
585 :formatting (:command ["nixfmt"])
586 :nix (
587 :maxMemoryMB 2560
588 :autoEvalInputs t
589 :nixpkgsInputName "nixpkgs"
590 )
591 )
592 :pylsp (
593 :configurationSources ["flake8"]
594 :plugins (:pycodestyle (:enabled nil)
595 :black (:enabled t)
596 :mccabe (:enabled nil)
597 :flake8 (:enabled t)))))
598 (defun eglot-format-buffer-on-save ()
599 (if (and (project-current) (eglot-managed-p))
600 (add-hook 'before-save-hook #'eglot-format-buffer nil 'local)
601 (remove-hook 'before-save-hook #'eglot-format-buffer 'local)))
602 (add-hook 'eglot-managed-mode-hook #'eglot-format-buffer-on-save)
603 :hook
604 ;; (before-save . gofmt-before-save)
605 ;; (before-save . eglot-format-buffer)
606 (nix-mode . eglot-ensure)
607 (nix-ts-mode . eglot-ensure)
608 (rust-mode . eglot-ensure)
609 (rust-ts-mode . eglot-ensure)
610 (python-mode . eglot-ensure)
611 (python-ts-mode . eglot-ensure)
612 (go-mode . eglot-ensure)
613 (go-ts-mode . eglot-ensure)
614 (sh-mode . eglot-ensure)
615 (sh-script-mode . eglot-ensure)
616 (org-mode . eglot-ensure)
617 (markdown-mode . eglot-ensure))
618
619(setq major-mode-remap-alist
620 '((python-mode . python-ts-mode)
621 (go-mode . go-ts-mode)))
622
623(use-package markdown-mode
624 :mode (("README\\.md\\'" . gfm-mode)
625 ("\\.md\\'" . markdown-mode)
626 ("\\.markdown\\'" . markdown-mode))
627 :hook ((markdown-mode . visual-line-mode)
628 (gfm-mode . visual-line-mode)))
629
630(use-package yaml-ts-mode
631 :mode "\\.ya?ml\\'"
632 :hook ((yaml-ts-mode . display-line-numbers-mode)
633 (yaml-ts-mode . outline-minor-mode)
634 (yaml-ts-mode . electric-pair-local-mode))
635 :config
636 (setq-local outline-regexp "^ *\\([A-Za-z0-9_-]*: *[>|]?$\\|-\\b\\)")
637 (font-lock-add-keywords
638 'yaml-ts-mode
639 '(("\\($(\\(workspaces\\|context\\|params\\)\.[^)]+)\\)" 1 'font-lock-constant-face prepend)
640 ("kind:\s*\\(.*\\)\n" 1 'font-lock-keyword-face prepend))))
641
642(use-package orgalist
643 :commands (orgalist-mode)
644 :hook ((markdown-mode . orgalist-mode)
645 (gfm-mode . orgalist-mode)))
646
647(use-package dockerfile-ts-mode
648 :mode (("Dockerfile\\'" . dockerfile-ts-mode)
649 ("\\.Dockerfile\\'" . dockerfile-ts-mode-map)
650 ("Containerfile\\'" . dockerfile-ts-mode)
651 ("\\.Containerfile\\'" . dockerfile-ts-mode-map)))
652
653(use-package go-ts-mode
654 :mode (("\\.go$" . go-ts-mode)
655 ("\\.go" . go-ts-mode)
656 ("\\.go\\'" . go-ts-mode))
657 :hook ((go-ts-mode . vde/go-mode-setup))
658 :config
659 (defun vde/go-mode-setup ()
660 "Setup for go-mode."
661 (setq-local ff-other-file-alist
662 '(("_test\\.go\\'" (".go"))
663 ("\\.go\\'" ("_test.go"))
664 ))))
665
666(use-package nix-ts-mode
667 :if (executable-find "nix")
668 :mode ("\\.nix\\'" "\\.nix.in\\'"))
669
670(use-package nix-drv-mode
671 :if (executable-find "nix")
672 :after nix-mode
673 :mode "\\.drv\\'")
674
675(use-package nix-shell
676 :if (executable-find "nix")
677 :after nix-mode
678 :commands (nix-shell-unpack nix-shell-configure nix-shell-build))
679
680(use-package nixpkgs-fmt
681 :if (executable-find "nix")
682 :after nix-ts-mode
683 :custom
684 (nixpkgs-fmt-command "nixfmt")
685 :config
686 (add-hook 'nix-ts-mode-hook 'nixpkgs-fmt-on-save-mode))
687
688(use-package minions
689 :hook (after-init . minions-mode)
690 :config
691 (add-to-list 'minions-prominent-modes 'flymake-mode))
692
693(use-package vundo
694 :bind (("M-u" . undo)
695 ("M-U" . undo-redo)
696 ("C-x u" . vundo)))
697
698(use-package vde-vcs
699 :commands (vde/gh-get-current-repo vde/vc-browse-remote)
700 :bind (("C-x v B" . vde/vc-browse-remote)))
701
702(use-package project-func
703 :commands (vde/project-magit-status vde/project-eat vde/project-vterm vde/project-run-in-vterm vde/project-try-local vde/open-readme))
704
705(use-package project
706 :commands (project-find-file project-find-regexp)
707 :custom
708 (project-switch-commands '((?f "File" project-find-file)
709 (?g "Grep" project-find-regexp)
710 (?d "Dired" project-dired)
711 (?b "Buffer" project-switch-to-buffer)
712 (?q "Query replace" project-query-replace-regexp)
713 (?m "Magit" vde/project-magit-status)
714 (?e "Eshell" project-eshell)
715 (?E "Eat" vde/project-eat)
716 (?s "Vterm" vde/project-vterm)
717 (?R "README" vde/open-readme)
718 (?g "Checkout GitHub PR" checkout-github-pr)))
719 (project-mode-line t)
720 (project-compilation-buffer-name-function 'project-prefixed-buffer-name)
721 (project-vc-extra-root-markers '(".project" "Cargo.toml" "pyproject.toml" "requirements.txt" "go.mod"))
722 :bind
723 ("C-x p v" . vde/project-magit-status)
724 ("C-x p s" . vde/project-vterm)
725 ("C-x p X" . vde/project-run-in-vterm)
726 ("C-x p E" . vde/project-eat)
727 ("C-x p G" . checkout-github-pr)
728 ("C-x p F" . flymake-show-project-diagnostics))
729
730;; TODO adapt this to my needs
731;; (defun tkj/vc-git-grep-current-line ()
732;; "Search Git project for the current line."
733;; (interactive)
734;; (let ((current-line (string-trim (thing-at-point 'line t))))
735;; (if (string-empty-p current-line)
736;; (message "No line in sight")
737;; (vc-git-grep current-line "*" (vc-git-root default-directory)))))
738;; (global-set-key (kbd "C-c p l") 'tkj/vc-git-grep-current-line)
739;;
740;; (defun tkj/vc-git-grep-symbol ()
741;; "Search Git project for the symbol at point. This could be any word or
742;; programming symbol, like a function or variable."
743;; (interactive)
744;; (let ((symbol (string-trim (thing-at-point 'symbol t))))
745;; (if (string-empty-p symbol)
746;; (message "You point at nothing")
747;; (vc-git-grep symbol "*" (vc-git-root default-directory)))))
748;; (global-set-key (kbd "C-c p s") 'tkj/vc-git-grep-symbol)
749
750(use-package magit
751 :unless noninteractive
752 :commands (magit-status magit-clone magit-pull magit-blame magit-log-buffer-file magit-log)
753 :bind (("C-c v c" . magit-commit)
754 ("C-c v C" . magit-checkout)
755 ("C-c v b" . magit-branch)
756 ("C-c v d" . magit-dispatch)
757 ("C-c v f" . magit-fetch)
758 ("C-c v g" . magit-blame)
759 ("C-c v l" . magit-log-buffer-file)
760 ("C-c v L" . magit-log)
761 ("C-c v p" . magit-pull)
762 ("C-c v P" . magit-push)
763 ("C-c v r" . magit-rebase)
764 ("C-c v s" . magit-stage)
765 ("C-c v v" . magit-status)
766 )
767 :custom
768 (magit-save-repository-buffers 'dontask)
769 (magit-refs-show-commit-count 'all)
770 (magit-branch-prefer-remote-upstream '("main"))
771 (magit-display-buffer-function #'magit-display-buffer-fullframe-status-v1)
772 (magit-bury-buffer-function #'magit-restore-window-configuration)
773 (magit-refresh-status-buffer nil "don't automatically refresh the status buffer after running a git command")
774 (magit-commit-show-diff nil "don't show the diff by default in the commit buffer. Use `C-c C-d' to display it")
775 (magit-branch-direct-configure nil "don't show git variables in magit branch")
776 :config
777 ;; cargo-culted from https://github.com/magit/magit/issues/3717#issuecomment-734798341
778 ;; valid gitlab options are defined in https://docs.gitlab.com/ee/user/project/push_options.html
779 ;;
780 ;; the second argument to transient-append-suffix is where to append
781 ;; to, not sure what -u is, but this works
782 (transient-append-suffix 'magit-push "-u"
783 '(1 "=s" "Skip gitlab pipeline" "--push-option=ci.skip"))
784 (transient-append-suffix 'magit-push "=s"
785 '(1 "=m" "Create gitlab merge-request" "--push-option=merge_request.create"))
786 (transient-append-suffix 'magit-push "=m"
787 '(1 "=o" "Set push option" "--push-option=")) ;; Will prompt, can only set one extra
788 )
789
790(use-package git-commit
791 :bind (:map git-commit-mode-map
792 ("C-c C-h" . git-commit-co-authored)))
793
794(use-package ediff
795 :commands (ediff ediff-files ediff-merge ediff3 ediff-files3 ediff-merge3)
796 :custom
797 (ediff-window-setup-function 'ediff-setup-windows-plain)
798 (ediff-split-window-function 'split-window-horizontally)
799 (ediff-diff-options "-w")
800 :hook
801 (ediff-after-quit-hook-internal . winner-undo))
802
803(use-package diff
804 :custom
805 (diff-default-read-only nil)
806 (diff-advance-after-apply-hunk t)
807 (diff-update-on-the-fly t)
808 (diff-refine 'font-lock)
809 (diff-font-lock-prettify nil)
810 (diff-font-lock-syntax nil))
811
812(use-package gitconfig-mode
813 :commands (gitconfig-mode)
814 :mode (("/\\.gitconfig\\'" . gitconfig-mode)
815 ("/\\.git/config\\'" . gitconfig-mode)
816 ("/git/config\\'" . gitconfig-mode)
817 ("/\\.gitmodules\\'" . gitconfig-mode)))
818
819(use-package gitignore-mode
820 :commands (gitignore-mode)
821 :mode (("/\\.gitignore\\'" . gitignore-mode)
822 ("/\\.git/info/exclude\\'" . gitignore-mode)
823 ("/git/ignore\\'" . gitignore-mode)))
824
825(use-package gitattributes-mode
826 :commands (gitattributes-mode)
827 :mode (("/\\.gitattributes" . gitattributes-mode)))
828
829(use-package diff-hl
830 :hook (find-file . diff-hl-mode)
831 :hook (prog-mode . diff-hl-mode)
832 :hook (magit-post-refresh . diff-hl-magit-post-refresh)
833 :bind
834 (:map diff-hl-command-map
835 ("n" . diff-hl-next-hunk)
836 ("p" . diff-hl-previous-hunk)
837 ("[" . nil)
838 ("]" . nil)
839 ("DEL" . diff-hl-revert-hunk)
840 ("<delete>" . diff-hl-revert-hunk)
841 ("SPC" . diff-hl-mark-hunk)
842 :map vc-prefix-map
843 ("n" . diff-hl-next-hunk)
844 ("p" . diff-hl-previous-hunk)
845 ("s" . diff-hl-stage-dwim)
846 ("DEL" . diff-hl-revert-hunk)
847 ("<delete>" . diff-hl-revert-hunk)
848 ("SPC" . diff-hl-mark-hunk))
849 :config
850 (put 'diff-hl-inline-popup-hide
851 'repeat-map 'diff-hl-command-map))
852
853(use-package diff-hl-inline-popup
854 :after (diff-hl))
855(use-package diff-hl-show-hunk
856 :after (diff-hl))
857
858(use-package diff-hl-dired
859 :after (diff-hl)
860 :hook (dired-mode . diff-hl-dired-mode))
861
862(use-package corfu
863 :custom
864 (corfu-auto 't)
865 (corfu-auto-delay 1)
866 (corfu-cycle 't)
867 (corfu-preselect 'prompt)
868 :bind
869 (:map corfu-map
870 ("TAB" . corfu-next)
871 ("C-c" . corfu-quit)
872 ([tab] . corfu-next)
873 ("S-TAB" . corfu-previous)
874 ([backtab] . corfu-previous))
875 :hook
876 (after-init . global-corfu-mode))
877
878(use-package corfu-history
879 :after (corfu)
880 :hook
881 (after-init . corfu-history-mode))
882
883(use-package corfu-popupinfo
884 :after corfu
885 :config
886 (corfu-popupinfo-mode 1))
887
888(use-package corfu-terminal
889 :unless (display-graphic-p)
890 :ensure t
891 :hook
892 (after-init . corfu-terminal-mode))
893
894(use-package envrc
895 :defer 2
896 :if (executable-find "direnv")
897 :bind (:map envrc-mode-map
898 ("C-c e" . envrc-command-map))
899 :custom
900 (envrc-remote t)
901 :config (envrc-global-mode))
902
903(use-package cape
904 :init
905 (add-hook 'completion-at-point-functions #'cape-dabbrev)
906 (add-hook 'completion-at-point-functions #'cape-file)
907 (add-hook 'completion-at-point-functions #'cape-elisp-block))
908
909(use-package winner
910 :unless noninteractive
911 :hook
912 (after-init . winner-mode))
913
914(use-package windmove
915 :bind
916 ("S-<up>" . windmove-up)
917 ("S-<left>" . windmove-left)
918 ("S-<right>" . windmove-right)
919 ("S-<down>" . windmove-down)
920 ("M-S-<up>" . windmove-swap-states-up)
921 ("M-S-<left>" . windmove-swap-states-left)
922 ("M-S-<right>" . windmove-swap-states-right)
923 ("M-S-<down>" . windmove-swap-states-down))
924
925(use-package window
926 :unless noninteractive
927 :commands (shrink-window-horizontally shrink-window enlarge-window-horizontally enlarge-window)
928 :bind (("S-C-<left>" . shrink-window-horizontally)
929 ("S-C-<right>" . enlarge-window-horizontally)
930 ("S-C-<down>" . shrink-window)
931 ("S-C-<up>" . enlarge-window)))
932
933;; Prefer ripgrep (rg) if present (instead of grep)
934(setq xref-search-program
935 (cond
936 ((or (executable-find "ripgrep")
937 (executable-find "rg"))
938 'ripgrep)
939 ((executable-find "ugrep")
940 'ugrep)
941 (t
942 'grep)))
943
944(use-package rg
945 :if (executable-find "rg")
946 :commands (rg rg-project rg-dwim)
947 :bind (("M-s r r" . rg)
948 ("M-s r p" . rg-project)
949 ("M-s r s" . rg-dwim))
950 :custom
951 (rg-group-result t)
952 (rg-hide-command t)
953 (rg-show-columns nil)
954 (rg-show-header t)
955 (rg-default-alias-fallback "all")
956 :config
957 (cl-pushnew '("tmpl" . "*.tmpl") rg-custom-type-aliases)
958 (cl-pushnew '("gotest" . "*_test.go") rg-custom-type-aliases)
959 (defun vde/rg-buffer-name ()
960 "Generate a rg buffer name from project if in one"
961 (let ((p (project-root (project-current))))
962 (if p
963 (format "rg: %s" (abbreviate-file-name p))
964 "rg")))
965 (setq rg-buffer-name #'vde/rg-buffer-name))
966
967(use-package wgrep
968 :unless noninteractive
969 :commands (wgrep-change-to-wgrep-mode)
970 :custom
971 (wgrep-auto-save-buffer t)
972 (wgrep-change-readonly-file t)
973 :bind (:map grep-mode-map
974 ("e" . wgrep-change-to-wgrep-mode)
975 ("C-x C-q" . wgrep-change-to-wgrep-mode)))
976
977(use-package abbrev
978 :ensure nil
979 :custom
980 (save-abbrevs nil)
981 :config
982 (define-abbrev-table 'global-abbrev-table
983 '(;; Arrows
984 ("ra" "→")
985 ("la" "←")
986 ("ua" "↑")
987 ("da" "↓")
988
989 ;; Emojis for context markers
990 ;; ("todo" "👷 TODO:")
991 ;; ("fixme" "🔥 FIXME:")
992 ;; ("note" "📎 NOTE:")
993 ;; ("hack" "👾 HACK:")
994 ("smile" "😄")
995 ("party" "🎉")
996 ("up" "☝️")
997 ("applause" "👏")
998 ("manyapplauses" "👏👏👏👏👏👏👏👏")
999 ("heart" "❤️")
1000
1001 ;; NerdFonts
1002 ("nerdfolder" " ")
1003 ("nerdgit" "")
1004 ("nerdemacs" "")
1005
1006 ;; Markdown
1007 ("cb" "```@\n\n```"
1008 (lambda () (search-backward "@") (delete-char 1)))
1009
1010 ;; ORG
1011 ("ocb" "#+BEGIN_SRC @\n\n#+END_SRC"
1012 (lambda () (search-backward "@") (delete-char 1))))))
1013
1014(use-package tempel
1015 :custom (tempel-path (expand-file-name "templates" user-emacs-directory))
1016 :bind (("M-+" . tempel-complete) ;; Alternative tempel-expand
1017 ("M-*" . tempel-insert)))
1018
1019(use-package consult
1020 :bind
1021 ("M-g M-g" . consult-goto-line)
1022 ("M-K" . consult-keep-lines)
1023 ("M-s M-b" . consult-buffer)
1024 ("M-s M-f" . consult-find)
1025 ("M-s M-g" . consult-grep)
1026 ("M-s M-r" . consult-ripgrep)
1027 ("M-s M-h" . consult-history)
1028 ("M-s M-l" . consult-line)
1029 ("M-s M-m" . consult-mark)
1030 ("M-s M-y" . consult-yank-pop)
1031 ("M-s M-s" . consult-outline))
1032
1033(use-package consult-vc-modified-files
1034 :bind
1035 ("C-c v ." . consult-vc-log-select-files)
1036 ("C-c v m" . consult-vc-modified-files))
1037
1038(use-package embark
1039 :unless noninteractive
1040 :commands (embark-act embark-dwim embark-prefix-help-command)
1041 :bind
1042 ("C-=" . embark-act)
1043 ("M-=" . embark-dwim)
1044 ("C-h b" . embark-bindings)
1045 ("C-h B" . embark-bindings-at-point)
1046 ("C-h M" . embark-bindings-in-keymap)
1047 (:map completion-list-mode-map
1048 ("=" . embark-act))
1049 :custom
1050 (prefix-help-command #'embark-prefix-help-command)
1051 (embark-indicators '(embark-minimal-indicator
1052 embark-highlight-indicator
1053 embark-isearch-highlight-indicator))
1054 (embark-cycle-key ".")
1055 (embark-help-key "?")
1056 :config
1057 (keymap-set embark-symbol-map "S i" #'symbol-overlay-put)
1058 (keymap-set embark-symbol-map "S c" #'symbol-overlay-remove-all)
1059 (keymap-set embark-symbol-map "S r" #'symbol-overlay-rename)
1060 (defun vde/short-github-link ()
1061 "Target a link at point of the for github:owner/repo#number"
1062 )
1063 )
1064
1065(use-package embark-consult
1066 :after (embark consult)
1067 :unless noninteractive
1068 :hook
1069 (embark-collect-mode . consult-preview-at-point-mode))
1070
1071(use-package consult-gh
1072 :after (consult)
1073 :custom
1074 (consult-gh-show-preview t)
1075 (consult-gh-preview-key "C-o")
1076 (consult-gh-large-file-warning-threshold 2500000)
1077 (consult-gh-default-interactive-command #'consult-gh-transient)
1078 (consult-gh-prioritize-local-folder nil)
1079 (consult-gh-group-dashboard-by :reason)
1080 ;;;; Optional
1081 (consult-gh-repo-preview-major-mode nil) ; show readmes in their original format
1082 (consult-gh-preview-major-mode 'org-mode) ; use 'org-mode for editing comments, commit messages, ...
1083 :config
1084 ;; Remember visited orgs and repos across sessions
1085 (add-to-list 'savehist-additional-variables 'consult-gh--known-orgs-list)
1086 (add-to-list 'savehist-additional-variables 'consult-gh--known-repos-list))
1087
1088;; (consult-gh-issue-list "tektoncd/pipeline -- --assignee \"@me\"" t)
1089;; (consult-gh-pr-list "tektoncd/pipeline -- --assignee \"@me\"" t)
1090
1091(use-package consult-gh-embark
1092 :after (embark consult)
1093 :config
1094 (consult-gh-embark-mode +1))
1095
1096(use-package pr-review
1097 :commands (pr-review pr-review-open pr-review-submit-review)
1098 :custom
1099 (pr-review-ghub-host "api.github.com")
1100 (pr-review-notification-include-read nil)
1101 (pr-review-notification-include-unsubscribed nil))
1102
1103(use-package pr-review-search
1104 :commands (pr-review-search pr-review-search-open pr-review-current-repository pr-review-current-repository-search)
1105 :config
1106 (defun pr-review-current-repository-search (query)
1107 "Run pr-review-search on the current repository."
1108 (interactive "sSearch query: ")
1109 (pr-review-search (format "is:pr archived:false is:open repo:%s %s" (vde/gh-get-current-repo) query)))
1110
1111 (defun pr-review-current-repository ()
1112 "Run pr-review-search on the current repository."
1113 (interactive)
1114 (pr-review-search (format "is:pr archived:false is:open repo:%s" (vde/gh-get-current-repo)))))
1115
1116(use-package jinx
1117 :hook (emacs-startup . global-jinx-mode)
1118 :bind (([remap ispell-word] . jinx-correct) ;; ("M-$" . jinx-correct)
1119 ("C-M-$" . jinx-languages)))
1120
1121(use-package eljira
1122 :commands (eljira)
1123 :ensure nil
1124 :load-path "~/src/github.com/sawwheet/eljira/"
1125 :custom
1126 (eljira-token (passage-get "redhat/issues/token/myji"))
1127 (eljira-username "vdemeest@redhat.com")
1128 (eljira-url "https://issues.redhat.com"))
1129
1130(use-package chatgpt-shell
1131 :commands (chatgpt-shell)
1132 :custom
1133 (chatgpt-shell-google-key (passage-get "ai/gemini/api_key"))
1134 (chatgpt-shell-openrouter-key (passage-get "ai/openroute/api_key"))
1135 (chatgpt-shell-deepseek-key (passage-get "ai/deepseek/api_key")))
1136
1137;; TODO window management
1138;; TODO ORG mode configuration (BIG one)
1139(defun ar/org-insert-link-dwim ()
1140 "Like `org-insert-link' but with personal dwim preferences."
1141 (interactive)
1142 (let* ((point-in-link (org-in-regexp org-link-any-re 1))
1143 (clipboard-url (when (string-match-p "^http" (current-kill 0))
1144 (current-kill 0)))
1145 (region-content (when (region-active-p)
1146 (buffer-substring-no-properties (region-beginning)
1147 (region-end)))))
1148 (cond ((and region-content clipboard-url (not point-in-link))
1149 (delete-region (region-beginning) (region-end))
1150 (insert (org-make-link-string clipboard-url region-content)))
1151 ((and clipboard-url (not point-in-link))
1152 (insert (org-make-link-string
1153 clipboard-url
1154 (read-string "title: "
1155 (with-current-buffer (url-retrieve-synchronously clipboard-url)
1156 (dom-text (car
1157 (dom-by-tag (libxml-parse-html-region
1158 (point-min)
1159 (point-max))
1160 'title))))))))
1161 (t
1162 (call-interactively 'org-insert-link)))))
1163
1164(use-package org
1165 :if (file-exists-p org-directory)
1166 :mode (("\\.org$" . org-mode)
1167 ("\\.org.draft$" . org-mode))
1168 :commands (org-agenda org-capture)
1169 :bind (("C-c o l" . org-store-link)
1170 ("C-c o r r" . org-refile)
1171 ;; ("C-c o r R" . vde/reload-org-refile-targets)
1172 ("C-c o a a" . org-agenda)
1173 ;; ("C-c o a r" . vde/reload-org-agenda-files)
1174 ;; ("C-c C-x i" . vde/org-clock-in-any-heading)
1175 ("C-c o s" . org-sort)
1176 ("C-c O" . org-open-at-point-global)
1177 ("<f12>" . org-agenda)
1178 :map org-mode-map
1179 ("C-c C-l" . ar/org-insert-link-dwim))
1180 :custom
1181 (org-use-speed-commands t)
1182 (org-special-ctrl-a/e t)
1183 (org-special-ctrl-k t)
1184 (org-hide-emphasis-markers t)
1185 (org-pretty-entities t)
1186 (org-ellipsis "…")
1187 (org-return-follows-link t)
1188 (org-todo-keywords '((sequence "STRT(s)" "NEXT(n)" "TODO(t)" "WAIT(w)" "|" "DONE(d!)" "CANX(c@/!)")))
1189 (org-todo-state-tags-triggers '(("CANX" ("CANX" . t))
1190 ("WAIT" ("WAIT" . t))
1191 (done ("WAIT"))
1192 ("TODO" ("WAIT") ("CANX"))
1193 ("NEXT" ("WAIT") ("CANX"))
1194 ("DONE" ("WAIT") ("CANX"))))
1195 (org-tag-alist
1196 '((:startgroup)
1197 ("Handson" . ?o)
1198 (:grouptags)
1199 ("Write" . ?w) ("Code" . ?c)
1200 (:endgroup)
1201
1202 (:startgroup)
1203 ("Handsoff" . ?f)
1204 (:grouptags)
1205 ("Read" . ?r) ("Watch" . ?W) ("Listen" . ?l)
1206 (:endgroup)))
1207 (org-log-done 'time)
1208 (org-log-redeadline 'time)
1209 (org-log-reschedule 'time)
1210 (org-log-into-drawer t)
1211 ;; https://jeffbradberry.com/posts/2025/05/orgmode-priority-cookies/
1212 ;; 1 2 and 3 are high, 4 is default, 5 is "hide / whenever or maybe never"
1213 (org-priority-highest 1)
1214 (org-priority-lowest 5)
1215 (org-priority-default 4)
1216 (org-list-demote-modify-bullet '(("+" . "-") ("-" . "+")))
1217 (org-agenda-file-regexp "^[a-zA-Z0-9-_]+.org$")
1218 (org-agenda-files `(,org-inbox-file ,org-todos-file ,org-habits-file ,org-reading-list-file))
1219 ;; (org-refile-targets '((org-agenda-files :maxlevel . 3)))
1220 (org-refile-targets (vde/org-refile-targets))
1221 (org-refile-use-outline-path 'file)
1222 (org-refile-allow-creating-parent-nodes 'confirm)
1223 (org-agenda-remove-tags t)
1224 (org-agenda-span 'day)
1225 (org-agenda-start-on-weekday 1)
1226 (org-agenda-window-setup 'current-window)
1227 (org-agenda-sticky t)
1228 (org-agenda-sorting-strategy
1229 '((agenda time-up deadline-up scheduled-up todo-state-up priority-down)
1230 (todo todo-state-up priority-down deadline-up)
1231 (tags todo-state-up priority-down deadline-up)
1232 (search todo-state-up priority-down deadline-up)))
1233 (org-agenda-custom-commands
1234 '(
1235 ;; Archive tasks
1236 ("#" "To archive" todo "DONE|CANX")
1237 ;; TODO take inspiration from those
1238 ;; ("$" "Appointments" agenda* "Appointments")
1239 ;; ("b" "Week tasks" agenda "Scheduled tasks for this week"
1240 ;; ((org-agenda-category-filter-preset '("-RDV")) ; RDV for Rendez-vous
1241 ;; (org-agenda-use-time-grid nil)))
1242 ;;
1243 ;; ;; Review started and next tasks
1244 ;; ("j" "STRT/NEXT" tags-todo "TODO={STRT\\|NEXT}")
1245 ;;
1246 ;; ;; Review other non-scheduled/deadlined to-do tasks
1247 ;; ("k" "TODO" tags-todo "TODO={TODO}+DEADLINE=\"\"+SCHEDULED=\"\"")
1248 ;;
1249 ;; ;; Review other non-scheduled/deadlined pending tasks
1250 ;; ("l" "WAIT" tags-todo "TODO={WAIT}+DEADLINE=\"\"+SCHEDULED=\"\"")
1251 ;;
1252 ;; ;; Review upcoming deadlines for the next 60 days
1253 ;; ("!" "Deadlines all" agenda "Past/upcoming deadlines"
1254 ;; ((org-agenda-span 1)
1255 ;; (org-deadline-warning-days 60)
1256 ;; (org-agenda-entry-types '(:deadline))))
1257
1258 ("d" "Daily Agenda"
1259 ((agenda ""
1260 ((org-agenda-span 'day)
1261 (org-deadline-warning-days 5)))
1262 (tags-todo "+PRIORITY=\"1\""
1263 ((org-agenda-overriding-header "High Priority Tasks")))
1264 (todo "NEXT"
1265 ((org-agenda-overriding-header "Next Tasks")))))
1266 ("D" "Daily Agenda (old)"
1267 ((agenda ""
1268 ((org-agenda-files (vde/all-org-agenda-files))
1269 (org-agenda-span 'day)
1270 (org-deadline-warning-days 5)))
1271 (tags-todo "+PRIORITY=\"A\""
1272 ((org-agenda-files (vde/all-org-agenda-files))
1273 (org-agenda-overriding-header "High Priority Tasks")))
1274 (todo "NEXT"
1275 ((org-agenda-files (vde/all-org-agenda-files))
1276 (org-agenda-overriding-header "Next Tasks")))))
1277 ("i" "Inbox (triage)"
1278 ((tags-todo ".*"
1279 ((org-agenda-files `(,org-inbox-file)) ;; FIXME use constant here
1280 (org-agenda-overriding-header "Unprocessed Inbox Item")))))
1281 ("A" "All (old)"
1282 ((tags-todo ".*"
1283 ((org-agenda-files (vde/all-org-agenda-files))))))
1284 ("u" "Untagged Tasks"
1285 ((tags-todo "-{.*}"
1286 ((org-agenda-overriding-header "Untagged tasks")))))
1287 ("w" "Weekly Review"
1288 ((agenda ""
1289 ((org-agenda-overriding-header "Completed Tasks")
1290 (org-agenda-skip-function '(org-agenda-skip-entry-if 'nottodo 'done))
1291 (org-agenda-span 'week)))
1292 (agenda ""
1293 ((org-agenda-overriding-header "Unfinished Scheduled Tasks")
1294 (org-agenda-skip-function '(org-agenda-skip-entry-if 'todo 'done))
1295 (org-agenda-span 'week)))))
1296 ;; FIXME Should only take into account projects and areas ?
1297 ("R" "Review projects" tags-todo "-CANX/"
1298 ((org-agenda-overriding-header "Reviews Scheduled")
1299 (org-agenda-skip-function 'org-review-agenda-skip)
1300 (org-agenda-cmp-user-defined 'org-review-compare)
1301 (org-agenda-sorting-strategy '(user-defined-down))))))
1302 ;; TODO cleanup this list a bit
1303 (org-agenda-category-icon-alist `(("personal" ,(list (propertize "🏡")))
1304 ("work" ,(list (propertize "🏢")))
1305 ("appointments" ,(list (propertize "📅")))
1306 ("health" ,(list (propertize "⚕️")))
1307 ("systems" ,(list (propertize "🖥️")))
1308 ("journal" ,(list (propertize "📝")))
1309 ("project--" ,(list (propertize "💼" )))
1310 ("tekton", (list (propertize "😼")))
1311 ("openshift-pipelines", (list (propertize "🎩")))
1312 ("redhat", (list (propertize "🎩")))
1313 ("area--" ,(list (propertize"🏢" )))
1314 ("area--home" ,(list (propertize "🏡")))
1315 ("home" ,(list (propertize "🏡")))
1316 ("home-services" ,(list (propertize "☕ ")))
1317 ("email" ,(list (propertize"📨" )))
1318 ("people" ,(list (propertize"👤" )))
1319 ("machine" ,(list (propertize "🖥️")))
1320 ("website" ,(list (propertize "🌍")))
1321 ("bike" ,(list (propertize "🚴♂️")))
1322 ("security" ,(list (propertize "🛡️")))
1323 ("i*" ,(list (propertize "📒")))))
1324 (org-agenda-prefix-format '((agenda . " %i %?-12t% s") ;; " %i %-12:c%?-12t%s%(my/org-agenda-repeater)"
1325 (todo . " %i")
1326 (tags . " %i")
1327 (search . " %i")))
1328 ;; (org-agenda-scheduled-leaders '("Sched." "S.%2dx"))
1329 ;; (org-agenda-deadline-leaders '("Deadl." "In%2dd" "D.%2dx"))
1330 (org-insert-heading-respect-content t)
1331 (org-M-RET-may-split-line '((default . nil)))
1332 (org-goto-interface 'outline-path-completion)
1333 (org-outline-path-complete-in-steps nil)
1334 (org-goto-max-level 2)
1335 :hook
1336 (org-mode . auto-fill-mode)
1337 :bind
1338 (:map org-mode-map
1339 ("C-<left>" . org-shiftleft)
1340 ("C-<right>" . org-shiftright)
1341 ("C-<up>" . org-shiftup)
1342 ("C-<down>" . org-shiftdown))
1343 :config
1344 (unbind-key "S-<left>" org-mode-map)
1345 (unbind-key "S-<right>" org-mode-map)
1346 (unbind-key "S-<up>" org-mode-map)
1347 (unbind-key "S-<down>" org-mode-map)
1348 (unbind-key "M-S-<left>" org-mode-map)
1349 (unbind-key "M-S-<right>" org-mode-map)
1350 (unbind-key "M-S-<up>" org-mode-map)
1351 (unbind-key "M-S-<down>" org-mode-map)
1352 (unbind-key "C-S-<left>" org-mode-map)
1353 (unbind-key "C-S-<right>" org-mode-map)
1354 (unbind-key "C-S-<up>" org-mode-map)
1355 (unbind-key "C-S-<down>" org-mode-map)
1356
1357 (defun my/org-agenda-repeater ()
1358 "Return the repeater string for the current agenda entry."
1359 (if (org-before-first-heading-p)
1360 "-------" ; Align with the time grid
1361 (format "%5s: " (or (org-get-repeat) ""))))
1362
1363 (require 'dash)
1364 (require 's)
1365 (defun vde/org-refile-targets ()
1366 (append '((org-inbox-file :level . 0)
1367 (org-todos-file :maxlevel . 3)
1368 (org-habits-file :maxlevel . 3))
1369 (->>
1370 (directory-files org-notes-directory nil ".org$")
1371 (--remove (s-starts-with? "." it))
1372 (--remove (s-contains? "==readwise=" it))
1373 (--map (format "%s/%s" org-notes-directory it))
1374 (--map `(,it :maxlevel . 3)))
1375 )))
1376
1377(use-package org-appear
1378 :hook (org-mode . org-appear-mode))
1379
1380(use-package org-agenda
1381 :after org
1382 :commands (org-agenda)
1383 :config
1384 (unbind-key "S-<left>" org-agenda-mode-map)
1385 (unbind-key "S-<right>" org-agenda-mode-map)
1386 (unbind-key "S-<up>" org-agenda-mode-map)
1387 (unbind-key "S-<down>" org-agenda-mode-map)
1388 (unbind-key "C-S-<left>" org-agenda-mode-map)
1389 (unbind-key "C-S-<right>" org-agenda-mode-map))
1390
1391;; Make sure we load org-protocol
1392(use-package org-protocol
1393 :after org)
1394
1395(use-package org-tempo
1396 :after (org)
1397 :custom
1398 (org-structure-template-alist '(("a" . "aside")
1399 ("c" . "center")
1400 ("C" . "comment")
1401 ("e" . "example")
1402 ("E" . "export")
1403 ("Ea" . "export ascii")
1404 ("Eh" . "export html")
1405 ("El" . "export latex")
1406 ("q" . "quote")
1407 ("s" . "src")
1408 ("se" . "src emacs-lisp")
1409 ("sE" . "src emacs-lisp :results value code :lexical t")
1410 ("sg" . "src go")
1411 ("sr" . "src rust")
1412 ("sp" . "src python")
1413 ("v" . "verse"))))
1414
1415(use-package org-capture
1416 :commands (org-capture)
1417 :bind (("C-c o c" . org-capture))
1418 :config
1419 (add-to-list 'org-capture-templates
1420 `("J" "🗞 Journal entry" item
1421 (file+datetree ,org-journal-file)
1422 "%U %?\n%i")
1423 t)
1424 (add-to-list 'org-capture-templates
1425 `("j" "🗞 Journelly" entry
1426 (file ,org-journelly-file)
1427 "* %U @ %^{Hostname}\n%?" :prepend t)
1428 t) ;; FIXME:
1429 (add-to-list 'org-capture-templates
1430 `("t" "📥 Tasks")
1431 t)
1432 (add-to-list 'org-capture-templates
1433 `("tt" " New task" entry
1434 (file ,org-inbox-file)
1435 "* TODO %?\n:PROPERTIES:\n:CREATED:\t%U\n:END:\n\n%i\n\nFrom: %a"
1436 :empty-lines 1)
1437 t)
1438 (add-to-list 'org-capture-templates
1439 `("tl" " New task (from capture)" entry
1440 (file ,org-inbox-file)
1441 "* TODO %a\n:PROPERTIES:\n:CREATED:\t%U\n:END:\n\n%i\n\nFrom: %a"
1442 :empty-lines 1)
1443 t)
1444 (add-to-list 'org-capture-templates
1445 `("td" "✅ Done" entry
1446 (file ,org-inbox-file)
1447 "* DONE %?\n:PROPERTIES:\n:CREATED:\t%U\n:END:\n\n%i\n\nFrom: %a"
1448 :empty-lines 1)
1449 t)
1450 ;; Refine this
1451 (add-to-list 'org-capture-templates
1452 `("tr" " PR Review" entry
1453 (file ,org-inbox-file)
1454 "* TODO review gh:%^{issue} :review:\n:PROPERTIES:\n:CREATED:%U\n:END:\n\n%i\n%?\nFrom: %a"
1455 :empty-lines 1)
1456 t)
1457 (add-to-list 'org-capture-templates
1458 `("l" "🔗 Link" entry
1459 (file ,org-inbox-file)
1460 "* %a\n%U\n%?\n%i"
1461 :empty-lines 1)
1462 t)
1463 (add-to-list 'org-capture-templates
1464 `("m" "✉ Email Workflow")
1465 t)
1466 (add-to-list 'org-capture-templates
1467 `("mf" "Follow Up" entry
1468 (file ,org-inbox-file)
1469 "* TODO Follow up with %:from on %a\nSCHEDULED:%t\nDEADLINE: %(org-insert-time-stamp (org-read-date nil t \"+2d\"))\n\n%i"
1470 :immediate-finish t)
1471 t)
1472 (add-to-list 'org-capture-templates
1473 `("mr" "Read Later" entry
1474 (file ,org-inbox-file)
1475 "* TODO Read %:subject\nSCHEDULED:%t\nDEADLINE: %(org-insert-time-stamp (org-read-date nil t \"+2d\"))\n\n%a\n\n%i" :immediate-finish t)
1476 t)
1477 (add-to-list 'org-capture-templates
1478 `("J" "🗞 Journal (antidated) entry" item
1479 (file+datetree+prompt ,org-journal-file)
1480 "%U %?\n%i")
1481 t)
1482 ;; TODO: refine this, create a function that reset this
1483 ;; emails
1484 ;; (add-to-list 'org-capture-templates
1485 ;; `("m" "Meeting notes" entry
1486 ;; (file+datetree ,org-meeting-notes-file)
1487 ;; (file ,(concat user-emacs-directory "/etc/orgmode/meeting-notes.org"))))
1488
1489 (defun vde/window-delete-popup-frame (&rest _)
1490 "Kill selected selected frame if it has parameter `prot-window-popup-frame'.
1491Use this function via a hook."
1492 (when (frame-parameter nil 'vde/window-popup-frame)
1493 (delete-frame)))
1494
1495 ;; (add-to-list 'org-capture-templates
1496 ;; `("w" "Writing"))
1497 (add-hook 'org-capture-after-finalize-hook #'vde/window-delete-popup-frame))
1498
1499(use-package org-habit
1500 :after org
1501 :custom
1502 (org-habit-show-habits-only-for-today nil)
1503 (org-habit-graph-column 80))
1504
1505(use-package ob-ditaa
1506 :after org
1507 :commands (org-babel-execute:ditaa)
1508 :config
1509 (setq org-ditaa-jar-path "/home/vincent/local/state/nix/profiles/home-manager/home-path/lib/ditaa.jar"))
1510
1511(use-package ob-dot
1512 :after org
1513 :commands (org-babel-execute:dot))
1514
1515(use-package xeft
1516 :commands (xeft)
1517 :custom
1518 (xeft-directory org-notes-directory)
1519 (xeft-recursive 'follow-symlinks)
1520 (xeft-extensions '("md" "org")))
1521
1522(use-package denote
1523 :commands (denote)
1524 :bind (("C-c n c" . denote-region)
1525 ("C-c n i" . denote-link-or-create)
1526 ("C-c n b" . denote-backlinks)
1527 ("C-c n F f" . denote-find-link)
1528 ("C-c n F b" . denote-find-backlink))
1529 :custom
1530 (denote-directory org-notes-directory)
1531 (denote-rename-buffer-format "📝 %t")
1532 (denote-date-prompt-denote-date-prompt-use-org-read-date t)
1533 (denote-prompts '(title keywords))
1534 (denote-backlinks-display-buffer-action
1535 '((display-buffer-reuse-window
1536 display-buffer-in-side-window)
1537 (side . bottom)
1538 (slot . 99)
1539 (window-width . 0.3)
1540 (dedicated . t)
1541 (preserve-size . (t . t))))
1542 :hook (dired-mode . denote-dired-mode)
1543 :config
1544 (denote-rename-buffer-mode 1)
1545 (defun my-denote-always-rename-on-save-based-on-front-matter ()
1546 "Rename the current Denote file, if needed, upon saving the file.
1547Rename the file based on its front matter, checking for changes in the
1548title or keywords fields.
1549
1550Add this function to the `after-save-hook'."
1551 (let ((denote-rename-confirmations nil)
1552 (denote-save-buffers t)) ; to save again post-rename
1553 (when (and buffer-file-name (denote-file-is-note-p buffer-file-name))
1554 (ignore-errors (denote-rename-file-using-front-matter buffer-file-name))
1555 (message "Buffer saved; Denote file renamed"))))
1556
1557 (add-hook 'after-save-hook #'my-denote-always-rename-on-save-based-on-front-matter)
1558
1559 (defun vde/org-category-from-buffer ()
1560 "Get the org category (#+category:) value from the buffer"
1561 (cond
1562 ((string-match "__journal.org$" (buffer-file-name))
1563 "journal")
1564 (t
1565 (denote-sluggify (denote--retrieve-title-or-filename (buffer-file-name) 'org))))))
1566
1567(use-package denote-org
1568 :after (denote org)
1569 :defer 2)
1570
1571(use-package popper
1572 :commands (popper-mode)
1573 :bind (("C-#" . popper-toggle)
1574 ;; ("C-~" . popper-kill-latest-popup)
1575 ("M-#" . popper-cycle)
1576 ;; alt keybind for mac mode, possibly disposable? TODO: should bind only on mac?
1577 ("C-`" . popper-toggle-latest)
1578 ("C-M-#" . popper-toggle-type))
1579 :custom
1580 (popper-reference-buffers
1581 '("\\*Messages\\*"
1582 "\\*Warnings\\*"
1583 "Output\\*$"
1584 "\\*Async Shell Command\\*"
1585 help-mode
1586 compilation-mode))
1587 (popper-window-height 0.3)
1588 :config
1589 (popper-mode))
1590
1591(use-package popper-echo
1592 :commands (popper-echo-mode popper-tab-line-mode)
1593 :init
1594 (popper-tab-line-mode))
1595
1596(use-package mu4e
1597 :commands (mu4e)
1598 :custom
1599 (mu4e-mu-home "/home/vincent/.local/cache/mu")
1600 (mu4e-context-policy 'pick-first)
1601 (mu4e-change-filenames-when-moving t)
1602 (mu4e-attachment-dir "~/desktop/downloads")
1603 :config
1604 (setq mu4e-get-mail-command (concat (executable-find "mbsync") " --all"))
1605 (setq mu4e-update-interval 1800) ; 30m
1606
1607 (defun vde-mu4e--mark-get-copy-target ()
1608 "Ask for a copy target, and propose to create it if it does not exist."
1609 (let* ((target (mu4e-ask-maildir "Copy message to: "))
1610 (target (if (string= (substring target 0 1) "/")
1611 target
1612 (concat "/" target)))
1613 (fulltarget (mu4e-join-paths (mu4e-root-maildir) target)))
1614 (when (mu4e-create-maildir-maybe fulltarget)
1615 target)))
1616
1617 (defun copy-message-to-target(docid msg target)
1618 (let (
1619 (new_msg_path nil) ;; local variable
1620 (msg_flags (mu4e-message-field msg :flags))
1621 )
1622 ;; 1. target is already determined interactively when executing the mark (:ask-target)
1623
1624 ;; 2. Determine the path for the new file: we use mu4e~draft-message-filename-construct from
1625 ;; mu4e-draft.el to create a new random filename, and append the original's msg_flags
1626 (setq new_msg_path (format "%s/%s/cur/%s" mu4e-maildir target (mu4e~draft-message-filename-construct
1627 (mu4e-flags-to-string msg_flags))))
1628
1629 ;; 3. Copy the message using file system call (copy-file) to new_msg_path:
1630 ;; (See e.g. mu4e-draft.el > mu4e-draft-open > resend)
1631 (copy-file (mu4e-message-field msg :path) new_msg_path)
1632
1633 ;; 4. Add the information to the database (may need to update current search query with 'g' if duplicating to current box. Try also 'V' to toggle the display of duplicates)
1634 (mu4e~proc-add new_msg_path (mu4e~mark-check-target target))
1635 )
1636 )
1637
1638 (defun vde-mu4e--refile (msg)
1639 "Refile function to smartly move `MSG' to a given folder."
1640 (cond
1641 ;; FIXME
1642 ((string= (plist-get (car-safe (mu4e-message-field msg :cc)) :email) "ci_activity@noreply.github.com")
1643 "/icloud/Deleted Messages")
1644 (t
1645 (let ((year (format-time-string "%Y" (mu4e-message-field msg :date))))
1646 (format "/icloud/Archives/%s" year)))))
1647
1648 (setq
1649 mu4e-headers-draft-mark '("D" . "💈")
1650 mu4e-headers-flagged-mark '("F" . "📍")
1651 mu4e-headers-new-mark '("N" . "🔥")
1652 mu4e-headers-passed-mark '("P" . "❯")
1653 mu4e-headers-replied-mark '("R" . "❮")
1654 mu4e-headers-seen-mark '("S" . "☑")
1655 mu4e-headers-trashed-mark '("T" . "💀")
1656 mu4e-headers-attach-mark '("a" . "📎")
1657 mu4e-headers-encrypted-mark '("x" . "🔒")
1658 mu4e-headers-signed-mark '("s" . "🔑")
1659 mu4e-headers-unread-mark '("u" . "⎕")
1660 mu4e-headers-list-mark '("l" . "🔈")
1661 mu4e-headers-personal-mark '("p" . "👨")
1662 mu4e-headers-calendar-mark '("c" . "📅"))
1663
1664 (setopt mu4e-completing-read-function completing-read-function)
1665 (setq mu4e-refile-folder 'vde-mu4e--refile)
1666 (setq mu4e-contexts `( ,(make-mu4e-context
1667 :name "icloud"
1668 :match-func (lambda (msg) (when msg
1669 (string-prefix-p "/icloud" (mu4e-message-field msg :maildir))))
1670 :vars '(
1671 (user-mail-address . "vincent@demeester.fr")
1672 (mu4e-trash-folder . "/icloud/Deleted Messages")
1673 (mu4e-sent-folder . "/icloud/Sent Messages")
1674 (mu4e-draft-folder . "/icloud/Drafts")
1675 ;; (mu4e-get-mail-command . "mbsync icloud")
1676 ))
1677 ,(make-mu4e-context
1678 :name "gmail"
1679 :match-func (lambda (msg) (when msg
1680 (string-prefix-p "/gmail" (mu4e-message-field msg :maildir))))
1681 :vars '(
1682 (user-mail-address . "vinc.demeester@gmail.com")
1683 (mu4e-drafts-folder . "/gmail/[Gmail]/Drafts")
1684 (mu4e-sent-folder . "/gmail/[Gmail]/Sent Mail")
1685 ;; (mu4e-refile-folder . "/gmail/[Gmail]/All Mail")
1686 (mu4e-trash-folder . "/gmail/[Gmail]/Trash")
1687 ;; (mu4e-get-mail-command . "mbsync gmail")
1688 ))
1689 ,(make-mu4e-context
1690 :name "redhat"
1691 :match-func (lambda (msg) (when msg
1692 (string-prefix-p "/redhat" (mu4e-message-field msg :maildir))))
1693 :vars '(
1694 (user-mail-address . "vdemeest@redhat.com")
1695 (mu4e-drafts-folder . "/redhat/[Gmail]/Drafts")
1696 (mu4e-sent-folder . "/redhat/[Gmail]/Sent Mail")
1697 ;; (mu4e-refile-folder . "/redhat/[Gmail]/All Mail")
1698 (mu4e-trash-folder . "/redhat/[Gmail]/Trash")
1699 ;; (mu4e-get-mail-command . "mbsync redhat")
1700 ))
1701 ))
1702 (add-to-list 'mu4e-bookmarks
1703 '( :name "All Inboxes"
1704 :query "maildir:/icloud/INBOX OR maildir:/gmail/INBOX OR maildir:/redhat/INBOX"
1705 :key ?b))
1706 (with-eval-after-load "mm-decode"
1707 (add-to-list 'mm-discouraged-alternatives "text/html")
1708 (add-to-list 'mm-discouraged-alternatives "text/richtext")))
1709
1710;; (use-package whisper
1711;; :commands (whisper-run whisper-file)
1712;; :custom
1713;; (whisper-install-whispercpp nil))
1714;; TODO gptel configuration (and *maybe* copilot)
1715
1716(use-package goose
1717 :commands (goose-transient goose-start-session)
1718 :bind (("C-c a G" . goose-transient)))
1719
1720(use-package mcp
1721 :commands (mcp-hub-start-all-server)
1722 :after gptel
1723 :custom (mcp-hub-servers
1724 `(("jira"
1725 :command "/home/vincent/src/github.com/chmouel/jayrah/.venv/bin/jayrah"
1726 :args ("mcp"))
1727 ("github"
1728 :command "github-mcp-server"
1729 :args ("stdio")
1730 :env (:GITHUB_PERSONAL_ACCESS_TOKEN ,(passage-get "github/vdemeester/github-mcp-server")))
1731 ("playwright"
1732 :command "npx @playwright/mcp@latest"
1733 :args ("--executable-path", "/run/current-system/sw/bin/chromium"))))
1734 :config (require 'mcp-hub))
1735
1736(use-package gptel
1737 :commands (gptel gptel-mode)
1738 :bind (("C-c a g" . gptel))
1739 :hook
1740 (gptel-mode . visual-line-mode)
1741 :bind
1742 (:map gptel-mode-map
1743 ("C-c C-k" . gptel-abort)
1744 ("C-c C-m" . gptel-menu)
1745 ("C-c C-c" . gptel-send))
1746 :custom
1747 (gptel-default-mode #'markdown-mode)
1748 :config
1749 (require 'gptel-curl)
1750 (require 'gptel-gemini)
1751 (require 'gptel-ollama)
1752 (require 'gptel-transient)
1753 (require 'gptel-integrations)
1754 (require 'gptel-rewrite)
1755 (require 'gptel-org)
1756 (require 'gptel-openai)
1757 (require 'gptel-openai-extras)
1758 (require 'gptel-autoloads)
1759 (gptel-mcp-connect)
1760
1761 (setq gptel-model 'gemini-2.5-flash
1762 gptel-backend (gptel-make-gemini "Gemini"
1763 :key (passage-get "ai/gemini/api_key"))
1764 )
1765
1766 (gptel-make-gemini "Gemini Red Hat"
1767 :key (passage-get "redhat/google/osp/vdeemest-api-key"))
1768
1769 (gptel-make-openai "MistralLeChat"
1770 :host "api.mistral.ai/v1"
1771 :endpoint "/chat/completions"
1772 :protocol "https"
1773 :key (passage-get "ai/mistralai/api_key")
1774 :models '("mistral-small"))
1775
1776 (gptel-make-openai "OpenRouter"
1777 :host "openrouter.ai"
1778 :endpoint "/api/v1/chat/completions"
1779 :stream t
1780 :key (passage-get "ai/openroute/api_key")
1781 :models '(cognittivecomputations/dolphin3.0-mistral-24b:free
1782 cognitivecomputations/dolphin3.0-r1-mistral-24b:free
1783 deepseek/deepseek-r1-zero:free
1784 deepseek/deepseek-chat:free
1785 deepseek/deepseek-r1-distill-qwen-32b:free
1786 deepseek/deepseek-r1-distill-llama-70b:free
1787 google/gemini-2.0-flash-lite-preview-02-05:free
1788 google/gemini-2.0-pro-exp-02-05:free
1789 google/gemini-2.5-pro-exp-03-25:free
1790 google/gemma-3-12b-it:free
1791 google/gemma-3-27b-it:free
1792 google/gemma-3-4b-it:free
1793 mistralai/mistral-small-3.1-24b-instruct:free
1794 open-r1/olympiccoder-32b:free
1795 qwen/qwen2.5-vl-3b-instruct:free
1796 qwen/qwen-2.5-coder-32b-instruct:free
1797 qwen/qwq-32b:free
1798 codellama/codellama-70b-instruct
1799 google/gemini-pro
1800 google/palm-2-codechat-bison-32k
1801 meta-llama/codellama-34b-instruct
1802 mistralai/mixtral-8x7b-instruct
1803 openai/gpt-3.5-turbo))
1804
1805 ;; TODO: configure shikoku/kobe ollama instances here
1806 ;; (gptel-make-ollama "Ollama"
1807 ;; :host "localhost:11434"
1808 ;; :stream t
1809 ;; :models '("smollm:latest"
1810 ;; "llama3.1:latest"
1811 ;; "deepseek-r1:latest"
1812 ;; "mistral-small:latest"
1813 ;; "deepseek-r1:7b"
1814 ;; "nomic-embed-text:latest"))
1815 )
1816
1817(use-package acp
1818 :load-path "/home/vincent/src/github.com/xenodium/acp.el/")
1819
1820(use-package agent-shell
1821 :commands (agent-shell agent-shell-toggle)
1822 :load-path "/home/vincent/src/github.com/xenodium/agent-shell/"
1823 :config
1824 (setq agent-shell-google-authentication
1825 (agent-shell-google-make-authentication :api-key (passage-get "redhat/google/osp/vdeemest-api-key")))
1826 (setq agent-shell-anthropic-claude-environment
1827 (agent-shell-make-environment-variables
1828 "CLAUDE_CODE_USE_VERTEX" "1"
1829 "CLOUD_ML_REGION" "us-east5"
1830 "ANTHROPIC_VERTEX_PROJECT_ID" "itpc-gcp-pnd-pe-eng-claude")))
1831
1832(use-package devdocs
1833 :commands (devdocs-lookup devdocs-install vde/install-devdocs)
1834 :bind (("C-h D" . devdocs-lookup))
1835 :config
1836 (defun vde/install-devdocs ()
1837 "Install the devdocs I am using the most."
1838 (interactive)
1839 (dolist (docset '("bash"
1840 "c"
1841 "click"
1842 "cpp"
1843 "css"
1844 "elisp"
1845 "flask"
1846 "git"
1847 "gnu_make"
1848 "go"
1849 "html"
1850 "htmx"
1851 "http"
1852 "javascript"
1853 "jq"
1854 "jquery"
1855 "kubectl"
1856 "kubernetes"
1857 "lua~5.4"
1858 "nix"
1859 "python~3.13"
1860 "python~3.12"
1861 "requests"
1862 "sqlite"
1863 "terraform"
1864 "werkzeug"
1865 "zig"))
1866 (devdocs-install docset))))
1867
1868(use-package ready-player
1869 :config
1870 (ready-player-mode +1))
1871
1872(defvar highlight-codetags-keywords
1873 '(("\\<\\(TODO\\|FIXME\\|BUG\\|XXX\\)\\>" 1 font-lock-warning-face prepend)
1874 ("\\<\\(NOTE\\|HACK\\)\\>" 1 font-lock-doc-face prepend)))
1875
1876(define-minor-mode highlight-codetags-local-mode
1877 "Highlight codetags like TODO, FIXME..."
1878 :global nil
1879 (if highlight-codetags-local-mode
1880 (font-lock-add-keywords nil highlight-codetags-keywords)
1881 (font-lock-remove-keywords nil highlight-codetags-keywords))
1882
1883 ;; Fontify the current buffer
1884 (when (bound-and-true-p font-lock-mode)
1885 (if (fboundp 'font-lock-flush)
1886 (font-lock-flush)
1887 (with-no-warnings (font-lock-fontify-buffer)))))
1888
1889(add-hook 'prog-mode-hook #'highlight-codetags-local-mode)
1890
1891(defun vde/wtype-text (text)
1892 "Process TEXT for wtype, handling newlines properly."
1893 (let* ((has-final-newline (string-match-p "\n$" text))
1894 (lines (split-string text "\n"))
1895 (last-idx (1- (length lines))))
1896 (string-join
1897 (cl-loop for line in lines
1898 for i from 0
1899 collect (cond
1900 ;; Last line without final newline
1901 ((and (= i last-idx) (not has-final-newline))
1902 (format "wtype -s 50 \"%s\""
1903 (replace-regexp-in-string "\"" "\\\\\"" line)))
1904 ;; Any other line
1905 (t
1906 (format "wtype -s 50 \"%s\" && wtype -k Return"
1907 (replace-regexp-in-string "\"" "\\\\\"" line)))))
1908 " && ")))
1909
1910(define-minor-mode vde/type-mode
1911 "Minor mode for inserting text via wtype."
1912 :keymap `((,(kbd "C-c C-c") . ,(lambda () (interactive)
1913 (call-process-shell-command
1914 (vde/wtype-text (buffer-string))
1915 nil 0)
1916 (delete-frame)))
1917 (,(kbd "C-c C-k") . ,(lambda () (interactive)
1918 (kill-buffer (current-buffer))))))
1919
1920(defun vde/type ()
1921 "Launch a temporary frame with a clean buffer for typing."
1922 (interactive)
1923 (let ((frame (make-frame '((name . "emacs-float")
1924 (fullscreen . 0)
1925 (undecorated . t)
1926 (width . 70)
1927 (height . 20))))
1928 (buf (get-buffer-create "emacs-float")))
1929 (select-frame frame)
1930 (switch-to-buffer buf)
1931 (with-current-buffer buf
1932 (erase-buffer)
1933 ;; (org-mode)
1934 (markdown-mode) ;; more common ?
1935 (flyspell-mode)
1936 (vde/type-mode)
1937 (setq-local header-line-format
1938 (format " %s to insert text or %s to cancel."
1939 (propertize "C-c C-c" 'face 'help-key-binding)
1940 (propertize "C-c C-k" 'face 'help-key-binding)))
1941 ;; Make the frame more temporary-like
1942 (set-frame-parameter frame 'delete-before-kill-buffer t)
1943 (set-window-dedicated-p (selected-window) t))))
1944
1945(defun vde/agenda ()
1946 "Launch a frame with the org-agenda and the `org-todos-file' buffer."
1947 (interactive)
1948 (let ((frame (make-frame '((name . "emacs-org-agenda")))))
1949 (select-frame frame)
1950 (org-agenda nil "d")
1951 (split-window-horizontally)
1952 (other-window 1)
1953 (find-file org-todos-file)))
1954
1955(defun memoize-remote (key cache orig-fn &rest args)
1956 "Memoize a value if the key is a remote path."
1957 (if (and key
1958 (file-remote-p key))
1959 (if-let ((current (assoc key (symbol-value cache))))
1960 (cdr current)
1961 (let ((current (apply orig-fn args)))
1962 (set cache (cons (cons key current) (symbol-value cache)))
1963 current))
1964 (apply orig-fn args)))
1965;; Memoize current project
1966(defvar project-current-cache nil)
1967(defun memoize-project-current (orig &optional prompt directory)
1968 (memoize-remote (or directory
1969 project-current-directory-override
1970 default-directory)
1971 'project-current-cache orig prompt directory))
1972
1973(advice-add 'project-current :around #'memoize-project-current)
1974
1975;; Memoize magit top level
1976(defvar magit-toplevel-cache nil)
1977(defun memoize-magit-toplevel (orig &optional directory)
1978 (memoize-remote (or directory default-directory)
1979 'magit-toplevel-cache orig directory))
1980(advice-add 'magit-toplevel :around #'memoize-magit-toplevel)
1981
1982;; memoize vc-git-root
1983(defvar vc-git-root-cache nil)
1984(defun memoize-vc-git-root (orig file)
1985 (let ((value (memoize-remote (file-name-directory file) 'vc-git-root-cache orig file)))
1986 ;; sometimes vc-git-root returns nil even when there is a root there
1987 (when (null (cdr (car vc-git-root-cache)))
1988 (setq vc-git-root-cache (cdr vc-git-root-cache)))
1989 value))
1990(advice-add 'vc-git-root :around #'memoize-vc-git-root)
1991
1992;; BIND this
1993(defun mu-date-at-point (date)
1994 "Insert current DATE at point via `completing-read'."
1995 (interactive
1996 (let* ((formats '("%Y%m%d" "%F" "%Y%m%d%H%M" "%Y-%m-%dT%T"))
1997 (vals (mapcar #'format-time-string formats))
1998 (opts
1999 (lambda (string pred action)
2000 (if (eq action 'metadata)
2001 '(metadata (display-sort-function . identity))
2002 (complete-with-action action vals string pred)))))
2003 (list (completing-read "Insert date: " opts nil t))))
2004 (insert date))
2005
2006(provide 'init)
2007;;; init.el ends here