Literate Emacs Config

About

This web page is a tangled version of my literate elisp config. Throughout this document, you may find helpful notes, tricks, and comments. I have been tinkering with emacs in some way since 2013. I made this repeatable and git-controlled in 2019, and in 2023 I began to publish it on my web site.

Notes on code blocks

If a code block is active, then it will appear with emacs-lisp syntax highlighting. If it’s not being used, then it will show up with plaintext highlighting. For example:

(print "This code will not be run!")
(print "This code is run in my config!")

Whether a code block is run at load time is controlled by the :load yes block argument of literate-elisp.

init.el

This is the main init file that wraps all the other code I love and rely heavily on straight.el.

(setq comp-speed 2)

;; Packages setup to prepare literate-elisp
(defvar bootstrap-version)
(let ((bootstrap-file
       (expand-file-name "straight/repos/straight.el/bootstrap.el"
                         (or (bound-and-true-p straight-base-dir)
                             user-emacs-directory)))
      (bootstrap-version 7))
  (unless (file-exists-p bootstrap-file)
    (with-current-buffer
        (url-retrieve-synchronously
         "<https://raw.githubusercontent.com/radian-software/straight.el/develop/install.el>"
         'silent
         'inhibit-cookies)
      (goto-char (point-max))
      (eval-print-last-sexp)))
  (load bootstrap-file nil 'nomessage))

(setq straight-use-package-by-default t)
(straight-use-package 'use-package)
(require 'use-package)
(setq use-package-compute-statistics t)

(use-package general)
(use-package org)

(use-package
 literate-elisp
 :demand t
 :config
 ;; ;; To make \`elisp-refs' work with \`literate-elisp', we need to add an advice to \`elisp-refs&#x2013;read-all-buffer-forms'.
 (eval-after-load "elisp-refs"
   '(advice-add
     'elisp-refs&#x2013;read-all-buffer-forms
     :around #'literate-elisp-refs&#x2013;read-all-buffer-forms))
 ;; To make \`elisp-refs' work with \`literate-elisp', we need to add an advice to \`elisp-refs&#x2013;loaded-paths'.
 (eval-after-load "elisp-refs"
   '(advice-add
     'elisp-refs&#x2013;loaded-paths
     :filter-return #'literate-elisp-refs&#x2013;loaded-paths))
 ;; To make \`helpful' work with \`literate-elisp', we need to add an advice to \`helpful&#x2013;find-by-macroexpanding'.
 (eval-after-load 'helpful
   '(advice-add
     'helpful&#x2013;find-by-macroexpanding
     :around #'literate-elisp-helpful&#x2013;find-by-macroexpanding)))

; Start literate load
(literate-elisp-load
 (expand-file-name "init.org" user-emacs-directory))

(setq custom-file (concat user-emacs-directory "custom.el"))
(if (not (file-exists-p custom-file))
    (f-touch custom-file))
(load custom-file)

MacOS specific settings

Command key as control

(when (eq system-type 'darwin)
  (setq mac-command-modifier 'control)
  (setq mac-right-command-modifier 'meta)

  (add-to-list 'default-frame-alist '(ns-transparent-titlebar . t))

  (add-to-list
   'default-frame-alist
   '(ns-appearance . light)) ;; or dark - depending on your theme

  (use-package
   osx-plist
   :straight
   '(osx-plist :type git :host github :repo "gonewest818/osx-plist")))

Window size

Big and Centered

I’ll do this first, so that it goes back to this when I exit full screen mode (if enabled).

(add-to-list 'default-frame-alist '(width . 130)) ; Set width to 80 columns
(add-to-list 'default-frame-alist '(height . 40)) ; Set height to 24 lines

(defun center-frame ()
  "Center the frame on the screen, respecting the size set in default-frame-alist."
  (interactive)
  (let* ((desired-width
          (or (cdr (assq 'width default-frame-alist)) 80))
         (desired-height
          (or (cdr (assq 'height default-frame-alist)) 24))
         (screen-width (x-display-pixel-width))
         (screen-height (x-display-pixel-height))
         (char-width (frame-char-width))
         (char-height (frame-char-height))
         (frame-pixel-width (* desired-width char-width))
         (frame-pixel-height (* desired-height char-height))
         (left (max 0 (/ (- screen-width frame-pixel-width) 2)))
         (top (max 0 (/ (- screen-height frame-pixel-height) 2))))
    (message
     "Screen size: %dx%d, Desired frame size: %dx%d, Position: (%d, %d)"
     screen-width screen-height desired-width desired-height left top)
    (set-frame-size (selected-frame) desired-width desired-height)
    (set-frame-position (selected-frame) left top)
    (message "Frame set to %dx%d at (%d, %d)"
             (frame-width)
             (frame-height)
             (frame-parameter nil 'left)
             (frame-parameter nil 'top))))

;; Call this function when Emacs starts
(add-hook 'window-setup-hook #'center-frame)

Full Screen

(defun toggle-fullscreen-or-maximized ()
  "Toggle between fullscreen on macOS and maximized on Linux."
  (interactive)
  (if (eq system-type 'darwin) ; macOS
      (set-frame-parameter
       nil 'fullscreen
       (if (frame-parameter nil 'fullscreen)
           nil
         'fullboth))
    (set-frame-parameter
     nil 'fullscreen
     (if (eq (frame-parameter nil 'fullscreen) 'maximized)
         nil
       'maximized)))) ; Linux

(add-hook 'window-setup-hook #'toggle-fullscreen-or-maximized)

Copying syntax highlighting to the clipboard

(when (eq system-type 'darwin)
  (use-package
   highlight2clipboard
   :straight
   '(highlight2clipboard
     :type git
     :host github
     :repo "Lindydancer/highlight2clipboard")))

Path

(setq default-directory "~/")
(use-package package)
(use-package
 exec-path-from-shell
 :demand t
 :init
 (setq exec-path-from-shell-variables
       '("PATH" "MANPATH" "OPENAI_API_KEY"))
 (setq exec-path-from-shell-arguments '("-l" "-i"))
 :config
 (when (memq window-system '(mac ns x))
   (exec-path-from-shell-initialize)))

Terminal

Vterm

(use-package
 vterm
 :init
 (setq vterm-copy-exclude-prompt nil)
 (setq vterm-always-compile-module t)
 (setq vterm-timer-delay 0.01)
 (setq vterm-tramp-shells
       '("/usr/bin/bash" "/bin/bash" "/bin/zsh" "docker" "/bin/sh"))
 :config
 (define-key
  vterm-mode-map (kbd "M-k")
  (lambda ()
    (interactive)
    (kill-this-buffer nil)))
 (define-key vterm-mode-map (kbd "M-0") nil)
 (define-key vterm-mode-map (kbd "M-1") nil)
 (define-key vterm-mode-map (kbd "M-2") nil)
 (define-key vterm-mode-map (kbd "M-3") nil)
 (define-key vterm-mode-map (kbd "M-T") nil)
 (define-key vterm-mode-map (kbd "M-R") nil)
 (define-key vterm-mode-map (kbd "M-G") nil)
 (define-key vterm-mode-map (kbd "M-:") nil)
 (define-key vterm-mode-map (kbd "M-s") 'nil)

 (defun kill-buffer-and-its-windows (buffer)
   "Kill BUFFER and delete its windows.  Default is `current-buffer'.
BUFFER may be either a buffer or its name (a string)."
   (interactive (list
                 (read-buffer "Kill buffer: "
                              (current-buffer)
                              'existing)))
   (setq buffer (get-buffer buffer))
   (if (buffer-live-p buffer) ; Kill live buffer only.
       (let
           ((wins (get-buffer-window-list buffer nil t))) ; On all frames.
         (when (and (buffer-modified-p buffer)
                    (fboundp '1on1-flash-ding-minibuffer-frame))
           (1on1-flash-ding-minibuffer-frame t)) ; Defined in `oneonone.el'.
         (when
             (kill-buffer buffer) ; Only delete windows if buffer killed.
           (dolist (win wins) ; (User might keep buffer if modified.)
             (when (window-live-p win)
               ;; Ignore error, in particular,
               ;; "Attempt to delete the sole visible or iconified frame".
               (condition-case nil
                   (delete-window win)
                 (error nil))))))
     (when (interactive-p)
       (error
        "Cannot kill buffer.  Not a live buffer: `%s'" buffer))))

 (setq vterm-kill-buffer-on-exit t)
 (define-key
  vterm-mode-map (kbd "M-k")
  (lambda ()
    (interactive)
    (kill-buffer-and-its-windows (current-buffer))))
 ;; Add goto-address-mode to vterm-mode-hook for clickable links
 (add-hook 'vterm-mode-hook 'goto-address-mode))

Vterm Toggle

(use-package
 vterm-toggle
 :after vterm
 :demand t
 :bind
 (:map
  vterm-mode-map
  ("<escape>" . vterm-send-C-c)
  ("M-T" . vterm-toggle)
  ("M-R" . vterm-toggle-cd)
  ("M-n" . vterm-toggle-forward)
  ("M-p" . vterm-toggle-backward))
 (:map global-map ("M-T" . vterm-toggle) ("M-R" . vterm-toggle-cd))
 :config
 (define-key vterm-mode-map (kbd "M-T") 'vterm-toggle)
 (define-key vterm-mode-map (kbd "M-R") 'vterm-toggle-cd)

 (setq vterm-toggle-scope 'dedicated)
 (setq vterm-toggle-project-root t)
 (setq vterm-toggle-cd-auto-create-buffer nil)
 (setq vterm-toggle-reset-window-configration-after-exit t)
 (setq vterm-toggle-fullscreen-p nil)
 (setq vterm-toggle-hide-method 'bury-all-vterm-buffer)
 (add-to-list
  'display-buffer-alist
  '((lambda (buffer-or-name _)
      (let ((buffer (get-buffer buffer-or-name)))
        (with-current-buffer buffer
          (or (equal major-mode 'vterm-mode)
              (string-prefix-p
               vterm-buffer-name (buffer-name buffer))))))
    (display-buffer-reuse-window display-buffer-in-direction)
    (direction . bottom)
    (dedicated . t)
    (reusable-frames . visible)
    (window-height . 0.3)
    (window-width . 0.3)))

 ;; Function to get project root without prompting
 (defun my/get-project-root ()
   (or (when (fboundp 'project-root)
         (when-let ((project (project-current nil)))
           (project-root project)))
       (when (fboundp 'projectile-project-root)
         (projectile-project-root))
       default-directory))

 ;; Override vterm-toggle--new
 (defun vterm-toggle--new (&optional buffer-name)
   "New vterm buffer."
   (let* ((buffer-name (or buffer-name vterm-buffer-name))
          (default-directory
           (if vterm-toggle-project-root
               (my/get-project-root)
             default-directory)))
     (if vterm-toggle-fullscreen-p
         (vterm buffer-name)
       (if (eq major-mode 'vterm-mode)
           (let ((display-buffer-alist nil))
             (vterm buffer-name))
         (vterm-other-window buffer-name)))))

 ;; Override vterm-toggle--project-root
 (defun vterm-toggle--project-root ()
   (my/get-project-root)))

Window management

Zoom

I think this is a little too aggressive right now, but it’s a cool idea.

(use-package
 zoom
 :init (setq zoom-size '(0.618 . 0.618))
 :config (zoom-mode))

Visual Fill Column

(use-package
 visual-fill-column
 :init (setq visual-fill-column-center-text t)
 :config (visual-fill-column-mode 1))

Centered window

(use-package
 centered-window
 :init (setq cwm-centered-window-width 180)
 :ensure t
 :config (centered-window-mode t)
 ; reload the fringe color after loading the theme
 (cwm-update-fringe-background))

Editing Packages

Global Keybindings

(global-set-key (kbd "M-k") (lambda () (interactive) (kill-this-buffer nil)))
(global-set-key (kbd "C-c C-b") 'compile)
(global-set-key (kbd "M-0") 'delete-window)
(global-set-key (kbd "M-1") 'delete-other-windows)
(global-set-key (kbd "M-2") 'split-window-below)
(global-set-key (kbd "M-3") 'split-window-right)

; Unbind reverse search because we'll use swiper
(global-unset-key (kbd "C-r"))
(global-set-key (kbd "M-u") 'upcase-dwim)
(global-set-key (kbd "M-l") 'downcase-dwim)
(global-set-key (kbd "C-.") 'xref-find-definitions-other-window)
(define-key global-map (kbd "RET") 'newline-and-indent)

Font Lock

(setq font-lock-support-mode 'jit-lock-mode)
(setq jit-lock-defer-time nil)
(setq jit-lock-stealth-time 0)

Ctrl-F

(use-package
 ctrlf
 :config
 (define-key
  ctrlf-minibuffer-mode-map (kbd "C-r") 'ctrlf-backward-default)
 (setq ctrlf-default-search-style 'fuzzy-regexp)
 (setq ctrlf-default-search-style 'literal)
 (ctrlf-mode t))

Whole line or region

(use-package
 whole-line-or-region
 :config (whole-line-or-region-global-mode t))

Popper

(use-package
 popper
 :bind
 (("C-`" . popper-toggle-latest)
  ("M-`" . popper-cycle)
  ("C-M-`" . popper-toggle-type))
 :init
 (setq popper-reference-buffers
       '("\\*Messages\\*"
         "Output\\*$"
         "\\*Async Shell Command\\*"
         help-mode
         compilation-mode))
 (popper-mode +1) (popper-echo-mode +1))

Page break lines

(use-package page-break-lines :config (global-page-break-lines-mode))

Eldoc

(use-package eldoc :hook (prog-mode . eldoc-mode))

Which Key

(use-package which-key :config (which-key-mode 1))

Ibuffer

(use-package
 ibuffer
 :config
 (global-set-key (kbd "C-x C-b") 'ibuffer)
 (define-key ibuffer-mode-map (kbd "M-o") nil))

Ace popup

;; Use ace-popup-menu for completions
(use-package
 ace-popup-menu
 :config
 (ace-popup-menu-mode 1)
 (setq ace-popup-menu-show-pane-header t))

Line Numbers in Code

(setq require-final-newline t)
(setq show-trailing-whitespace t)
(setq native-comp-async-report-warnings-errors nil)
;; Show the line number of the cursor in the mode bar at the bottom of each buffer
(setq line-number-mode t)

Bells

;; Disable the loud bell
(setq ring-bell-function
      (lambda ()
        (let ((orig-fg (face-foreground 'mode-line)))
          (set-face-foreground 'mode-line "#F2804F")
          (run-with-idle-timer 0.1 nil
                               (lambda (fg)
                                 (set-face-foreground 'mode-line fg))
                               orig-fg))))

Backups

;; Make sure all backup files only live in one place
(setq backup-directory-alist `((".*" . ,temporary-file-directory)))
(setq auto-save-file-name-transforms
      `((".*" ,temporary-file-directory t)))
;; Don't truncate lines
(setq truncate-lines t)
(setq-default indent-tabs-mode nil)

;; Don't show the scroll bar on the side of buffers
(scroll-bar-mode -1)
;; Don't show the toolbar, it just takes up space
(tool-bar-mode -1)

;; Show column number in the modeline
(setq column-number-mode t)
(setq blink-paren-function nil)
(setq inhibit-startup-screen t)

Expand Region

Vanilla

(use-package
 expand-region
 :config (global-set-key (kbd "M-J") 'er/expand-region))

With Tree Sitter Support

(use-package
 expreg
 :config (global-set-key (kbd "M-J") 'expreg-expand))

Direnv

(use-package
 direnv
 :init
 ; An attempt to run direnv earlier in the startup process
 (setq direnv--hooks
       '(find-file-hook
         post-command-hook before-hack-local-variables-hook))
 :config (direnv-mode 't))

Fish

(use-package fish-mode)

Window movement keybindings

(define-key term-raw-map (kbd "M-o") 'next-multiframe-window)
(define-key term-raw-map (kbd "M-i") 'previous-multiframe-window)
(define-key global-map (kbd "M-o") 'next-multiframe-window)
(define-key global-map (kbd "M-i") 'previous-multiframe-window)

(setq aw-keys '(?a ?s ?d ?f ?g ?h ?j ?k ?l))

Anzu

(use-package
  anzu
  :bind
  (([remap query-replace] . #'anzu-query-replace)
   ([remap query-replace-regexp] . #'anzu-query-replace-regexp))
  :config (global-anzu-mode +1))

Line Highlighting

(use-package lin :init (lin-global-mode t))
(use-package
 hl-line
 :config
 (add-hook
  'eshell-mode-hook (lambda () (setq-local global-hl-line-mode nil)))
 (add-hook
  'term-mode-hook (lambda () (setq-local global-hl-line-mode nil)))
 (add-hook
  'vterm-mode-hook (lambda () (setq-local global-hl-line-mode nil)))
 (global-hl-line-mode t))

;; Replace the text of selections
(pending-delete-mode t)

Undo

Undo-Fu

(use-package
 undo-fu
 :bind ("C-/" . undo-fu-only-undo) ("C-?" . undo-fu-only-redo))
(use-package undo-fu-session :config (global-undo-fu-session-mode))

Vundo

(use-package
 vundo
 :init (setq vundo-glyph-alist vundo-unicode-symbols))

So long mode

I find this breaks browsing of long json files, more than it helps me out.

(global-so-long-mode t)

Emacs startup profiler (esup)

(use-package esup)

Scratch

(use-package
 scratch
 :defer t
 :straight
 '(scratch
   :host nil
   :type git
   :repo "https://codeberg.org/emacs-weirdware/scratch.git")
 :config (scratch--create 'emacs-lisp-mode "*scratch*"))

Multiple Cursors (MC)

(use-package
 multiple-cursors
 :config
 (global-set-key (kbd "C-M-j") 'mc/edit-lines)
 (global-set-key (kbd "C->") 'mc/mark-next-like-this)
 (global-set-key (kbd "C-<") 'mc/mark-previous-like-this)
 (global-set-key (kbd "C-c C-<") 'mc/mark-all-like-this)
 (global-set-key (kbd "C-M-=") 'mc/mark-all-symbols-like-this))

Dashboard

(use-package
 dashboard
 :straight
 '(emacs-dashboard
   :type git
   :host github
   :repo "emacs-dashboard/emacs-dashboard"
   :files ("banners" :defaults))
 :config (setq dashboard-projects-backend 'project-el)
 ;; Set the title
 (setq dashboard-banner-logo-title "Welcome to Emacs!")
 ;; Set the banner
 (setq dashboard-startup-banner 'official)
 (setq dashboard-items
       '((projects . 5)
         (recents . 5) (bookmarks . 5)
         ;; (agenda . 5)
         (registers . 5)))
 ;; ;; Value can be
 ;; ;; 'official which displays the official emacs logo
 ;; ;; 'logo which displays an alternative emacs logo
 ;; ;; 1, 2 or 3 which displays one of the text banners
 ;; ;; "path/to/your/image.png" which displays whatever image you would prefer

 ;; ;; Content is not centered by default. To center, set
 (setq dashboard-center-content t)
 (setq initial-buffer-choice (lambda () (get-buffer "*dashboard*")))

 ;; ;; To disable shortcut "jump" indicators for each section, set
 ;; (setq dashboard-show-shortcuts nil)

 ;; Override this function so that we can filter remote projects
 (defun dashboard-projects-backend-load-projects ()
   "Depending on `dashboard-projects-backend' load corresponding backend.
  Return function that returns a list of projects."
   (cl-remove-if
    (lambda (x) (string-search "/ssh" x))
    (cl-case
     dashboard-projects-backend
     (`projectile
      (require 'projectile)
      (dashboard-mute-apply (projectile-cleanup-known-projects))
      (projectile-load-known-projects))
     (`project-el
      (require 'project)
      (dashboard-mute-apply
       (dashboard-funcall-fboundp #'project-forget-zombie-projects))
      (project-known-project-roots))
     (t
      (display-warning
       '(dashboard) "Invalid value for `dashboard-projects-backend'"
       :error)))))

 (dashboard-setup-startup-hook))

Because I’m trying this out, I’m going to disable LSP’s breadcrumb mode, which I’ve been disappointed with.

(use-package
 breadcrumb
 :straight '(breadcrumb :type git :host github :repo "joaotavora/breadcrumb")
 :config (breadcrumb-mode t))

Parens

(setq show-paren-when-point-inside-paren 't)
(setq show-paren-style 'mixed)
(setq show-paren-context-when-offscreen 't)
(setq show-paren-context-when-offscreen t)
(setq show-paren-style 'mixed)


;; Treat ‘<’ and ‘>’ as if they were words, instead of ‘parenthesis’.
(modify-syntax-entry ?< "w<")
(modify-syntax-entry ?> "w>")

;; Show matching parens
(setq show-paren-delay 0)
(show-paren-mode t)

Electric Pair

(use-package
  elec-pair
  :config ;; Disable electric pair in minibuffer
  (defun my/inhibit-electric-pair-mode (char)
    (or (minibufferp) (electric-pair-conservative-inhibit char)))
  (setq electric-pair-inhibit-predicate
        #'my/inhibit-electric-pair-mode)
(electric-pair-mode t)
;; The ‘<’ and ‘>’ are not ‘parenthesis’, so give them no compleition.
(setq electric-pair-inhibit-predicate
      (lambda (c)
        (or (member c '(?< ?> ?~))
            (electric-pair-default-inhibit c)))))

Smartparens

(use-package
 smartparens
 :hook (prog-mode . smartparens-mode)
 :hook (text-mode . smartparens-mode)
 :hook (org-mode . smartparens-mode)
 :hook (markdown-mode . smartparens-mode)
 :config
 ;; load default config
 (require 'smartparens-config))

Rsync-mode

Loving use of my old colleague Ryan Pilgrim’s package to sync accross our secure environments. Edit: Now trying out handcrafted Unison mode.

(use-package
 rsync-mode
 :straight '(rsync-mode :type git :host github :repo "jsigman/rsync-mode"))

Unison sync mode

This is my own little package for syncing with Unison.

(use-package
 unison-sync-mode
 :straight
 (:host github :repo "jsigman/unison-sync-mode"))

Ripgrep

(use-package wgrep :config (setq wgrep-auto-save-buffer t))
(use-package rg :config (rg-enable-menu) (setq rg-executable "rg"))

Avy

Main Package

(use-package
 avy
 :config
 ;; (global-set-key
 ;;  (kbd "C-;")
 ;;  'avy-goto-char-timer) ;; I use this most frequently
 (global-set-key
  (kbd "C-'")
  'avy-goto-line) ;; Consistent with ivy-avy
 (global-set-key
  (kbd "C-M-'")
  'avy-goto-end-of-line) ;; Consistent with ivy-avy

 (setq avy-case-fold-search nil) ;; case sensitive makes selection easier
 (setq avy-indent-line-overlay t))

Casual Avy

(use-package casual-avy
  :ensure t
  :bind ("M-'" . casual-avy-tmenu))

Autoformatting

Apheleia

(use-package
 apheleia
 :config
 (setf (alist-get 'isort apheleia-formatters)
       '("isort" "--stdout" "-"))
 (setf (alist-get 'python-ts-mode apheleia-mode-alist) '(isort black))
 (add-to-list
  'apheleia-formatters
  '(prettier-toml
    npx "prettier" "--stdin-filepath" filepath "--parser=toml"))
 (add-to-list 'apheleia-mode-alist '(conf-toml-mode . prettier-toml))
 (defun apheleia-indent-region+ (orig scratch callback)
   (with-current-buffer scratch
     (setq-local indent-line-function
                 (buffer-local-value 'indent-line-function orig))
     (indent-region (point-min) (point-max))
     (funcall callback scratch)))

 (push '(jsonian-mode . prettier-json) apheleia-mode-alist)
 (setq apheleia-mode-alist
       (assq-delete-all 'emacs-lisp-mode apheleia-mode-alist))

 ;; Add shfmt for direnv-envrc-mode
 (add-to-list 'apheleia-mode-alist '(direnv-envrc-mode . shfmt))

 (apheleia-global-mode t))

Elisp Autofmt

(use-package
 elisp-autofmt
 :commands (elisp-autofmt-mode elisp-autofmt-buffer)
 :hook (emacs-lisp-mode . elisp-autofmt-mode)
 :init (setq elisp-autofmt-check-elisp-autofmt-exists 'always)
 :straight
 '(elisp-autofmt
   ;; :files (:defaults "elisp-autofmt")
   :host nil
   :type git
   :repo "https://codeberg.org/ideasman42/emacs-elisp-autofmt.git")
 :config
 (setq elisp-autofmt-on-save-p
       ; return t unless in "~/.emacs.d/straight"
       (lambda ()
         (not
          (string-match-p
           (concat
            "^"
            (regexp-quote
             (expand-file-name "straight" user-emacs-directory)))
           (buffer-file-name))))))

Whitespace butler

(use-package ws-butler
:hook (prog-mode . ws-butler-mode)
:hook (org-mode . ws-butler-mode))

Indentation

Electric Indent

(electric-indent-mode 0)

Highlight Indent Guides

(use-package
 highlight-indent-guides
 :hook ((python-mode) . highlight-indent-guides-mode))

Indent bars

This is not working now due to stipple support, I believe.

(use-package
 indent-bars
 :straight (indent-bars :type git :host github :repo "jdtsmith/indent-bars")
 :config
 (require 'indent-bars-ts) ; not needed with straight
 :custom
 (indent-bars-treesit-support t)
 (indent-bars-treesit-ignore-blank-lines-types '("module"))
 ;; Add other languages as needed
 (indent-bars-treesit-scope
  '((python
     function_definition
     class_definition
     for_statement
     if_statement
     with_statement
     while_statement)))
 ;; Note: wrap may not be needed if no-descend-list is enough
 ;;(indent-bars-treesit-wrap '((python argument_list parameters ; for python, as an example
 ;;				      list list_comprehension
 ;;				      dictionary dictionary_comprehension
 ;;				      parenthesized_expression subscript)))
 :hook ((python-mode) . indent-bars-mode))

Dired

(setq
 dired-omit-files
 "^\\.?#\\|^\\.\\(DS_Store\\|localized\\|AppleDouble\\)$\\|^\\.\\.$")
(setq dired-kill-when-opening-new-dired-buffer t)
(when (eq system-type 'darwin) (setq
 insert-directory-program "gls"
 dired-use-ls-dired t))
(setq dired-listing-switches "-al --group-directories-first")

;; wdired settings
(use-package
 wdired
 :config
 (setq wdired-allow-to-change-permissions t)
 (define-key dired-mode-map (kbd "e") 'wdired-change-to-wdired-mode)
 (define-key dired-mode-map (kbd "M-G") nil))

Project Packages

Project.el

Migrating from projectile to project.el for better integration with eglot and Flymake. This setup extends the default project detection to handle Git submodules correctly while maintaining existing functionality.

(use-package
 project
 :demand t
 :straight (:type built-in)
 :bind-keymap ("C-c p" . project-prefix-map)
 :config (setq project-vc-include-untracked t)
 ;; Custom project detection function
 (defun my/project-try-local (dir)
   "Determine if DIR is a project by finding the nearest .git directory.
This helps with correctly identifying Git submodules as separate projects."
   (let ((root (locate-dominating-file dir ".git")))
     (when root
       (cons 'transient (expand-file-name root)))))

 ;; TODO - This function not working yet for monorepo folders
 ;; Add our custom function to the beginning of project-find-functions
 ;; (add-hook 'project-find-functions #'my/project-try-local)

 ;; Set up project-vc-ignores
 (setq project-vc-ignores
       '("venv/"
         "typings/"
         "node_modules/"
         ".mypy_cache/"
         ".pytest_cache/"
         ".cache/"
         ".dvc/cache/"
         ".dvc/tmp/"
         ".jekyll-cache/"
         "!*.org"
         "!/notes/"))

 ;; Function to add ignored directories to project-vc-ignores
 (defun my/add-project-ignore (dir)
   "Add DIR to the list of ignored directories in project-vc-ignores."
   (add-to-list 'project-vc-ignores dir))

 ;; Add additional directories to project-vc-ignores
 (dolist (dir
          '("venv"
            "data"
            "typings"
            "node_modules"
            ".mypy_cache"
            ".pytest_cache"
            ".cache"
            ".dvc/cache"
            ".dvc/tmp"
            ".jekyll-cache"))
   (my/add-project-ignore dir))

 ;; Disable automatic project remembering
 (advice-add 'project-remember-project :override #'ignore)

 ;; Prevent automatic removal of projects not found
 (advice-add 'project--remove-from-project-list :override #'ignore))

Notes on changes and functionality:

  1. Custom project detection:
    • We’ve added a new function `my/project-try-local` that finds the nearest .git directory.
    • This function is added to the beginning of `project-find-functions`, allowing it to handle Git submodules correctly.
    • If `my/project-try-local` doesn’t find a project, the default `project-try-vc` will still run.
  2. Existing functionality preserved:
    • The `project-vc-ignores` setup remains unchanged.
    • The `my/add-project-ignore` function and the `dolist` that adds additional ignores are kept as is.
    • Advice to disable automatic project remembering and removal is maintained.
  3. Usage of built-in project.el:
    • The `:straight (:type built-in)` ensures we’re using the built-in version of project.el, which is important for compatibility with eglot and Flymake.
  4. Keybinding:
    • The `C-c p` keybinding for the project prefix map is preserved.

This setup should now correctly handle Git submodules as separate projects while maintaining all the customizations and ignores you had previously set up. The custom project detection function will be tried first, falling back to the default behavior if it doesn’t find a project.

Projectile

(use-package
 projectile
 :init (setq projectile-git-submodule-command nil)
 ;; always ignore the home directory and root
 (setq projectile-ignored-projects
       `("/" "~/" ,(expand-file-name "~/")))

 (setq projectile-track-known-projects-automatically nil)

 ;; Use alien as the default, and project-wise add other files
 (setq projectile-indexing-method 'native)
 (setq projectile-enable-caching t)
 (setq projectile-files-cache-expire 300)
 (setq projectile-file-exists-remote-cache-expire nil)

 :config
 (define-key projectile-mode-map (kbd "s-p") 'projectile-command-map)
 (define-key
  projectile-mode-map (kbd "C-c p") 'projectile-command-map)
 (define-key projectile-mode-map (kbd "M-K") 'projectile-kill-buffers)

 (add-to-list 'projectile-globally-ignored-directories "/venv")
 (add-to-list 'projectile-globally-ignored-directories "/data")
 (add-to-list 'projectile-globally-ignored-directories "/typings")
 (add-to-list
  'projectile-globally-ignored-directories "/node_modules")
 (add-to-list 'projectile-globally-ignored-directories "/.mypy_cache")
 (add-to-list
  'projectile-globally-ignored-directories "/.pytest_cache")
 (add-to-list 'projectile-globally-ignored-directories "/.cache")
 (add-to-list 'projectile-globally-ignored-directories "/.dvc/cache")
 (add-to-list 'projectile-globally-ignored-directories "/.dvc/tmp")
 (add-to-list
  'projectile-globally-ignored-directories "/.jekyll-cache")
 (projectile-mode +1)
 ;; (add-hook 'magit-run-section-hook 'projectile-invalidate-cache)
 (add-hook
  'magit-section-post-command-hook 'projectile-invalidate-cache)

 (setq projectile-ignored-project-function
       (lambda (project-root)
         (string-match-p tramp-file-name-regexp project-root))))

Completions

Corfu

(use-package
 corfu
 ;; Optional customizations
 :custom
 (corfu-cycle t) ;; Enable cycling for `corfu-next/previous'
 (corfu-auto t) ;; Enable auto completion
 ;; (corfu-commit-predicate nil)   ;; Do not commit selected candidates on next input
 (corfu-quit-at-boundary 'separator) ;; Automatically quit at word boundary
 (corfu-quit-no-match 'separator) ;; Automatically quit if there is no match
 (corfu-scroll-margin 5) ;; Use scroll margin
 ;; (corfu-preview-current nil)    ;; Do not preview current candidate
 (corfu-auto-delay 0.0)
 (corfu-auto-prefix 1)
 (corfu-on-exact-match 'quit)

 ;; (corfu-separator ?\s)          ;; Orderless field separator
 ;; (corfu-preview-current nil)    ;; Disable current candidate preview
 ;; (corfu-preselect-first nil)    ;; Disable candidate preselection
 ;; (corfu-on-exact-match nil)     ;; Configure handling of exact matches
 ;; (corfu-echo-documentation nil) ;; Disable documentation in the echo area
 ;; (corfu-scroll-margin 5)        ;; Use scroll margin

 ;; You may want to enable Corfu only for certain modes.
 ;; :hook ((prog-mode . corfu-mode)
 ;;        (shell-mode . corfu-mode)
 ;;        (eshell-mode . corfu-mode))

 ;; Recommended: Enable Corfu globally.
 ;; This is recommended since dabbrev can be used globally (M-/).
 :init (global-corfu-mode)

 ;; :config
 ;; (define-key corfu-map (kbd "M-p") #'corfu-doc-scroll-down) ;; corfu-next
 ;; (define-key corfu-map (kbd "M-n") #'corfu-doc-scroll-up)  ;; corfu-previous

 ;; Quit on save
 :hook (before-save-hook . corfu-quit)
 :load-path "straight/build/corfu/extensions"
 :config
 (require 'corfu-history)
 (corfu-history-mode 1)
 (savehist-mode 1)
 (add-to-list 'savehist-additional-variables 'corfu-history)
 ;; (corfu-mode-hook . corfu-doc-mode)
 )

(advice-add 'corfu--candidates :around
            (lambda (orig-fun &rest args)
              (message "corfu--candidates called with args: %S" args)
              (let ((result (apply orig-fun args)))
                (message "corfu--candidates returned: %S" result)
                result)))

Corfu/Eglot integration

From this source.

(advice-add 'eglot-completion-at-point :around #'cape-wrap-buster)

;; Option 1: Specify explicitly to use Orderless for Eglot
(setq completion-category-overrides '((eglot (styles orderless))
                                      (eglot-capf (styles orderless))))

;; Option 2: Undo the Eglot modification of completion-category-defaults
(with-eval-after-load 'eglot
   (setq completion-category-defaults nil))

;; Enable cache busting, depending on if your server returns
;; sufficiently many candidates in the first place.
(advice-add 'eglot-completion-at-point :around #'cape-wrap-buster)

(defun my/eglot-capf ()
  (setq-local completion-at-point-functions
              (list (cape-capf-super
                     #'eglot-completion-at-point
                     ;; #'yas-expand
                     #'cape-file))))

(add-hook 'eglot-managed-mode-hook #'my/eglot-capf)

Cape

(defun add-cape-completions ()
  (add-to-list 'completion-at-point-functions #'cape-file)
  ;; (add-to-list 'completion-at-point-functions
  ;;              #'cape-keyword)
  ;; (add-to-list 'completion-at-point-functions
  ;;              #'cape-symbol)
  )

;; Add extensions
(use-package
 cape
 ;; Bind dedicated completion commands
 ;; :bind (("C-c p p" . completion-at-point) ;; capf
 ;;        ("C-c p t" . complete-tag)        ;; etags
 ;;        ("C-c p d" . cape-dabbrev)        ;; or dabbrev-completion
 ;;        ("C-c p f" . cape-file)
 ;;        ("C-c p k" . cape-keyword)
 ;;        ("C-c p s" . cape-symbol)
 ;;        ("C-c p a" . cape-abbrev)
 ;;        ("C-c p i" . cape-ispell)
 ;;        ("C-c p l" . cape-line)
 ;;        ("C-c p w" . cape-dict)
 ;;        ("C-c p \\" . cape-tex)
 ;;        ("C-c p _" . cape-tex)
 ;;        ("C-c p ^" . cape-tex)
 ;;        ("C-c p &" . cape-sgml)
 ;;        ("C-c p r" . cape-rfc1345))
 :hook (corfu-mode . add-cape-completions))
;; A few more useful configurations...
(setq completion-cycle-threshold 3)

Orderless

;; Optionally use the `orderless' completion style.
(use-package
 orderless
 :after vertico
 :init
 ;; Tune the global completion style settings to your liking!
 ;; This affects the minibuffer and non-lsp completion at point.
 (setq
  completion-styles '(orderless partial-completion basic)
  completion-category-defaults nil
  completion-category-overrides nil))

;; ;; Use dabbrev with Corfu!
;; (use-package dabbrev
;;   ;; Swap M-/ and C-M-/
;;   :bind (("M-/" . dabbrev-completion)
;;          ("C-M-/" . dabbrev-expand)))

;; A few more useful configurations...
(use-package
 emacs
 :init
 ;; TAB cycle if there are only few candidates
 (setq completion-cycle-threshold 3)

 ;; Emacs 28: Hide commands in M-x which do not apply to the current mode.
 ;; Corfu commands are hidden, since they are not supposed to be used via M-x.
 ;; (setq read-extended-command-predicate
 ;;       #'command-completion-default-include-p)
 )

Vertico

;; Enable vertico
(use-package
 vertico
 :init (vertico-mode)
 :bind (:map vertico-map ("C-j" . vertico-exit-input))

 ;; Different scroll margin
 ;; (setq vertico-scroll-margin 0)

 ;; Show more candidates
 ;; (setq vertico-count 20)

 ;; Grow and shrink the Vertico minibuffer
 ;; (setq vertico-resize t)

 ;; Optionally enable cycling for `vertico-next' and `vertico-previous'.
 ;; (setq vertico-cycle t)
 )

;; Persist history over Emacs restarts. Vertico sorts by history position.
(use-package savehist :init (savehist-mode))

;; A few more useful configurations...
(use-package
 emacs
 :init
 ;; Add prompt indicator to `completing-read-multiple'.
 ;; We display [CRM<separator>], e.g., [CRM,] if the separator is a comma.
 (defun crm-indicator (args)
   (cons
    (format "[CRM%s] %s"
            (replace-regexp-in-string
             "\\`\\[.*?]\\*\\|\\[.*?]\\*\\'" "" crm-separator)
            (car args))
    (cdr args)))
 (advice-add #'completing-read-multiple :filter-args #'crm-indicator)

 ;; Do not allow the cursor in the minibuffer prompt
 (setq minibuffer-prompt-properties
       '(read-only t cursor-intangible t face minibuffer-prompt))
 (add-hook 'minibuffer-setup-hook #'cursor-intangible-mode)

 ;; Emacs 28: Hide commands in M-x which do not work in the current mode.
 ;; Vertico commands are hidden in normal buffers.
 ;; (setq read-extended-command-predicate
 ;;       #'command-completion-default-include-p)

 ;; Enable recursive minibuffers
 (setq enable-recursive-minibuffers t))

Marginalia

(use-package marginalia
  ;; Bind `marginalia-cycle' locally in the minibuffer.  To make the binding
  ;; available in the *Completions* buffer, add it to the
  ;; `completion-list-mode-map'.
  :bind (:map minibuffer-local-map
         ("M-A" . marginalia-cycle))

  ;; The :init section is always executed.
  :init

  ;; Marginalia must be activated in the :init section of use-package such that
  ;; the mode gets enabled right away. Note that this forces loading the
  ;; package.
  (marginalia-mode))

Embark

(use-package embark
  :ensure t
  :bind
  (("C-." . embark-act)         ;; pick some comfortable binding
   ;; ("C-;" . embark-dwim)        ;; good alternative: M-.
   ("C-h B" . embark-bindings)) ;; alternative for `describe-bindings'

  :init

  ;; Optionally replace the key help with a completing-read interface
  (setq prefix-help-command #'embark-prefix-help-command)

  ;; Show the Embark target at point via Eldoc. You may adjust the
  ;; Eldoc strategy, if you want to see the documentation from
  ;; multiple providers. Beware that using this can be a little
  ;; jarring since the message shown in the minibuffer can be more
  ;; than one line, causing the modeline to move up and down:

  ;; (add-hook 'eldoc-documentation-functions #'embark-eldoc-first-target)
  ;; (setq eldoc-documentation-strategy #'eldoc-documentation-compose-eagerly)

  :config

  ;; Hide the mode line of the Embark live/completions buffers
  (add-to-list 'display-buffer-alist
               '("\\`\\*Embark Collect \\(Live\\|Completions\\)\\*"
                 nil
                 (window-parameters (mode-line-format . none)))))

;; Consult users will also want the embark-consult package.
(use-package embark-consult
  :ensure t ; only need to install it, embark loads it after consult if found
  :hook
  (embark-collect-mode . consult-preview-at-point-mode))

Consult

;; Example configuration for Consult
(use-package
 consult
 ;; Replace bindings. Lazily loaded due by `use-package'.
 :demand t
 :bind
 ( ;; C-c bindings in `mode-specific-map'
  ("C-c M-x" . consult-mode-command)
  ("C-c h" . consult-history)
  ("C-c k" . consult-kmacro)
  ("C-c m" . consult-man)
  ("C-c i" . consult-info)
  ([remap Info-search] . consult-info)
  ;; C-x bindings in `ctl-x-map'
  ("C-x M-:" . consult-complex-command) ;; orig. repeat-complex-command
  ("C-x b" . consult-buffer) ;; orig. switch-to-buffer
  ("C-x 4 b" . consult-buffer-other-window) ;; orig. switch-to-buffer-other-window
  ("C-x 5 b" . consult-buffer-other-frame) ;; orig. switch-to-buffer-other-frame
  ("C-x t b" . consult-buffer-other-tab) ;; orig. switch-to-buffer-other-tab
  ("C-x r b" . consult-bookmark) ;; orig. bookmark-jump
  ("C-x p b" . consult-project-buffer) ;; orig. project-switch-to-buffer
  ;; Custom M-# bindings for fast register access
  ("M-#" . consult-register-load)
  ;; ("M-'" . consult-register-store) ;; orig. abbrev-prefix-mark (unrelated)
  ("M-\"" . consult-register-store) ;; orig. abbrev-prefix-mark (unrelated)
  ("C-M-#" . consult-register)
  ;; Other custom bindings
  ("M-y" . consult-yank-pop) ;; orig. yank-pop
  ;; M-g bindings in `goto-map'
  ("M-g e" . consult-compile-error)
  ("M-g f" . consult-flymake) ;; Alternative: consult-flycheck
  ("M-g g" . consult-goto-line) ;; orig. goto-line
  ("M-g M-g" . consult-goto-line) ;; orig. goto-line
  ("M-g o" . consult-outline) ;; Alternative: consult-org-heading
  ("M-g m" . consult-mark)
  ("M-g k" . consult-global-mark)
  ("M-g i" . consult-imenu)
  ("M-g I" . consult-imenu-multi)
  ;; M-s bindings in `search-map'
  ("M-s d" . consult-find) ;; Alternative: consult-fd
  ("M-s c" . consult-locate)
  ("M-s g" . consult-grep)
  ("M-s G" . consult-git-grep)
  ("M-s r" . consult-ripgrep)
  ("M-s l" . consult-line)
  ("M-s L" . consult-line-multi)
  ("M-s k" . consult-keep-lines)
  ("M-s u" . consult-focus-lines)
  ;; Isearch integration
  ;; ("M-s e" . consult-isearch-history)
  :map
  isearch-mode-map
  ("M-e" . consult-isearch-history) ;; orig. isearch-edit-string
  ("M-s e" . consult-isearch-history) ;; orig. isearch-edit-string
  ("M-s l" . consult-line) ;; needed by consult-line to detect isearch
  ("M-s L" . consult-line-multi) ;; needed by consult-line to detect isearch
  ;; Minibuffer history
  :map
  minibuffer-local-map
  ("M-s" . consult-history) ;; orig. next-matching-history-element
  ("M-r" . consult-history)) ;; orig. previous-matching-history-element

 ;; Enable automatic preview at point in the *Completions* buffer. This is
 ;; relevant when you use the default completion UI.
 :hook (completion-list-mode . consult-preview-at-point-mode)

 ;; The :init configuration is always executed (Not lazy)
 :init
 (defun consult-ripgrep-project-root (&optional initial)
   (interactive "P")
   (let ((dir (funcall consult-project-function)))
     (consult--grep
      "Ripgrep" #'consult--ripgrep-make-builder dir initial)))

 ;; Optionally configure the register formatting. This improves the register
 ;; preview for `consult-register', `consult-register-load',
 ;; `consult-register-store' and the Emacs built-ins.
 (setq
  register-preview-delay 0.5
  register-preview-function #'consult-register-format)

 ;; Optionally tweak the register preview window.
 ;; This adds thin lines, sorting and hides the mode line of the window.
 (advice-add #'register-preview :override #'consult-register-window)

 ;; Use Consult to select xref locations with preview
 (setq
  xref-show-xrefs-function #'consult-xref
  xref-show-definitions-function #'consult-xref)

 ;; Configure other variables and modes in the :config section,
 ;; after lazily loading the package.
 :config

 ;; Optionally configure preview. The default value
 ;; is 'any, such that any key triggers the preview.
 ;; (setq consult-preview-key 'any)
 ;; (setq consult-preview-key "M-.")
 ;; (setq consult-preview-key '("S-<down>" "S-<up>"))
 ;; For some commands and buffer sources it is useful to configure the
 ;; :preview-key on a per-command basis using the `consult-customize' macro.
 (consult-customize
  consult-theme
  :preview-key
  '(:debounce 0.2 any)
  consult-ripgrep
  consult-git-grep
  consult-grep
  consult-bookmark
  consult-recent-file
  consult-xref
  consult--source-bookmark
  consult--source-file-register
  consult--source-recent-file
  consult--source-project-recent-file
  ;; :preview-key "M-."
  :preview-key '(:debounce 0.4 any))

 ;; Optionally configure the narrowing key.
 ;; Both < and C-+ work reasonably well.
 (setq consult-narrow-key "<") ;; "C-+"

 ;; Optionally make narrowing help available in the minibuffer.
 ;; You may want to use `embark-prefix-help-command' or which-key instead.
 ;; (define-key consult-narrow-map (vconcat consult-narrow-key "?") #'consult-narrow-help)

 ;; By default `consult-project-function' uses `project-root' from project.el.
 ;; Optionally configure a different project root function.
 ;;;; 1. project.el (the default)
 ;; (setq consult-project-function #'project-root)
 ;;;; 2. vc.el (vc-root-dir)
 ;; (setq consult-project-function (lambda (_) (vc-root-dir)))
 ;;;; 3. locate-dominating-file
 ;; (setq consult-project-function (lambda (_) (locate-dominating-file "." ".git")))
 ;;;; 4. projectile.el (projectile-project-root)
 ;; (autoload 'projectile-project-root "projectile")
 ;; (setq consult-project-function
 ;;       (lambda (_) (projectile-project-root)))
 ;;;; 5. No project support
 ;; (setq consult-project-function nil)
 )

Snippets

Yasnippet

(use-package
 yasnippet
 :demand t
 :init
 (load "yasnippet.el") ; get rid of weird invalid function issue
 )
(use-package
 yasnippet-snippets
 :demand t
 :straight
 '(yasnippet-snippets
   :type git
   :host github
   :repo "jsigman/yasnippet-snippets"))

(yas-global-mode 1)

Consult Yasnippet

(use-package
 consult-yasnippet
 :after consult
 :config (global-set-key (kbd "M-Y") 'consult-yasnippet))

Yasnippet-Capf

(use-package
 yasnippet-capf
 :after cape
 :init
 (setq yasnippet-capf-lookup-by 'key) ;; key or name
 :config (add-to-list 'completion-at-point-functions #'yasnippet-capf))

LSP Server Support

Eglot

(use-package
 eglot
 :ensure t
 :demand t
 :straight nil
 :init
 (defun file-is-remote-p ()
   "Return non-nil if the current file is remote."
   (and (buffer-file-name) (file-remote-p (buffer-file-name))))

 (defun maybe-start-eglot ()
   "Start Eglot if the current file is not remote."
   (unless (file-is-remote-p)
     (when (fboundp 'eglot-ensure)
       (eglot-ensure))))

 (defun disable-eglot-if-remote ()
   "Disable Eglot if the current file is remote."
   (when (and (file-is-remote-p)
              (fboundp 'eglot-managed-p)
              (fboundp 'eglot-shutdown)
              (eglot-managed-p))
     (eglot-shutdown)))

 :hook
 ((python-mode
   markdown-mode
   sh-mode
   yaml-mode
   json-mode
   dockerfile-mode
   LaTeX-mode)
  . maybe-start-eglot)
 :hook (find-file . disable-eglot-if-remote)
 :config
 (add-to-list
  'eglot-server-programs
  '(python-mode . ("pyright-langserver" "--stdio")))
 (add-to-list
  'eglot-server-programs
  '(markdown-mode . ("vscode-markdown-language-server" "--stdio")))
 (add-to-list
  'eglot-server-programs
  '(sh-mode . ("bash-language-server" "start")))
 (add-to-list
  'eglot-server-programs
  '(yaml-mode . ("yaml-language-server" "--stdio")))
 (add-to-list
  'eglot-server-programs
  '(json-mode . ("vscode-json-languageserver" "--stdio")))
 (add-to-list
  'eglot-server-programs
  '(dockerfile-mode . ("docker-langserver" "--stdio")))
 (add-to-list 'eglot-server-programs '(LaTeX-mode . ("texlab")))
 (custom-set-faces
  '(eglot-highlight-symbol-face
    ((t (:inherit highlight :underline t)))))

 ;; Basic settings
 (setq eglot-autoshutdown t)
 (setq eglot-extend-to-xref t)

 ;; Configure completion
 (setq completion-category-defaults nil)
 (setq completion-cycle-threshold 3)
 (setq tab-always-indent 'complete)

 ;; Increase read-process-output-max
 (setq read-process-output-max (* 1024 1024))

 ;; Ignore certain directories for file watching
 (setq eglot-ignored-server-capabilities
       '(:documentOnTypeFormattingProvider))

 ;; Key bindings (optional)
 :bind
 (:map
  eglot-mode-map
  ("C-c l a" . eglot-code-actions)
  ("C-c l r" . eglot-rename)
  ("C-c l f" . eglot-format)
  ("C-c l d" . eglot-find-declaration)))

Eglot-iedit

(use-package
 iedit
 :custom-face (iedit-occurrence ((t (:background "Red"))))
 :bind (:map eglot-mode-map ("M-S" . eglot-iedit-highlights))
 :init
 (defun eglot-iedit-highlights ()
   "Start an `iedit' operation on the documentHighlights at point.
This can be used as a primitive `eglot-rename' replacement if the
language server doesn't support renaming.

See also `eglot-server-capable' for :documentHighlightProvider."
   (interactive)
   (unless (eglot-server-capable :documentHighlightProvider)
     (error "Server does not support documentHighlights"))
   (let
       ((highlights
         (eglot--request
          (eglot--current-server-or-lose)
          :textDocument/documentHighlight (eglot--TextDocumentPositionParams)))
        (-compare-fn
         (lambda (hl1 hl2)
           (and (equal
                 (plist-get (plist-get hl1 :range) :start)
                 (plist-get (plist-get hl2 :range) :start))
                (equal
                 (plist-get (plist-get hl1 :range) :end)
                 (plist-get (plist-get hl2 :range) :end))))))
     (iedit-mode)
     (dolist (highlight (-distinct highlights))
       (let* ((range (plist-get highlight :range))
              (start
               (eglot--lsp-position-to-point
                (plist-get range :start)))
              (end
               (eglot--lsp-position-to-point (plist-get range :end))))
         (iedit-add-occurrence-overlay start end))))))

Programming Modes

Elisp

Elisp code libraries

(use-package dash)
(use-package ht)
(use-package pcre2el)
(use-package async)

Elisp UI

(use-package eros :config (eros-mode t))
(use-package
 lisp-extra-font-lock
 :config (lisp-extra-font-lock-global-mode 1))
(use-package elisp-docstring-mode)
(use-package
 highlight-function-calls
 :hook (emacs-lisp-mode . highlight-function-calls-mode))
(use-package
 inspector
 :straight
 '(inspector :type git :host github :repo "mmontone/emacs-inspector"))
(setq eval-expression-print-length nil)
(setq eval-expression-print-level nil)

Python

(use-package
 python
 :init
 (setq python-shell-interpreter "python3")
 (setq python-shell-interpreter-args "-i")
 :config
 (define-key python-mode-map (kbd "C-c C-c") nil)
 (define-key python-mode-map (kbd "C-c C-p") nil))

(add-hook
 'python-mode-hook
 (lambda ()
   (mapc
    (lambda (pair) (push pair prettify-symbols-alist))
    '( ;; Syntax
      ;; ("def" .      #x2131)
      ;; ("not" .      #x2757)
      ("in" . #x2208)
      ;; ("not in" .   #x2209)
      ("return" . #x27fc) ("yield" . #x27fb)
      ;; ("for" .      #x2200)
      ;; Base Types
      ;; ("int" .      #x2124)
      ;; ("float" .    #x211d)
      ;; ("str" .      #x1d54a)
      ;; ("True" .     #x1d54b)
      ;; ("False" .    #x1d53d)
      ;; Mypy
      ;; ("Dict" .     #x1d507)
      ;; ("List" .     #x2112)
      ;; ("Tuple" .    #x2a02)
      ;; ("Set" .      #x2126)
      ;; ("Iterable" . #x1d50a)
      ;; ("Any" .      #x2754)
      ;; ("Union" .    #x22c3)
      ))))
(use-package
 pip-requirements
 :init
 (add-to-list
  'auto-mode-alist
  `(,(rx "requirements" (zero-or-more anything) ".in" string-end)
    . pip-requirements-mode)))

Docstrings

(use-package
 buftra
 :straight
 '(buftra.el :type git :host github :repo "humitos/buftra.el"))

(use-package
 py-pyment
 :straight
 '(py-cmd-buffer.el
   :type git
   :host github
   :repo "humitos/py-cmd-buffer.el")
 :after python
 :config (setq py-pyment-options '("--output=google")))

Copying lines as a single line for pasting into the pdbpp debugger

(defun python-multiline-to-singleline ()
  "Convert multi-line Python code in the current region to a single line with single spaces."
  (interactive)
  (when (use-region-p)
    (let* ((start (region-beginning))
           (end (region-end))
           (multi-line-code (buffer-substring start end))
           (single-line-code (replace-regexp-in-string "[ \t\n]+" " " multi-line-code)))
      (kill-new single-line-code)
      (message "Single-line code copied to kill ring."))))

(with-eval-after-load 'python
  (define-key python-mode-map (kbd "C-c C-l") 'python-multiline-to-singleline))

Markdown

(use-package
 markdown-mode
 :mode ("README\\.md\\'" . gfm-mode)
 :init (setq markdown-command "multimarkdown")
 :config)

HTML

(use-package
 sgml-mode
 :mode ("\\.html\\'" . html-mode)
 :bind
 (:map
  sgml-mode-map
  ("M-o b" . nil)
  ("M-o d" . nil)
  ("M-o i" . nil)
  ("M-o l" . nil)
  ("M-o o" . nil)
  ("M-o u" . nil)
  ("M-o M-o" . nil)
  ("M-o" . nil))
 :bind
 (:map
  html-mode-map
  ("M-o b" . nil)
  ("M-o d" . nil)
  ("M-o i" . nil)
  ("M-o l" . nil)
  ("M-o o" . nil)
  ("M-o u" . nil)
  ("M-o M-o" . nil)
  ("M-o" . nil)))
(use-package html-ts-mode)

Web mode

(use-package
 web-mode
 :config
 (add-to-list 'auto-mode-alist '("\\.liquid\\'" . web-mode)))

Dotenv Mode

(use-package dotenv-mode :defer t)

Latex

(use-package
 tex
 :straight auctex
 :defer t
 :hook (LaTeX-mode . visual-line-mode)
 :hook (LaTeX-mode . flyspell-mode)
 :hook (LaTeX-mode . LaTeX-math-mode)
 :hook (LaTeX-mode . TeX-source-correlate-mode)
 :config
 (setq TeX-auto-save t)
 (setq TeX-parse-self t)
 (setq-default TeX-master nil)

 ;; (add-hook 'LaTeX-mode-hook 'company-auctex-init)
 ;; (add-hook 'LaTeX-mode-hook 'company-mode)
 (add-hook 'LaTeX-mode-hook 'turn-on-reftex)
 (setq reftex-plug-into-AUCTeX t)
 (setq TeX-PDF-mode t))
(use-package cdlatex)

;; -------------------------/AucTex-------------------------------;;

MATLAB

(use-package
 matlab-mode
 '(matlab-mode
   :type git
   :repo "https://git.code.sf.net/p/matlab-emacs/src")
 :defer t)
(require 'matlab)
(setq matlab-shell-command-switches '("-nodesktop" "-nosplash"))

SQL

So far, I am unable to find a way to compile sqlite3 on my own, but I would love to be able to do this with straight package management.

;; Override the 'yes-or-no-p' temporarily
(let ((original-yes-or-no-p (symbol-function 'yes-or-no-p)))
  (fset 'yes-or-no-p (lambda (&rest args) t))

  ;; Load the sqlite3 package
  (use-package
   sqlite3
   :straight
   (sqlite3
    :type git
    :host github
    :repo "pekingduck/emacs-sqlite3-api"
    :files ("*.c" "*.h" "*.el" "Makefile")))

  ;; Restore the original function
  (fset 'yes-or-no-p original-yes-or-no-p))

Yaml

(use-package yaml-mode)
(use-package yaml-ts-mode)

DAP Mode

I’m currently not using DAP mode, and prefer to use python from the command line (vterm) with pdb. Some day I’d like to learn this.

(use-package
 dap-mode
 :init
 (if (eq system-type 'gnu/linux)
     (add-to-list 'image-types 'svg))
 :config
 (add-hook
  'dap-stopped-hook (lambda (arg) (call-interactively #'dap-hydra)))
 ;; Enabling only some features
 (setq dap-auto-configure-features
       '(sessions locals controls tooltip))
 (setq dap-python-debugger 'debugpy)
 (require 'dap-mode)
 (require 'dap-python)
 (require 'dap-ui)

 (add-hook 'python-mode-hook 'dap-mode)
 (add-hook 'python-mode-hook 'dap-ui-mode)
 (add-hook 'python-mode-hook 'dap-tooltip-mode)

 (define-key python-mode-map (kbd "M-D") #'dap-hydra))

JQ

(use-package jq-mode)

Json-ts-mode

(use-package json-mode)
(use-package json-ts-mode)

Jsonian

(use-package
 jsonian
 :straight
 '(jsonian
   :type git
   :host github
   :repo "iwahbe/jsonian"
   :build (:not autoloads)))

Large Language Models in Emacs

Github Copilot

(use-package
 copilot
 :straight (:host github :repo "zerolfx/copilot.el" :files ("dist" "*.el"))
 :bind
 (:map
  copilot-completion-map ("M-<return>" . copilot-accept-completion))
 :hook
 ((prog-mode yaml-mode org-mode direnv-envrc-mode conf-mode)
  .
  my/copilot-mode-setup)
 ; setting tab-width to 4 for python-mode currently fixes issues with copilot
 :hook (python-mode . (lambda () (setq tab-width 4)))
 :config
 (setq copilot-max-char -1)
 (setq copilot-indent-offset-warning-disable t)

 ;; Custom function to disable copilot on "coder.*" SSH hosts
 (defun my/copilot-mode-setup ()
   "Disable copilot-mode if connected to a 'coder.*' host."
   (let ((remote-host (file-remote-p default-directory 'host)))
     (unless (and remote-host
                  (string-match-p "^coder\\." remote-host))
       (copilot-mode 1)))))

C3PO

This one didn’t work very well

(use-package
 c3po
 :straight (:host github :repo "d1egoaz/c3po.el")
 :config
 (setq chat-api-key
       (f-read-text (expand-file-name "~/.openai/emacs-key.txt"))))

OpenAI

(use-package
 openai
 :straight
 (openai :type git :host github :repo "emacs-openai/openai")
 :init (setq openai-key (getenv "OPENAI_API_KEY")))
(use-package
 chatgpt
 :straight (chatgpt :type git :host github :repo "emacs-openai/chatgpt")
 ;; :config (setq chatgpt-model "gpt-4-0613")
 )
(use-package
 codegpt
 :straight
 (codegpt :type git :host github :repo "emacs-openai/codegpt"))
(use-package
 dall-e
 :straight
 (dall-e :type git :host github :repo "emacs-openai/dall-e"))

Ellama

(use-package llm)
(use-package
 ellama
 :init
 (setopt
  ellama-provider (make-llm-ollama :chat-model "codellama:34b")))

Linting

Flycheck

(use-package
 flycheck
 :init
 (define-fringe-bitmap 'my-flycheck-fringe-indicator
   (vector
    #b00000000
    #b00000000
    #b00000000
    #b00000000
    #b00000000
    #b00000000
    #b00000000
    #b00011100
    #b00111110
    #b00111110
    #b00111110
    #b00011100
    #b00000000
    #b00000000
    #b00000000
    #b00000000
    #b00000000))
 (flycheck-define-error-level
  'error
  :severity 2
  :overlay-category 'flycheck-error-overlay
  :fringe-bitmap 'my-flycheck-fringe-indicator
  :fringe-face 'flycheck-fringe-error)
 (flycheck-define-error-level
  'warning
  :severity 1
  :overlay-category 'flycheck-warning-overlay
  :fringe-bitmap 'my-flycheck-fringe-indicator
  :fringe-face 'flycheck-fringe-warning)
 (flycheck-define-error-level
  'info
  :severity 0
  :overlay-category 'flycheck-info-overlay
  :fringe-bitmap 'my-flycheck-fringe-indicator
  :fringe-face 'flycheck-fringe-info)
 ;; :config
 ;; (use-package flycheck-pos-tip )
 ;; (flycheck-pos-tip-mode)
 ; Flycheck
 (setq flycheck-idle-change-delay 0.1)
 (setq flycheck-display-errors-delay 0.1)
 (setq flycheck-idle-buffer-switch-delay 0.1)

 (setq flycheck-checkers (remove 'python-pylint flycheck-checkers))
 (setq flycheck-checkers (remove 'python-pycompile flycheck-checkers))
 (setq flycheck-checkers (remove 'python-pyright flycheck-checkers))

 (add-hook 'after-init-hook #'global-flycheck-mode)
 (setq flycheck-global-modes
       '(python-base-mode js-mode python-mode python-ts-mode))

 (defun debug-flycheck-ruff-disabled (symbol newval operation where)
   "Log debug information when python-ruff is added to flycheck--automatically-disabled-checkers."
   (when (and (eq operation 'set) (member 'python-ruff newval))
     (let ((debug-on-error t)
           (debug-buffer (get-buffer-create "*flycheck-ruff-debug*")))
       (with-current-buffer debug-buffer
         (goto-char (point-max))
         (insert "\n\n")
         (insert
          (format-time-string
           "Debug info captured at %Y-%m-%d %H:%M:%S\n\n"))
         (insert
          "python-ruff has been added to automatically disabled checkers.\n\n")
         (insert (format "All disabled checkers: %s\n\n" newval))
         (insert
          (format ":enabled predicate returned: %s\n\n"
                  (flycheck-checker-get 'python-ruff :enabled)))
         (insert
          (format "Error threshold: %s\n\n"
                  flycheck-checker-error-threshold))
         (insert "Stack trace:\n")
         (insert
          (with-output-to-string
            (backtrace)))
         (insert "\n\nEnd of debug info.\n"))
       (display-buffer debug-buffer))))

 (add-variable-watcher
  'flycheck--automatically-disabled-checkers
  #'debug-flycheck-ruff-disabled)

 (setq flycheck-checker-error-threshold nil)
 (setq flycheck-debug t))

Toggling flycheck buffer with “M-C”

I have my own little hook to open the flycheck buffer with M-C, and close it again with another M-C keystroke.

(defvar should-delete-flycheck-list-buffer nil)
(defun my/flycheck-list-errors ()
  "Open flycheck list if it doesn't exist.  If it does, close it."
  (interactive)
  (let* ((target-buffer-name "*Flycheck errors*")
         (target-buffer (get-buffer target-buffer-name))
         (target-window (get-buffer-window target-buffer)))
    (if (and target-buffer target-window)
        ;; the target buffer exists and window is visible
        (progn
          (when should-delete-flycheck-list-buffer
            (delete-window target-window))
          (kill-buffer target-buffer))
      ;; the target buffer doesn't exist or the window isn't visible
      (let* ((starting-window-count (count-windows)))
        (flycheck-list-errors)
        (setq should-delete-flycheck-list-buffer
              (> (count-windows) starting-window-count))))))

(define-key flycheck-mode-map (kbd "M-C") 'my/flycheck-list-errors)

Flymake

(use-package flymake :ensure t)
(add-hook 'python-ts-mode-hook 'flymake-mode)

Ruff

(use-package flymake-ruff :ensure t :config (flymake-ruff-load))

JSON

(use-package flymake-json :ensure t :config (flymake-json-load))

Bugfix: Ruff

(defun flymake-ruff--check-buffer ()
  "Generate a list of diagnostics for the current buffer."
  (let ((code-buffer (current-buffer))
        (start-line (line-number-at-pos (point-min) t))
        (code-content
         (without-restriction
           (buffer-substring-no-properties (point-min) (point-max))))
        (dxs '()))
    (with-temp-buffer
      (insert code-content)
      (let* ((config
              (and (project-current)
                   (seq-find
                    #'file-readable-p
                    (mapcar
                     (lambda (f)
                       (expand-file-name
                        f
                        (project-root (project-current))))
                     flymake-ruff--default-configs))))
             (args
              (if config
                  (append
                   (list "check" "--config" config)
                   flymake-ruff-program-args)
                (cons "check" flymake-ruff-program-args))))
        (apply #'call-process-region
               (point-min)
               (point-max)
               flymake-ruff-program
               t
               t
               nil
               args))
      (goto-char (point-min))
      (while (search-forward-regexp flymake-ruff--output-regex
                                    (point-max)
                                    t)
        (when (match-string 2)
          (let* ((line (string-to-number (match-string 2)))
                 (col (string-to-number (match-string 3)))
                 (code (match-string 4))
                 (msg (match-string 5))
                 (description (format "Ruff: %s %s" code msg))
                 (region
                  (flymake-diag-region
                   code-buffer (1+ (- line start-line)) col))
                 (dx
                  (flymake-make-diagnostic
                   code-buffer
                   (car region)
                   (cdr region)
                   :error description)))
            (add-to-list 'dxs dx)))))
    dxs))
(setq flymake-ruff-program-args
      '("--output-format" "concise" "--exit-zero" "--quiet" "-"))
(defun my-python-ruff-setup ()
  (add-hook 'flymake-diagnostic-functions #'flymake-ruff--run-checker nil t))
(add-hook 'python-base-mode-hook #'my-python-ruff-setup)

TRAMP

General Settings

(setq enable-remote-dir-locals 't)
(setq tramp-chunksize 4050)
(setq tramp-verbose 10)
(setq vc-ignore-dir-regexp
      (format "\\(%s\\)\\|\\(%s\\)"
              vc-ignore-dir-regexp
              tramp-file-name-regexp))
(defun open-remote-dired ()
  "Opens a Dired buffer at the path specified by REMOTE_HOST and REMOTE_PATH environment variables."
  (interactive)
  (condition-case nil
      (let ((remote-host (getenv "REMOTE_HOST"))
            (remote-path (getenv "REMOTE_PATH")))
        (if (and remote-host remote-path)
            (progn
              (message "Attempting to open remote directory...")
              (dired (concat "/ssh:" remote-host ":" remote-path))
              (message "Remote directory opened."))
          (message
           "Error: REMOTE_HOST or REMOTE_PATH environment variables not set.")))
    (error
     (message
      "Error: Unable to open remote directory. Check your connection and environment variables."))))

;; Bind the function to M-R globally
(global-set-key (kbd "M-R") 'open-remote-dired)

Local PATH

(defun my-setup-tramp-path ()
  (let ((local-path (cdr (assoc 'my-project-specific-path dir-local-variables-alist))))
    (when local-path
      (add-to-list 'tramp-remote-path
                   (concat "/sshx:your_username@remote_host:" local-path)))))
(add-hook 'hack-dir-local-variables-hook #'my-setup-tramp-path)

Eglot in Tramp

(defun my/eglot-project-function (dir)
  "Custom project function for Eglot that avoids using Projectile for remote directories."
  (if (file-remote-p dir)
      (cons 'transient dir)  ; Treat remote dirs as transient projects
    (project-try-vc dir)))   ; Use VC-based detection for local dirs

(setq eglot-project-function #'my/eglot-project-function)

Lock files

Disable lock files in TRAMP

(defun my-tramp-file-name-handler (operation &rest args)
  "Disable file locks for TRAMP files."
  (if (eq operation 'vc-registered)
      nil
    (let ((file-name-handler-alist
           (remove (cons "\\`/\\(ssh\\|scp\\|ftp\\):" 'my-tramp-file-name-handler)
                   file-name-handler-alist)))
      (apply operation args))))

(add-to-list 'file-name-handler-alist
             '("\\`/\\(ssh\\|scp\\|ftp\\):" . my-tramp-file-name-handler))

Direnv in Tramp

Code exists in an unmerged branch.

(defcustom my-direnv-enabled-hosts nil
  "List of remote hosts to use Direnv on.

Each host must have `direnv' executable accessible in the default
environment."
  :type '(repeat string)
  :group 'my)

(defun tramp-sh-handle-start-file-process@my-direnv (args)
  "Enable Direnv for hosts in `my-direnv-enabled-hosts'."
  (with-parsed-tramp-file-name
   (expand-file-name default-directory) nil
   (if (member host my-direnv-enabled-hosts)
       (progn (pcase-let ((`(,name ,buffer ,program . ,args) args))
         `(,name ,buffer "direnv" "exec" ,localname ,program ,@args)) (debug))
     args)))

(with-eval-after-load "tramp-sh"
  (advice-add
   'tramp-sh-handle-start-file-process
   :filter-args #'tramp-sh-handle-start-file-process@my-direnv))

Sudo editing shortcut

(defun edit-current-file-as-root ()
  "Reopen the current file as root."
  (interactive)
  (let ((file (buffer-file-name)))
    (if (not (file-writable-p file))
        (find-file (concat "/sudo::" file))
      (message "File is already writable"))))

Vertico improvements

I get False Positive completions using coder with TRAMP ssh completions. This filter removes them.

(defun my/tramp-ssh-completion-filter (completions)
  (cl-remove-if-not
   (lambda (completion)
     (not (or (string-match-p "^coder-vscode--:$" completion)         ; Exclude "coder-vscode--:"
              (string-match-p "^coder-vscode\\.coder\\.infiniaml\\.net--:$" completion)  ; Exclude "coder-vscode.coder.infiniaml.net--:"
              (string-match-p "^coder\\.:$" completion))))             ; Exclude "coder.:"
   completions))

(advice-add 'tramp-completion-handle-file-name-all-completions
            :filter-return #'my/tramp-ssh-completion-filter)

Dir-Locals

(add-to-list
 'auto-mode-alist
 '("\\.dir-locals\\(?:-2\\)?\\.el\\'" . emacs-lisp-mode))

This really improves use with local variables. You get a highly visible warning when a dir-local file is unreadable or misconfigured.

(defun my/dir-local-error-warning (orig-fun &rest args)
  "Advice to display a warning on directory local variable read errors."
  (condition-case err
      (apply orig-fun args)
    (error
     (display-warning
      'dir-locals
      (format "Error reading .dir-locals.el: %s"
              (error-message-string err))
      :error))))
(advice-add
 'hack-dir-local-variables
 :around #'my/dir-local-error-warning)

Application Development

Docker

(use-package
 dockerfile-mode
 :config (setq dockerfile-mode-command "docker"))
(use-package docker
  :ensure t
  :bind ("C-c d" . docker))

Heroku

(use-package
 heroku
 :ensure t
 :defer t
 :bind (:map global-map ("C-c h" . heroku-command-map))
 :commands
 (heroku-list
  heroku-logs
  heroku-restart
  heroku-dynos-kill
  heroku-run-detached
  heroku-run-python
  heroku-run-bash)
 :init
 (define-prefix-command 'heroku-command-map)
 (define-key heroku-command-map (kbd "l") 'heroku-list)
 (define-key heroku-command-map (kbd "s") 'heroku-sql)
 :config
 ;; Function to get app name, falling back to the default
 (defun my/heroku-get-app-name ()
   (or (and (boundp 'heroku-app-name) heroku-app-name)
       (getenv "HEROKU_APP_NAME")
       heroku-default-app))

 ;; Advice to use our custom function
 (advice-add 'heroku-get-app-name :override #'my/heroku-get-app-name)

 :custom
 (heroku-timestamp-regex
  "^[[:digit:]]\\{4\\}-[[:digit:]]\\{2\\}-[[:digit:]]\\{2\\}T[[:digit:]:\+\.]*")
 (heroku-app-name-re "^[[:alnum:]-]*")
 (heroku-region-re "\(\\([[:alnum:]]*\\)\)")
 (heroku-collab-re "[[:alnum:]]*@[[:alnum:]\.-_]*"))

Org Mode

Org Src Formatting

(defun org-src-format-and-save ()
  (interactive)
  (when (provided-mode-derived-p major-mode 'python-base-mode)
    (if (and (boundp 'apheleia-formatter)
             (seq-contains-p apheleia-formatter 'ruff))
        (apheleia-format-buffer 'ruff)
      (apheleia-format-buffer 'black)))
  (when (eq major-mode 'emacs-lisp-mode)
    (elisp-autofmt-buffer))
  (sit-for 0.100)
  (org-edit-src-save))

Org Mode Settings

(use-package
 org
 :defer t
 :hook (org-mode-hook . visual-line-mode)
 :hook (org-babel-after-execute-hook . org-display-inline-images)
 :hook (org-babel-after-execute-hook . append)
 :hook
 (org-mode
  .
  (lambda ()
    (remove-hook
     'completion-at-point-functions #'pcomplete-completions-at-point
     t)
    (remove-hook
     'completion-at-point-functions #'ispell-completion-at-point
     t)))
 :bind
 (:map
  org-mode-map
  ("C-'" . nil)
  ("C-c C-c" . org-ctrl-c-ctrl-c)
  ("C-M-<return>" . my/org-babel-execute-and-next)
  ("C-c <" . nil)
  ("M-g o" . consult-org-heading)
  :map
  org-src-mode-map
  ("C-x C-s" . org-src-format-and-save))
 :config
 (setq org-format-latex-options
       (plist-put org-format-latex-options :scale 2.0))
 (setq org-latex-create-formula-image-program 'dvisvgm)

 (defun my/org-babel-execute-and-next ()
   (interactive)
   (org-babel-execute-src-block)
   (org-babel-next-src-block))

 (org-babel-do-load-languages
  'org-babel-load-languages
  '((emacs-lisp . t)
    (sqlite . t)
    (shell . t)
    (jq . t)
    (jupyter . nil)
    (python . t)))
 ; Needed to make org-babel-jupyter work, want to refresh the kernels to see what is visible in venv
 (defun my/load-org-jupyter ()
   (org-babel-do-load-languages
    'org-babel-load-languages '((jupyter . t)))
   (org-babel-jupyter-aliases-from-kernelspecs 'refresh))
 (visual-line-mode t)

 ; Org-babel stuff
 (setf (alist-get "bash" org-src-lang-modes nil nil #'equal)
       'bash-ts)

 (setq org-log-done t)

 ;; adding this does some weird stuff in colors
 (setq org-startup-indented nil)

 ;; fontify code in code blocks
 (setq org-src-fontify-natively t)
 (setq org-confirm-babel-evaluate nil) ;don't prompt me to confirm everytime I want to evaluate a block
 (setq org-src-preserve-indentation t)

 (defun my-update-direnv-in-org-src ()
   "Update direnv environment variables for org-src buffers."
   (when (and (bound-and-true-p direnv-mode)
              (eq major-mode 'org-mode))
     (let ((org-file-path (buffer-file-name (buffer-base-buffer))))
       (when org-file-path
         (direnv-update-environment org-file-path)))))

 (add-hook 'org-src-mode-hook 'my-update-direnv-in-org-src))

Org Babel

(setq ob-ipython-command "jupyter")
(use-package
 plantuml-mode
 :after org
 :config
 (setq plantuml-default-exec-mode 'executable)
 (setq org-plantuml-exec-mode 'plantuml)
 (add-to-list 'auto-mode-alist '("\\.plantuml\\'" . plantuml-mode)))
(use-package ob-napkin)
(use-package mermaid-mode)
(use-package ob-mermaid)
(use-package htmlize :after org)

Colors in Org

(use-package
 org-bullets
 :after org
 :hook (org-mode-hook . org-bullets-mode))
;;nil means to wrap lines in org mode
(setq org-startup-truncated nil)

Org Modern

I thought I liked this at first, but now I’m going to disable it because of some annoying text interactions.

(use-package org-modern :after org :config (global-org-modern-mode))
(use-package
 org-modern-indent
 :after org
 :straight
 '(org-modern-indent
   :type git
   :host github
   :repo "jdtsmith/org-modern-indent")
 :config
 (setq org-startup-indented t)
 (add-hook 'org-mode-hook #'org-modern-indent-mode 90))

Org Roam

I’m not currently using Org Roam, but maybe some day I’d like to.

(use-package org-roam
  :ensure t
  :custom
  (org-roam-directory (file-truename "~/Dropbox/org-roam/")) ;; Define org-roam-directory first
  :bind
  (("C-c n l" . org-roam-buffer-toggle)
   ("C-c n f" . org-roam-node-find)
   ("C-c n g" . org-roam-graph)
   ("C-c n i" . org-roam-node-insert)
   ("C-c n c" . org-roam-capture)
   ;; Dailies
   ("C-c n j" . org-roam-dailies-capture-today))
  :init
  ;; Delay setting org-id-locations-file until after org-roam is loaded
  (with-eval-after-load 'org-roam
    (setq org-id-locations-file (expand-file-name "org-id-locations" org-roam-directory)))
  :config
  ;; If using a vertical completion framework, customize completion interface
  (setq org-roam-node-display-template
        (concat
         "${title:*} " (propertize "${tags:10}" 'face 'org-tag)))
  ;; Automatically sync the database
  (org-roam-db-autosync-mode)

  ;; Check and update org-id-locations file if it doesn't exist
  (unless (file-exists-p org-id-locations-file)
    (message "Org-id-locations file not found. Generating a new one...")
    (org-roam-update-org-id-locations))

  ;; If using org-roam-protocol
  (require 'org-roam-protocol))

Jupyter

ZMQ

(use-package
 zmq
 :straight '(zmq :host github :repo "nnicandro/emacs-zmq")
 :init
 ; macro to wrap loading
 (defmacro safe-wrap (fn &rest clean-up)
   `(unwind-protect
        (let (retval)
          (condition-case ex
              (setq retval
                    (progn
                      ,fn))
            ('error
             (message (format "Caught exception: [%s]" ex))
             (setq retval (cons 'exception (list ex)))))
          retval)
      ,@clean-up))

 (defun fix-zmq-file-naming ()
   "copy .so to .dylib so that we can proceed with installing zmq"
   (let* ((tag (concat "tags/" zmq-emacs-version))
          (api-url
           "https://api.github.com/repos/nnicandro/emacs-zmq/")
          (repo-url "https://github.com/nnicandro/emacs-zmq/")
          (release-url (concat api-url "releases/"))
          (info
           (zmq--download-url
            (concat release-url tag) (require 'json)
            (let ((json-object-type 'plist))
              (ignore-errors
                (json-read)))))
          (tag-name
           (or (plist-get info :tag_name) (throw 'failure nil)))
          (ezmq-sys (concat "emacs-zmq-" (zmq--system-configuration)))
          (assets
           (cl-remove-if-not
            (lambda (x) (string-prefix-p ezmq-sys x))
            (mapcar
             (lambda (x) (plist-get x :name))
             (append (plist-get info :assets) nil)))))
     (when assets
       (let ((default-directory
              (file-name-directory (locate-library "zmq"))))
         ;; We have a signature file and a tar.gz file for each binary so the
         ;; minimum number of files is two.
         (if (> (length assets) 2)
             (error "TODO More than one file found")
           (let* ((tgz-file
                   (cl-find-if
                    (lambda (x) (string-suffix-p "tar.gz" x)) assets))
                  (lib
                   (expand-file-name (concat
                                      "emacs-zmq" module-file-suffix)
                                     (expand-file-name
                                      (file-name-sans-extension
                                       (file-name-sans-extension
                                        tgz-file))))))
             (let* ((source-file
                     (concat (file-name-sans-extension lib) ".so")))
               (when (not (f-exists? lib))
                 (print (format "Copy from %s to %s" source-file lib))
                 (copy-file source-file lib)))
             t))))))

 (let (original-noninteractive-value
       noninteractive)
   ;; this is a hack so i don't have to ask about downloading the compatible binary
   (setq noninteractive t)
   (safe-wrap
    (condition-case nil
        (require 'zmq)
      (error
       (fix-zmq-file-naming)
       (require 'zmq)))) ;; set the variable back to its original value
   (setq noninteractive original-noninteractive-value)))

ob-jupyter

(use-package
 jupyter
 :demand t
 :custom (jupyter-repl-echo-eval-p t)
 :config
 (defun my-org-babel-jupyter-use-python-ts-mode (&rest _)
   "Set python-ts-mode for jupyter-python in org-src-lang-modes."
   (setf (alist-get "jupyter-python" org-src-lang-modes
                    nil
                    nil
                    #'equal)
         "python-ts"))
 (advice-add
  'org-babel-jupyter-make-local-aliases
  :after #'my-org-babel-jupyter-use-python-ts-mode))

Org Markdown

This is really only used in the publishing script of my emacs config, I don’t need to load this on every startup.

ox-md

(use-package ox-jekyll-md :init (setq org-jekyll-md-include-yaml-front-matter nil))

org-jekyll-lite

This is really only used in the publishing script of my emacs config, I don’t need to load this on every startup.

(use-package
 ox-jekyll-lite
 :after org
 :straight
 '(ox-jekyll-lite
   :type git
   :host github
   :repo "jsigman/ox-jekyll-lite"))

Org Appear

(use-package
 org-appear
 :straight
 '(org-appear :type git :host github :repo "awth13/org-appear")
 :after org
 :hook (org-mode-hook . org-appear-mode))

Org Ref

(use-package request)
(use-package
 org-ref
 :after org
 :config
 (setq org-latex-pdf-process
       (list "latexmk -shell-escape -bibtex -f -pdf %f")))

Magit and Version Control

Magit

(use-package
 magit
 :demand t
 :init
 (setq magit-delete-by-moving-to-trash nil)
 :bind
 (:map
  magit-mode-map
  ("C-x g" . nil)
  ("M-G" . nil)
  ("M-0" . nil)
  ("M-1" . nil)
  ("M-2" . nil)
  ("M-3" . nil))
 :bind (:map global-map ("M-G" . magit-status))
 :config (use-package transient))

Forge

Haven’t yet found a great use for this.

(use-package
 forge
 :after magit
 :config
 ; This is so we can access forge information
 (setq auth-sources '("~/.authinfo.gpg"))
 (setq epa-pinentry-mode 'loopback)
 (setq forge-status-buffer-default-topic-filters
       (forge--topics-spec
        :type 'pullreq
        :active t
        :state 'open
        :order 'newest)))
(use-package ghub :defer t)

Magit Todos mode

This is currently broken for me.

(use-package
 magit-todos
 :straight t
 :after magit
 :hook (magit-mode . magit-todos-mode)
 :config
 (setq magit-todos-scanner 'magit-todos--scan-with-rg)
 (setq magit-todos-keywords '("TODO" "FIXME" "BUG" "HACK"))
 (setq magit-todos-ignore-case t)
 (setq magit-todos-search-regexp "\\b(TODO|FIXME|BUG|HACK)\\b")
 (setq magit-todos-keyword-suffix "")
 (magit-todos-mode 1))

Homegrown Magit TODOs

(require 'magit)

(defface magit-todos-line-number
  '((t :inherit magit-filename :weight bold))
  "Face for line numbers in Magit-Todos."
  :group 'magit-faces)

(defvar magit-todos-debug nil
  "Enable debug messages for magit-todos.")

(defun magit-todos-debug-message (format-string &rest args)
  "Print a debug message if `magit-todos-debug' is non-nil."
  (when magit-todos-debug
    (apply #'message
           (concat "magit-todos debug: " format-string)
           args)))

(defun magit-todos-jump-to-file (file line-number)
  "Jump to FILE at LINE-NUMBER."
  (magit-todos-debug-message "Jumping to %s:%s" file line-number)
  (let ((full-path (expand-file-name file (magit-toplevel))))
    (if (file-exists-p full-path)
        (progn
          (find-file full-path)
          (goto-char (point-min))
          (forward-line (1- line-number)))
      (message "File not found: %s" full-path))))

(defun magit-todos-jump-to-todo ()
  "Jump to the TODO at point."
  (interactive)
  (let ((file (get-text-property (point) 'magit-todos-file))
        (line-number (get-text-property (point) 'magit-todos-line)))
    (magit-todos-debug-message
     "magit-todos-jump-to-todo called with %s:%s"
     file line-number)
    (if (and file line-number)
        (magit-todos-jump-to-file file line-number)
      (magit-todos-debug-message
       "No file or line number found at point"))))

(defun magit-insert-todos ()
  "Insert a section for TODOs found in comments."
  (magit-todos-debug-message "Inserting TODOs section")
  (magit-insert-section
   (todos) (magit-insert-heading "TODOs")
   (let* ((root (magit-toplevel))
          (todos-output
           (shell-command-to-string
            (concat "rg --vimgrep '(#|//|;|<!--)\\s*TODO' " root)))
          (todos-list '()))
     (magit-todos-debug-message "Found %d lines of TODOs"
                                (length
                                 (split-string todos-output "\n" t)))
     ;; Parse and collect todos
     (dolist (line (split-string todos-output "\n" t))
       (when (string-match
              "\\([^:]+\\):\\([0-9]+\\):\\([0-9]+\\):\\(.*\\)" line)
         (let ((file (file-relative-name (match-string 1 line) root))
               (line-number (string-to-number (match-string 2 line)))
               (content (match-string 4 line)))
           (push (list file line-number content) todos-list))))
     ;; Sort todos by filename and line number
     (setq todos-list
           (sort todos-list
                 (lambda (a b)
                   (or (string< (car a) (car b))
                       (and (string= (car a) (car b))
                            (< (cadr a) (cadr b)))))))
     (magit-todos-debug-message "Sorted %d TODOs" (length todos-list))
     ;; Insert sorted todos
     (dolist (todo todos-list)
       (let* ((file (nth 0 todo))
              (line-number (nth 1 todo))
              (content (nth 2 todo))
              (todo-string
               (concat
                (propertize file 'face 'magit-filename) ":"
                (propertize (number-to-string line-number)
                            'face 'magit-todos-line-number)
                ": " content)))
         (magit-insert-section
          (todo)
          (insert
           (propertize todo-string
                       'magit-todos-file
                       file
                       'magit-todos-line
                       line-number
                       'keymap
                       (let ((map (make-sparse-keymap)))
                         (define-key
                          map (kbd "RET") 'magit-todos-jump-to-todo)
                         (define-key
                          map [mouse-1] 'magit-todos-jump-to-todo)
                         map)
                       'mouse-face
                       'highlight
                       'help-echo
                       "mouse-1 or RET: visit this TODO"))
          (insert "\n") (magit-insert-heading)))))))

(define-minor-mode magit-todos-mode
  "Display TODOs in Magit status using rg."
  :lighter " Magit-Todos"
  :global
  t
  (if magit-todos-mode
      (magit-todos-mode-enable)
    (magit-todos-mode-disable)))

(defun magit-todos-mode-enable ()
  "Enable Magit-Todos integration."
  (magit-todos-debug-message "Enabling magit-todos-mode")
  (add-hook 'magit-status-sections-hook 'magit-insert-todos t))

(defun magit-todos-mode-disable ()
  "Disable Magit-Todos integration."
  (magit-todos-debug-message "Disabling magit-todos-mode")
  (remove-hook 'magit-status-sections-hook 'magit-insert-todos))

(provide 'magit-todos)

;;; magit-todos.el ends here

;; Automatically enable the mode
(magit-todos-mode 1)

Magit coloring

git-gutter

(use-package
 git-gutter+
 :config (global-git-gutter+-mode t)

 (setq git-gutter+-modified-sign " ") ;; two space
 (setq git-gutter+-added-sign "+") ;; multiple character is OK
 (setq git-gutter+-deleted-sign "-")

 (set-face-foreground 'git-gutter+-modified "magenta")
 (set-face-foreground 'git-gutter+-added "dark green")
 (set-face-foreground 'git-gutter+-deleted "red"))

; add this so that git gutter plus does not screw up tramp sessions
; it disables GGP during tramp
(defun git-gutter+-refresh ()
  (git-gutter+-clear)
  (when (not (file-remote-p (buffer-file-name)))
    (let ((file (buffer-file-name)))
      (when (and file (file-exists-p file))
        (if (file-remote-p file)
            (let* ((repo-root (git-gutter+-root-directory file))
                   (default-directory
                    (git-gutter+-remote-default-directory
                     repo-root file)))
              (git-gutter+-process-diff
               (git-gutter+-remote-file-path repo-root file)))
          (git-gutter+-process-diff
           (git-gutter+-local-file-path file)))))))

diff-hl

(use-package
 diff-hl
 :config (global-diff-hl-mode)
 (add-hook 'magit-pre-refresh-hook 'diff-hl-magit-pre-refresh)
 (add-hook 'magit-post-refresh-hook 'diff-hl-magit-post-refresh))

Ediff

(use-package
 ediff
 :defer t
 :init (setq ediff-split-window-function 'split-window-horizontally)
 (setq ediff-window-setup-function
       'ediff-setup-windows-plain)
 (setq ediff-diff-options "-w"))

Treemacs

Treemacs base packages

(use-package
 treemacs
 :bind (("M-P" . treemacs))
 :config (setq treemacs-persist-file "treemacs-persist"))

Addons

(use-package treemacs-projectile)
(use-package treemacs-icons-dired :config (treemacs-icons-dired-mode))

Appearance

;; Treat all themes as safe; no query before use.
(setf custom-safe-themes 't)
(setq frame-title-format nil)

Emacs Theme

Leuven

Leuven is always a nice option, beautiful with Org Mode.

(use-package leuven-theme
  :config
  (load-theme 'leuven-dark t))

Catpuccin Theme

Trying this theme out for a little bit.

(use-package
 catppuccin-theme
 :config
 (load-theme 'catppuccin :no-confirm)
 (setq catppuccin-flavor 'frappe)
 (catppuccin-reload))

ef-day

I’m always changing my theme, but I tend to like light themes with yellow-ish backgrounds. Almost like handwriting with a yellow memo pad.

(use-package ef-themes :config (load-theme 'ef-day t))

Gruvbox-dark

(use-package
 gruvbox-theme
 :ensure t
 :config (load-theme 'gruvbox-dark-hard :no-confirm)
 ;; :after (centered-window-mode) :hook (centered-window-mode . cwm-update-fringe-background)
)

Nano

(use-package
 nano
 :straight
 (nano-emacs :type git :host github :repo "rougier/nano-emacs")
 :config (require 'nano))

Colors in Dired

(use-package rainbow-mode :hook (LaTeX-mode . rainbow-mode))
(use-package dired-hacks :hook (emacs-lisp-mode . rainbow-mode))

Catch if running centered-window-mode

This may be needed to set the fringe to the new background color. Run it when startup is finished.

(add-hook 'emacs-startup-hook
          (lambda ()
            (when (fboundp 'cwm-update-fringe-background)
              (cwm-update-fringe-background))))

Fonts

;; Font settings
(if (eq system-type 'darwin)
    (if (and (display-graphic-p) (> (x-display-pixel-width) 1440))
        ;; Set default font larger if on a big screen
        (set-face-font 'default "roboto mono-15")
      ;; (set-face-font 'default "arial-15")

      ;; else
      (set-face-font 'default "roboto mono-14")
      ;; (set-fontset-font "fontset-default" "Menlo 12")
      )
  ;; else
  (if (not (eq window-system nil))
      (if (and (display-graphic-p) (> (x-display-pixel-width) 1440))
          ;; Set default font larger if on a big screen
          (set-face-font 'default "roboto mono-12")
        ;; else
        (set-face-font 'default "roboto mono-12")
        ;; (set-fontset-font "fontset-default" "Menlo 12")
        )
    ;; else
    ))

Modeline

Simple Modeline

(use-package
 simple-modeline
 :init
 (setq simple-modeline-segments
       '((simple-modeline-segment-modified
          simple-modeline-segment-buffer-name
          simple-modeline-segment-position)
         (
          ;; simple-modeline-segment-minor-modes
          simple-modeline-segment-input-method
          simple-modeline-segment-eol
          simple-modeline-segment-encoding
          simple-modeline-segment-vc
          simple-modeline-segment-misc-info
          simple-modeline-segment-process
          simple-modeline-segment-major-mode)))
 :hook (after-init . simple-modeline-mode))

Telephone Line

(use-package
 telephone-line
 :config
 (setq telephone-line-lhs
       '((evil . (telephone-line-evil-tag-segment))
         (accent
          .
          (telephone-line-vc-segment
           telephone-line-erc-modified-channels-segment
           telephone-line-process-segment))
         (nil
          .
          (telephone-line-minor-mode-segment
           telephone-line-buffer-segment))))
 (setq telephone-line-rhs
       '((nil . (telephone-line-misc-info-segment))
         (accent . (telephone-line-major-mode-segment))
         (evil . (telephone-line-airline-position-segment))))
 (telephone-line-mode t))

Doom Modeline

Bugfix: Requires installing shrink path manually on linux. Not sure why.

(use-package
 shrink-path
 :straight
 '(shrink-path :type git :host github :repo "zbelial/shrink-path.el"))
(use-package
 doom-modeline
 :config
 (doom-modeline-mode 1)
 (setq doom-modeline-unicode-fallback t)
 (defun doom-modeline--project-root ()
   "Get the path to the project root.
Return nil if no project was found."
   (or doom-modeline--project-root
       (setq doom-modeline--project-root
             (cond
              ((and (memq
                     doom-modeline-project-detection '(auto ffip))
                    (fboundp 'ffip-project-root))
               (let ((inhibit-message t))
                 (ffip-project-root)))
              ((and (memq
                     doom-modeline-project-detection
                     '(auto projectile))
                    (bound-and-true-p projectile-mode))
               (projectile-project-root))
              ((and (memq
                     doom-modeline-project-detection '(auto project))
                    (fboundp 'project-current))
               (when-let ((project (project-current)))
                 (expand-file-name
                  (if (fboundp 'project-root)
                      (project-root project)
                    (car
                     (with-no-warnings
                       (project-roots project))))))))))))

Awesome Tray

(use-package
 awesome-tray
 :straight
 '(awesome-tray
   :type git
   :host github
   :repo "manateelazycat/awesome-tray")
 :init
 (setq awesome-tray-active-modules
       '("buffer-name" "mode-name" "belong"))
 :config (awesome-tray-mode 1) awesome-tray-active-modules)

Icons

All the icons

(use-package all-the-icons)
(use-package
 all-the-icons-ibuffer
 :hook (ibuffer-mode . all-the-icons-ibuffer-mode))

;; I don't think I like buffer expose after all
(use-package
 all-the-icons-completion
 :config (all-the-icons-completion-mode)
 :hook (marginalia-mode . all-the-icons-completion-marginalia-setup))

SVG Support

; Re-enable with SVG support
(use-package
 svg-lib
 :straight '(svg-lib :host github :repo "emacs-straight/svg-lib"))
(use-package
 kind-icon
 :straight '(kind-icon :host github :repo "jdtsmith/kind-icon")
 :ensure t
 :after corfu
 :config
 ; On my linux machine, I need to use smaller icons like this due to the 4k display
 (when (eq system-type 'gnu/linux)
   (setq kind-icon-default-style
         '(:padding
           -1
           :stroke 0
           :margin 0
           :radius 0
           :height 0.5
           :scale 1.0)))
 :custom
 (kind-icon-default-face 'corfu-default) ; to compute blended backgrounds correctly
 :config (add-to-list 'corfu-margin-formatters #'kind-icon-margin-formatter))

Fuzzy

(use-package fuzzy)
(use-package fuzzy-match)

Free keys

(use-package free-keys)
(use-package restart-emacs)
; ---- Auto Revert Modes ----- ;

Auto Revert on Images

(autoload 'eimp-mode "eimp" "Emacs Image Manipulation Package." t)
(add-hook 'image-mode-hook 'auto-revert-mode)

; --- CSV --- ;

CSV Mode

(use-package
 csv-mode
 :straight
 '(csv-mode :type git :host github :repo "emacsmirror/csv-mode"))

Explain Pause Mode

(use-package
 explain-pause-mode
 :straight
 '(explain-pause-mode
   :type git
   :host github
   :repo "lastquestion/explain-pause-mode"))

Helpful

;; use helpful instead of the normal help buffers
;; Note that the built-in `describe-function' includes both functions
;; and macros. `helpful-function' is functions only, so we provide
;; `helpful-callable' as a drop-in replacement.
(use-package
 helpful
 :defer t
 :bind
 ("C-h f" . helpful-callable)
 ("C-h v" . helpful-variable)
 ("C-h k" . helpful-key))

Dimmer

(use-package
 dimmer
 :config
 (dimmer-configure-which-key)
 (dimmer-configure-org)
 (dimmer-configure-posframe)
 (dimmer-configure-magit)
 (dimmer-configure-hydra)

 (setq dimmer-fraction 0.15)
 (dimmer-mode t))

Volatile Highlights

(use-package
 volatile-highlights
 :config (volatile-highlights-mode t))

Highlight todos

(use-package hl-todo :init (global-hl-todo-mode))

Tree-Sitter

Tree-Sitter mode

This is the old way of using tree-sitter in emacs. In emacs 29, this moved to treesit.

(use-package
 tree-sitter
 :config (global-tree-sitter-mode t)
 :hook ((tree-sitter-after-on-hook . tree-sitter-hl-mode) (python-mode . tree-sitter-hl-mode)))
(use-package tree-sitter-langs)

Combobulate

Combobulate seems like a cool idea, but I’ve found it to be more frustrating than helpful in python.

(use-package
 combobulate
 :straight '(combobulate :type git :host github :repo "mickeynp/combobulate")
 ;; You can manually enable Combobulate with `M-x
 ;; combobulate-mode'.
 :hook
 ((python-ts-mode . combobulate-mode)
  (js-ts-mode . combobulate-mode)
  (css-ts-mode . combobulate-mode)
  (yaml-ts-mode . combobulate-mode)
  (typescript-ts-mode . combobulate-mode)
  (tsx-ts-mode . combobulate-mode))
 ;; Amend this to the directory where you keep Combobulate's source
 ;; code.
 :bind
 (:map
  combobulate-key-map (("C-;" . combobulate-avy-jump) ("M-k" . nil))))

Treesit

(defun copy-different-key-bindings (source-map dest-map)
  "Copy key bindings from SOURCE-MAP to DEST-MAP, but only where they differ."
  (map-keymap
   (lambda (binding value)
     (unless (eq value (lookup-key dest-map (vector binding)))
       (define-key dest-map (vector binding) value)))
   source-map))

(defun merge-mode-hooks (source-hook dest-hook)
  "Merge hooks from SOURCE-HOOK to DEST-HOOK without duplicates."
  (dolist (hook (symbol-value source-hook))
    (unless (memq hook (symbol-value dest-hook))
      (add-hook dest-hook hook))))

Treesit Auto

(use-package
 treesit-auto
 :straight
 '(treesit-auto :type git :host github :repo "renzmann/treesit-auto")
 :demand t
 :init
 (setq treesit-auto-install t)
 (setq treesit-auto-langs
       '(awk
         bash
         bibtex
         c
         cmake
         commonlisp
         cpp
         css
         dockerfile
         html
         java
         javascript
         json
         make
         markdown
         proto
         python
         ruby
         toml
         typescript
         yaml))
 :config (global-treesit-auto-mode))

Merge mode hooks in treesit

I want the same hooks to be used in treesit as in the original mode.

;; Merge hooks for relevant modes
(merge-mode-hooks 'python-mode-hook 'python-ts-mode-hook)
(merge-mode-hooks 'yaml-mode-hook 'yaml-ts-mode-hook)
(merge-mode-hooks 'html-mode-hook 'html-ts-mode-hook)
(merge-mode-hooks 'json-mode-hook 'json-ts-mode-hook)
;; (merge-mode-hooks 'js-mode-hook 'js-ts-mode-hook)
;; (merge-mode-hooks 'markdown-mode-hook 'markdown-ts-mode-hook)
(merge-mode-hooks 'sh-mode-hook 'bash-ts-mode-hook)


;; Copy different key bindings
(copy-different-key-bindings python-mode-map python-ts-mode-map)
(copy-different-key-bindings yaml-mode-map yaml-ts-mode-map)
(copy-different-key-bindings html-mode-map html-ts-mode-map)
(copy-different-key-bindings json-mode-map json-ts-mode-map)
;; (copy-different-key-bindings markdown-mode-map markdown-ts-mode-map)
(copy-different-key-bindings sh-mode-map bash-ts-mode-map)



Enjoy Reading This Article?

Here are some more articles you might like to read next:

  • Unison Sync in Emacs
  • Studying Eigenvalues of Rotation Group Matrices
  • Why we can interpret softmax scores as probabilities
  • Indicating which blocks are loaded in webpage literate elisp
  • Hosting my CV with github actions