system-manager-wakasu
   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 xeft
1506  :commands (xeft)
1507  :custom
1508  (xeft-directory org-notes-directory)
1509  (xeft-recursive 'follow-symlinks)
1510  (xeft-extensions '("md" "org")))
1511
1512(use-package denote
1513  :commands (denote)
1514  :bind (("C-c n c" . denote-region)
1515	 ("C-c n i" . denote-link-or-create)
1516	 ("C-c n b" . denote-backlinks)
1517	 ("C-c n F f" . denote-find-link)
1518	 ("C-c n F b" . denote-find-backlink))
1519  :custom
1520  (denote-directory org-notes-directory)
1521  (denote-rename-buffer-format "📝 %t")
1522  (denote-date-prompt-denote-date-prompt-use-org-read-date t)
1523  (denote-prompts '(title keywords))
1524  (denote-backlinks-display-buffer-action
1525   '((display-buffer-reuse-window
1526      display-buffer-in-side-window)
1527     (side . bottom)
1528     (slot . 99)
1529     (window-width . 0.3)
1530     (dedicated . t)
1531     (preserve-size . (t . t))))
1532  :hook (dired-mode . denote-dired-mode)
1533  :config
1534  (denote-rename-buffer-mode 1)
1535  (defun my-denote-always-rename-on-save-based-on-front-matter ()
1536    "Rename the current Denote file, if needed, upon saving the file.
1537Rename the file based on its front matter, checking for changes in the
1538title or keywords fields.
1539
1540Add this function to the `after-save-hook'."
1541    (let ((denote-rename-confirmations nil)
1542          (denote-save-buffers t)) ; to save again post-rename
1543      (when (and buffer-file-name (denote-file-is-note-p buffer-file-name))
1544	(ignore-errors (denote-rename-file-using-front-matter buffer-file-name))
1545	(message "Buffer saved; Denote file renamed"))))
1546
1547  (add-hook 'after-save-hook #'my-denote-always-rename-on-save-based-on-front-matter)
1548  
1549  (defun vde/org-category-from-buffer ()
1550    "Get the org category (#+category:) value from the buffer"
1551    (cond
1552     ((string-match "__journal.org$" (buffer-file-name))
1553      "journal")
1554     (t
1555      (denote-sluggify (denote--retrieve-title-or-filename (buffer-file-name) 'org))))))
1556
1557(use-package denote-org
1558  :after (denote org)
1559  :defer 2)
1560
1561(use-package popper
1562  :commands (popper-mode)
1563  :bind (("C-#"   . popper-toggle)
1564	 ;; ("C-~"   . popper-kill-latest-popup)
1565         ("M-#"   . popper-cycle)
1566	 ;; alt keybind for mac mode, possibly disposable? TODO: should bind only on mac?
1567	 ("C-`"   . popper-toggle-latest)
1568         ("C-M-#" . popper-toggle-type))
1569  :custom
1570  (popper-reference-buffers
1571   '("\\*Messages\\*"
1572     "\\*Warnings\\*"
1573     "Output\\*$"
1574     "\\*Async Shell Command\\*"
1575     help-mode
1576     compilation-mode))
1577  (popper-window-height 0.3)
1578  :config
1579  (popper-mode))
1580
1581(use-package popper-echo
1582  :commands (popper-echo-mode popper-tab-line-mode)
1583  :init
1584  (popper-tab-line-mode))
1585
1586(use-package mu4e
1587  :commands (mu4e)
1588  :custom
1589  (mu4e-mu-home "/home/vincent/.local/cache/mu")
1590  (mu4e-context-policy 'pick-first)
1591  (mu4e-change-filenames-when-moving t)
1592  (mu4e-attachment-dir "~/desktop/downloads")
1593  :config
1594  (setq mu4e-get-mail-command (concat (executable-find "mbsync") " --all"))
1595  (setq mu4e-update-interval 1800) ; 30m
1596  
1597  (defun vde-mu4e--mark-get-copy-target ()
1598    "Ask for a copy target, and propose to create it if it does not exist."
1599    (let* ((target (mu4e-ask-maildir "Copy message to: "))
1600           (target (if (string= (substring target 0 1) "/")
1601                       target
1602                     (concat "/" target)))
1603           (fulltarget (mu4e-join-paths (mu4e-root-maildir) target)))
1604      (when (mu4e-create-maildir-maybe fulltarget)
1605	target)))
1606
1607  (defun copy-message-to-target(docid msg target)
1608    (let (
1609          (new_msg_path nil) ;; local variable                                                                
1610          (msg_flags (mu4e-message-field msg :flags))                                                                                                       
1611          )                                                                                                   
1612      ;; 1. target is already determined interactively when executing the mark (:ask-target)                     
1613
1614      ;; 2. Determine the path for the new file: we use mu4e~draft-message-filename-construct from            
1615      ;; mu4e-draft.el to create a new random filename, and append the original's msg_flags                   
1616      (setq new_msg_path (format "%s/%s/cur/%s" mu4e-maildir target (mu4e~draft-message-filename-construct    
1617								     (mu4e-flags-to-string msg_flags))))                                                                     
1618
1619      ;; 3. Copy the message using file system call (copy-file) to new_msg_path:                              
1620      ;; (See e.g. mu4e-draft.el > mu4e-draft-open > resend)                                             
1621      (copy-file (mu4e-message-field msg :path) new_msg_path)                                                 
1622
1623      ;; 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) 
1624      (mu4e~proc-add new_msg_path (mu4e~mark-check-target target))                                              
1625      )                                                                                                       
1626    )
1627
1628  (defun vde-mu4e--refile (msg)
1629    "Refile function to smartly move `MSG' to a given folder."
1630    (cond
1631     ;; FIXME
1632     ((string= (plist-get (car-safe (mu4e-message-field msg :cc)) :email) "ci_activity@noreply.github.com")
1633      "/icloud/Deleted Messages")
1634     (t
1635      (let ((year (format-time-string "%Y" (mu4e-message-field msg :date))))
1636	(format "/icloud/Archives/%s" year)))))
1637
1638  (setq
1639   mu4e-headers-draft-mark     '("D" . "💈")
1640   mu4e-headers-flagged-mark   '("F" . "📍")
1641   mu4e-headers-new-mark       '("N" . "🔥")
1642   mu4e-headers-passed-mark    '("P" . "")
1643   mu4e-headers-replied-mark   '("R" . "")
1644   mu4e-headers-seen-mark      '("S" . "")
1645   mu4e-headers-trashed-mark   '("T" . "💀")
1646   mu4e-headers-attach-mark    '("a" . "📎")
1647   mu4e-headers-encrypted-mark '("x" . "🔒")
1648   mu4e-headers-signed-mark    '("s" . "🔑")
1649   mu4e-headers-unread-mark    '("u" . "")
1650   mu4e-headers-list-mark      '("l" . "🔈")
1651   mu4e-headers-personal-mark  '("p" . "👨")
1652   mu4e-headers-calendar-mark  '("c" . "📅"))
1653
1654  (setopt mu4e-completing-read-function completing-read-function)
1655  (setq mu4e-refile-folder 'vde-mu4e--refile)
1656  (setq mu4e-contexts `( ,(make-mu4e-context
1657			   :name "icloud"
1658			   :match-func (lambda (msg) (when msg
1659						       (string-prefix-p "/icloud" (mu4e-message-field msg :maildir))))
1660			   :vars '(
1661				   (user-mail-address . "vincent@demeester.fr")
1662				   (mu4e-trash-folder . "/icloud/Deleted Messages")
1663				   (mu4e-sent-folder . "/icloud/Sent Messages")
1664				   (mu4e-draft-folder . "/icloud/Drafts")
1665				   ;; (mu4e-get-mail-command . "mbsync icloud")
1666				   ))
1667			 ,(make-mu4e-context
1668			   :name "gmail"
1669			   :match-func (lambda (msg) (when msg
1670						       (string-prefix-p "/gmail" (mu4e-message-field msg :maildir))))
1671			   :vars '(
1672				   (user-mail-address . "vinc.demeester@gmail.com")
1673				   (mu4e-drafts-folder  . "/gmail/[Gmail]/Drafts")
1674				   (mu4e-sent-folder  . "/gmail/[Gmail]/Sent Mail")
1675				   ;; (mu4e-refile-folder  . "/gmail/[Gmail]/All Mail")
1676				   (mu4e-trash-folder  . "/gmail/[Gmail]/Trash")
1677				   ;; (mu4e-get-mail-command . "mbsync gmail")
1678				   ))
1679			 ,(make-mu4e-context
1680			   :name "redhat"
1681			   :match-func (lambda (msg) (when msg
1682						       (string-prefix-p "/redhat" (mu4e-message-field msg :maildir))))
1683			   :vars '(
1684				   (user-mail-address . "vdemeest@redhat.com")
1685				   (mu4e-drafts-folder  . "/redhat/[Gmail]/Drafts")
1686				   (mu4e-sent-folder  . "/redhat/[Gmail]/Sent Mail")
1687				   ;; (mu4e-refile-folder  . "/redhat/[Gmail]/All Mail")
1688				   (mu4e-trash-folder  . "/redhat/[Gmail]/Trash")
1689				   ;; (mu4e-get-mail-command . "mbsync redhat")
1690				   ))
1691			 ))
1692  (add-to-list 'mu4e-bookmarks
1693	       '( :name  "All Inboxes"
1694		  :query "maildir:/icloud/INBOX OR maildir:/gmail/INBOX OR maildir:/redhat/INBOX"
1695		  :key   ?b))
1696  (with-eval-after-load "mm-decode"
1697    (add-to-list 'mm-discouraged-alternatives "text/html")
1698    (add-to-list 'mm-discouraged-alternatives "text/richtext")))
1699
1700;; (use-package whisper
1701;;   :commands (whisper-run whisper-file)
1702;;   :custom
1703;;   (whisper-install-whispercpp nil))
1704;; TODO gptel configuration (and *maybe* copilot)
1705
1706(use-package goose
1707  :commands (goose-transient goose-start-session)
1708  :bind (("C-c a G" . goose-transient)))
1709
1710(use-package mcp
1711  :commands (mcp-hub-start-all-server)
1712  :after gptel
1713  :custom (mcp-hub-servers
1714	   `(("jira"
1715	      :command "/home/vincent/src/github.com/chmouel/jayrah/.venv/bin/jayrah"
1716	      :args ("mcp"))
1717	     ("github"
1718	      :command "github-mcp-server"
1719	      :args ("stdio")
1720	      :env (:GITHUB_PERSONAL_ACCESS_TOKEN ,(passage-get "github/vdemeester/github-mcp-server")))
1721	     ("playwright"
1722	      :command "npx @playwright/mcp@latest"
1723	      :args ("--executable-path", "/run/current-system/sw/bin/chromium"))))
1724  :config (require 'mcp-hub))
1725
1726(use-package gptel
1727  :commands (gptel gptel-mode)
1728  :bind (("C-c a g" . gptel))
1729  :hook
1730  (gptel-mode . visual-line-mode)
1731  :bind
1732  (:map gptel-mode-map
1733        ("C-c C-k" . gptel-abort)
1734        ("C-c C-m" . gptel-menu)
1735        ("C-c C-c" . gptel-send))
1736  :custom
1737  (gptel-default-mode #'markdown-mode)
1738  :config
1739  (require 'gptel-curl)
1740  (require 'gptel-gemini)
1741  (require 'gptel-ollama)
1742  (require 'gptel-transient)
1743  (require 'gptel-integrations)
1744  (require 'gptel-rewrite)
1745  (require 'gptel-org)
1746  (require 'gptel-openai)
1747  (require 'gptel-openai-extras)
1748  (require 'gptel-autoloads)
1749  (gptel-mcp-connect)
1750  
1751  (setq gptel-model 'gemini-2.5-flash
1752	gptel-backend (gptel-make-gemini "Gemini"
1753					 :key (passage-get "ai/gemini/api_key"))
1754	)
1755
1756  (gptel-make-gemini "Gemini Red Hat"
1757		     :key (passage-get "redhat/google/osp/vdeemest-api-key"))
1758  
1759  (gptel-make-openai "MistralLeChat"
1760		     :host "api.mistral.ai/v1"
1761		     :endpoint "/chat/completions"
1762		     :protocol "https"
1763		     :key (passage-get "ai/mistralai/api_key")
1764		     :models '("mistral-small"))
1765  
1766  (gptel-make-openai "OpenRouter"
1767		     :host "openrouter.ai"
1768		     :endpoint "/api/v1/chat/completions"
1769		     :stream t
1770		     :key (passage-get "ai/openroute/api_key")
1771		     :models '(cognittivecomputations/dolphin3.0-mistral-24b:free
1772			       cognitivecomputations/dolphin3.0-r1-mistral-24b:free
1773			       deepseek/deepseek-r1-zero:free
1774			       deepseek/deepseek-chat:free
1775			       deepseek/deepseek-r1-distill-qwen-32b:free
1776			       deepseek/deepseek-r1-distill-llama-70b:free
1777			       google/gemini-2.0-flash-lite-preview-02-05:free
1778			       google/gemini-2.0-pro-exp-02-05:free
1779			       google/gemini-2.5-pro-exp-03-25:free
1780			       google/gemma-3-12b-it:free
1781			       google/gemma-3-27b-it:free
1782			       google/gemma-3-4b-it:free
1783			       mistralai/mistral-small-3.1-24b-instruct:free
1784			       open-r1/olympiccoder-32b:free
1785			       qwen/qwen2.5-vl-3b-instruct:free
1786			       qwen/qwen-2.5-coder-32b-instruct:free
1787			       qwen/qwq-32b:free
1788			       codellama/codellama-70b-instruct
1789			       google/gemini-pro
1790			       google/palm-2-codechat-bison-32k
1791			       meta-llama/codellama-34b-instruct
1792			       mistralai/mixtral-8x7b-instruct
1793			       openai/gpt-3.5-turbo))
1794
1795  ;; TODO: configure shikoku/kobe ollama instances here
1796  ;; (gptel-make-ollama "Ollama"
1797  ;;   :host "localhost:11434"
1798  ;;   :stream t
1799  ;;   :models '("smollm:latest"
1800  ;; 	      "llama3.1:latest"
1801  ;; 	      "deepseek-r1:latest"
1802  ;; 	      "mistral-small:latest"
1803  ;; 	      "deepseek-r1:7b"
1804  ;; 	      "nomic-embed-text:latest"))
1805  )
1806
1807(use-package acp
1808  :load-path "/home/vincent/src/github.com/xenodium/acp.el/")
1809
1810(use-package agent-shell
1811  :commands (agent-shell agent-shell-toggle)
1812  :load-path "/home/vincent/src/github.com/xenodium/agent-shell/"
1813  :config
1814  (setq agent-shell-google-authentication
1815	(agent-shell-google-make-authentication :api-key (passage-get "redhat/google/osp/vdeemest-api-key")))
1816  (setq agent-shell-anthropic-claude-environment
1817	(agent-shell-make-environment-variables
1818	 "CLAUDE_CODE_USE_VERTEX" "1"
1819	 "CLOUD_ML_REGION" "us-east5"
1820	 "ANTHROPIC_VERTEX_PROJECT_ID" "itpc-gcp-pnd-pe-eng-claude")))
1821
1822(use-package devdocs
1823  :commands (devdocs-lookup devdocs-install vde/install-devdocs)
1824  :bind (("C-h D" . devdocs-lookup))
1825  :config
1826  (defun vde/install-devdocs ()
1827    "Install the devdocs I am using the most."
1828    (interactive)
1829    (dolist (docset '("bash"
1830		      "c"
1831		      "click"
1832		      "cpp"
1833		      "css"
1834		      "elisp"
1835		      "flask"
1836		      "git"
1837		      "gnu_make"
1838		      "go"
1839		      "html"
1840		      "htmx"
1841		      "http"
1842		      "javascript"
1843		      "jq"
1844		      "jquery"
1845		      "kubectl"
1846		      "kubernetes"
1847		      "lua~5.4"
1848		      "nix"
1849		      "python~3.13"
1850		      "python~3.12"
1851		      "requests"
1852		      "sqlite"
1853		      "terraform"
1854		      "werkzeug"
1855		      "zig"))
1856      (devdocs-install docset))))
1857
1858(use-package ready-player
1859  :config
1860  (ready-player-mode +1))
1861
1862(defvar highlight-codetags-keywords
1863  '(("\\<\\(TODO\\|FIXME\\|BUG\\|XXX\\)\\>" 1 font-lock-warning-face prepend)
1864    ("\\<\\(NOTE\\|HACK\\)\\>" 1 font-lock-doc-face prepend)))
1865
1866(define-minor-mode highlight-codetags-local-mode
1867  "Highlight codetags like TODO, FIXME..."
1868  :global nil
1869  (if highlight-codetags-local-mode
1870      (font-lock-add-keywords nil highlight-codetags-keywords)
1871    (font-lock-remove-keywords nil highlight-codetags-keywords))
1872
1873  ;; Fontify the current buffer
1874  (when (bound-and-true-p font-lock-mode)
1875    (if (fboundp 'font-lock-flush)
1876        (font-lock-flush)
1877      (with-no-warnings (font-lock-fontify-buffer)))))
1878
1879(add-hook 'prog-mode-hook #'highlight-codetags-local-mode)
1880
1881(defun vde/wtype-text (text)
1882  "Process TEXT for wtype, handling newlines properly."
1883  (let* ((has-final-newline (string-match-p "\n$" text))
1884         (lines (split-string text "\n"))
1885         (last-idx (1- (length lines))))
1886    (string-join
1887     (cl-loop for line in lines
1888              for i from 0
1889              collect (cond
1890                       ;; Last line without final newline
1891                       ((and (= i last-idx) (not has-final-newline))
1892                        (format "wtype -s 50 \"%s\"" 
1893                                (replace-regexp-in-string "\"" "\\\\\"" line)))
1894                       ;; Any other line
1895                       (t
1896                        (format "wtype -s 50 \"%s\" && wtype -k Return" 
1897                                (replace-regexp-in-string "\"" "\\\\\"" line)))))
1898     " && ")))
1899
1900(define-minor-mode vde/type-mode
1901  "Minor mode for inserting text via wtype."
1902  :keymap `((,(kbd "C-c C-c") . ,(lambda () (interactive)
1903                                   (call-process-shell-command
1904                                    (vde/wtype-text (buffer-string))
1905                                    nil 0)
1906                                   (delete-frame)))
1907            (,(kbd "C-c C-k") . ,(lambda () (interactive)
1908                                   (kill-buffer (current-buffer))))))
1909
1910(defun vde/type ()
1911  "Launch a temporary frame with a clean buffer for typing."
1912  (interactive)
1913  (let ((frame (make-frame '((name . "emacs-float")
1914                             (fullscreen . 0)
1915                             (undecorated . t)
1916                             (width . 70)
1917                             (height . 20))))
1918        (buf (get-buffer-create "emacs-float")))
1919    (select-frame frame)
1920    (switch-to-buffer buf)
1921    (with-current-buffer buf
1922      (erase-buffer)
1923      ;; (org-mode)
1924      (markdown-mode) ;; more common ?
1925      (flyspell-mode)
1926      (vde/type-mode)
1927      (setq-local header-line-format
1928                  (format " %s to insert text or %s to cancel."
1929                          (propertize "C-c C-c" 'face 'help-key-binding)
1930			  (propertize "C-c C-k" 'face 'help-key-binding)))
1931      ;; Make the frame more temporary-like
1932      (set-frame-parameter frame 'delete-before-kill-buffer t)
1933      (set-window-dedicated-p (selected-window) t))))
1934
1935(defun vde/agenda ()
1936  "Launch a frame with the org-agenda and the `org-todos-file' buffer."
1937  (interactive)
1938  (let ((frame (make-frame '((name . "emacs-org-agenda")))))
1939    (select-frame frame)
1940    (org-agenda nil "d")
1941    (split-window-horizontally)
1942    (other-window 1)
1943    (find-file org-todos-file)))
1944
1945(defun memoize-remote (key cache orig-fn &rest args)
1946  "Memoize a value if the key is a remote path."
1947  (if (and key
1948           (file-remote-p key))
1949      (if-let ((current (assoc key (symbol-value cache))))
1950          (cdr current)
1951        (let ((current (apply orig-fn args)))
1952          (set cache (cons (cons key current) (symbol-value cache)))
1953          current))
1954    (apply orig-fn args)))
1955;; Memoize current project
1956(defvar project-current-cache nil)
1957(defun memoize-project-current (orig &optional prompt directory)
1958  (memoize-remote (or directory
1959                      project-current-directory-override
1960                      default-directory)
1961                  'project-current-cache orig prompt directory))
1962
1963(advice-add 'project-current :around #'memoize-project-current)
1964
1965;; Memoize magit top level
1966(defvar magit-toplevel-cache nil)
1967(defun memoize-magit-toplevel (orig &optional directory)
1968  (memoize-remote (or directory default-directory)
1969                  'magit-toplevel-cache orig directory))
1970(advice-add 'magit-toplevel :around #'memoize-magit-toplevel)
1971
1972;; memoize vc-git-root
1973(defvar vc-git-root-cache nil)
1974(defun memoize-vc-git-root (orig file)
1975  (let ((value (memoize-remote (file-name-directory file) 'vc-git-root-cache orig file)))
1976    ;; sometimes vc-git-root returns nil even when there is a root there
1977    (when (null (cdr (car vc-git-root-cache)))
1978      (setq vc-git-root-cache (cdr vc-git-root-cache)))
1979    value))
1980(advice-add 'vc-git-root :around #'memoize-vc-git-root)
1981
1982;; BIND this
1983(defun mu-date-at-point (date)
1984  "Insert current DATE at point via `completing-read'."
1985  (interactive
1986   (let* ((formats '("%Y%m%d" "%F" "%Y%m%d%H%M" "%Y-%m-%dT%T"))
1987          (vals (mapcar #'format-time-string formats))
1988          (opts
1989           (lambda (string pred action)
1990             (if (eq action 'metadata)
1991                 '(metadata (display-sort-function . identity))
1992               (complete-with-action action vals string pred)))))
1993     (list (completing-read "Insert date: " opts nil t))))
1994  (insert date))
1995
1996(provide 'init)
1997;;; init.el ends here