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"
user-emacs-directory))
(bootstrap-version 6))
(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–read-all-buffer-forms'.
(eval-after-load "elisp-refs"
'(advice-add
'elisp-refs–read-all-buffer-forms
:around #'literate-elisp-refs–read-all-buffer-forms))
;; To make \`elisp-refs' work with \`literate-elisp', we need to add an advice to \`elisp-refs–loaded-paths'.
(eval-after-load "elisp-refs"
'(advice-add
'elisp-refs–loaded-paths
:filter-return #'literate-elisp-refs–loaded-paths))
;; To make \`helpful' work with \`literate-elisp', we need to add an advice to \`helpful–find-by-macroexpanding'.
(eval-after-load 'helpful
'(advice-add
'helpful–find-by-macroexpanding
:around #'literate-elisp-helpful–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
(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)
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"))
:config
(when (memq window-system '(mac ns x))
(exec-path-from-shell-initialize)))
Terminal
Vterm
(use-package
vterm
:init
(setq vterm-always-compile-module t)
(setq vterm-timer-delay 0.01)
:config
(define-key
vterm-mode-map (kbd "M-k")
(lambda ()
(interactive)
(kill-this-buffer nil)))
;; (define-key vterm-mode-map (kbd "M-i") 'previous-multiframe-window)
;; (define-key vterm-mode-map (kbd "M-o") 'next-multiframe-window)
(define-key vterm-mode-map (kbd "M-0") 'delete-window)
(define-key vterm-mode-map (kbd "M-1") 'delete-other-windows)
(define-key vterm-mode-map (kbd "M-2") 'split-window-below)
(define-key vterm-mode-map (kbd "M-3") 'split-window-right)
(define-key vterm-mode-map (kbd "M-T") 'vterm-toggle)
(define-key vterm-mode-map (kbd "M-R") 'vterm-toggle-cd)
; Unbind M-s so we can use this for other commands
(define-key vterm-mode-map (kbd "M-s") 'nil)
(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)
(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)
(defun my-vterm-scroll-to-top (orig-fun &rest args)
"Ensure vterm buffer is scrolled to the top after creation."
(let ((buf (apply orig-fun args)))
(with-current-buffer buf
(goto-char (point-min))
(vterm-clear-scrollback)
(recenter-top-bottom 0))
buf))
(advice-add 'vterm :around #'my-vterm-scroll-to-top)
(advice-add 'vterm-other-window :around #'my-vterm-scroll-to-top))
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
(setq vterm-toggle-fullscreen-p nil)
(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
(add-to-list
'ctrlf-minibuffer-bindings '("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
: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 Tree
I haven’t found a great way to use undo-tree, but everyone seems to love it.
(use-package undo-tree :init (global-undo-tree-mode))
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))
Breadcrumb mode
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)
(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)))))
(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)
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 mode
(defvar unison-sync-queue nil
"Queue of Unison sync commands to run.")
(defvar unison-sync-running nil
"Flag to check if a Unison sync is currently running.")
(defvar-local unison-root1 nil
"The first root directory for Unison sync.")
(defvar-local unison-root2 nil
"The second root directory for Unison sync.")
(defvar-local unison-excluded nil
"List of patterns to exclude in Unison sync.")
(defcustom unison-one-way-sync nil
"Specify whether to perform one-way synchronization.
If non-nil, Unison will only propagate changes from `unison-root1` to `unison-root2`."
:type 'boolean
:group 'unison)
(defun unison-build-command ()
"Build the Unison command based on directory local variables."
(when (and unison-root1 unison-root2)
(let ((command
(concat
"unison -batch " unison-root1 " " unison-root2 " -auto")))
(dolist (pattern unison-excluded)
(setq command (concat command " -ignore 'Name " pattern "'")))
(when unison-one-way-sync
(setq command (concat command " -force " unison-root1)))
command)))
(defun unison-process-next-command ()
"Process the next command in the queue if not currently running."
(when (and (not unison-sync-running) unison-sync-queue)
(let ((command (pop unison-sync-queue)))
(unison-run-command command))))
(defun unison-run-command (command)
"Run a Unison command."
(let ((output-buffer (get-buffer-create "*Unison Sync*")))
(with-current-buffer output-buffer
(read-only-mode -1)
(erase-buffer)
(insert (format "Running command: %s\n\n" command)))
(setq unison-sync-running t)
(let ((process
(start-process-shell-command
"unison-sync" output-buffer command)))
(set-process-sentinel process 'unison-sync-sentinel))))
(defun unison-sync-sentinel (process event)
"Handle completion of a Unison process."
(setq unison-sync-running nil)
(unison-process-next-command) ; Process next command in the queue
(with-current-buffer (process-buffer process)
(goto-char (point-max))
(insert (format "\nProcess %s %s" process event))
(if (zerop (process-exit-status process))
(message
(propertize "Unison sync completed successfully"
'face
'(:foreground "green")))
(message
(propertize (format "Unison sync failed: %s" event)
'face
'(:foreground "red"))))))
(defun unison-sync-on-save ()
"Queue Unison sync command on file save."
(let ((command (unison-build-command)))
(when command
(push command unison-sync-queue)
(unison-process-next-command))))
(define-minor-mode unison-sync-mode
"Minor mode to sync the current project using Unison on file save."
:lighter
" Unison-Sync"
(if unison-sync-mode
(add-hook 'after-save-hook 'unison-sync-on-save nil t)
(remove-hook 'after-save-hook 'unison-sync-on-save t)))
(defun maybe-enable-unison-sync-mode ()
"Enable `unison-sync-mode` if `unison-root1` and `unison-root2` are set."
(when (and unison-root1 unison-root2)
(unison-sync-mode 1)))
(add-hook 'hack-local-variables-hook 'maybe-enable-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))
(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-mode 0)
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
I’m trying migrating from projectile
onto project.el
for its purported benefits. project.el
and eglot
and Flymake
don’t like to play nice unless you use the built in. To do this with straight.el
, you have to give it a specific keyword.
(use-package
project
:demand t
:straight (:type built-in)
:bind-keymap ("C-c p" . project-prefix-map)
:config
(setq project-vc-ignores
'("venv"
"data"
"typings"
"node_modules"
".mypy_cache"
".pytest_cache"
".cache"
".dvc/cache"
".dvc/tmp"
".jekyll-cache"))
;; Function to add ignored directories to project-vc-ignores
(defun my/add-project-ignore (dir)
(add-to-list 'project-vc-ignores dir))
;; Add each directory 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))
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
:demand 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)
: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
:after consult
: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'.
: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
LSP Mode
lsp package setup
(setq my/lsp-package "lsp")
;------------------------LSP---------------------------;
; PATCH BELOW
(setenv "LSP_USE_PLISTS" "true")
(use-package
lsp-mode
:custom (lsp-completion-provider :none) ; We use corfu
:demand t
:init
(setq lsp-signature-render-documentation t)
(setq lsp-signature-auto-activate t)
(setq lsp-signature-doc-lines 3)
(setq lsp-prefer-flymake nil)
(setq lsp-idle-delay 0.0)
(setq lsp-enable-snippet t)
(setq lsp-keymap-prefix "M-P")
(setq lsp-log-io nil)
(setq lsp-print-performance nil)
(setq lsp-response-timeout 10)
(setq lsp-enable-on-type-formatting nil)
(setq lsp-keep-workspace-alive nil)
(setq lsp-headerline-breadcrumb-enable-diagnostics nil)
(setq lsp-headerline-breadcrumb-enable nil)
(setq read-process-output-max (* 1024 1024))
(setq lsp-semantic-highlighting-faces t)
(setq lsp-enable-imenu nil)
(setq lsp-enable-text-document-color t)
(setq lsp-completion-no-cache t)
(setq lsp-completion-filter-on-incomplete nil)
(setq lsp-enable-file-watchers t)
(setq lsp-auto-execute-action nil)
(setq lsp-auto-guess-root 't)
(setq lsp-document-sync-method nil)
(setq lsp-completion-use-last-result nil)
(setq lsp-completion-show-kind t)
(setq lsp-completion-show-detail t)
;; (defun my/lsp-mode-setup-completion ()
;; (setf (alist-get
;; 'styles
;; (alist-get 'lsp-capf completion-category-defaults))
;; '(orderless))) ;; Configure orderless
(defun my/lsp-mode-setup-completion ()
(setf (alist-get
'styles
(alist-get 'lsp-capf completion-category-defaults))
'(orderless)))
;; Optionally configure the cape-capf-buster.
(setq-local completion-at-point-functions
(list (cape-capf-buster #'lsp-completion-at-point)))
:hook
(lsp-mode . lsp-enable-which-key-integration)
(lsp-completion-mode . my/lsp-mode-setup-completion)
;; (lsp-mode . lsp-ui-mode)
)
(add-hook 'LaTeX-mode-hook #'lsp-deferred)
(add-hook 'yaml-base-mode-hook #'lsp-deferred)
(add-hook 'json-mode-hook #'lsp-deferred)
(add-hook 'dockerfile-mode-hook #'lsp-deferred)
(setq lsp-enable-on-type-formatting t)
(setq lsp-enable-indentation t)
(add-hook 'sh-mode-hook #'lsp-deferred)
(use-package lsp-mssql :hook (sql-mode . lsp))
;; (use-package
;; lsp-markdown
;; :config (add-hook 'markdown-mode-hook 'lsp-markdown))
(add-to-list 'lsp-file-watch-ignored-directories "[/\\\\]\\venv\\'")
(add-to-lis 'lsp-file-watch-ignored-directories "[/\\\\]\\.dvc\\'")
(add-to-list
'lsp-file-watch-ignored-directories "[/\\\\]\\kubernetes\\'")
(add-to-list 'lsp-file-watch-ignored-directories "[/\\\\]\\notes\\'")
(add-to-list
'lsp-file-watch-ignored-directories "[/\\\\]\\checkpoints\\'")
(add-to-list
'lsp-file-watch-ignored-directories "[/\\\\]\\experiments\\'")
(add-to-list
'lsp-file-watch-ignored-directories "[/\\\\]\\.mypy_cache\\'")
(add-to-list
'lsp-file-watch-ignored-directories "[/\\\\]\\.pytest_cache\\'")
lsp-ui
For right now, I don’t really like LSP UI, let’s not load this config
(use-package
lsp-ui
:config
(setq lsp-ui-doc-enable nil)
(setq lsp-ui-peek-enable nil)
(setq lsp-ui-peek-fontify "never") ;; never, on-demand, or always
(setq lsp-ui-peek-list-width 50)
(setq lsp-ui-peek-peek-height 20)
(setq lsp-ui-sideline-code-actions-prefix "")
(setq lsp-ui-doc nil)
;inline right flush docs
(setq lsp-ui-sideline-enable nil)
(setq lsp-ui-sideline-ignore-duplicate t)
(setq lsp-ui-sideline-show-code-actions nil)
(setq lsp-ui-sideline-show-diagnostics nil)
(setq lsp-ui-sideline-show-hover nil)
(setq lsp-ui-sideline-show-symbol nil)
(setq lsp-ui-sideline-delay 0.01)
(add-hook
'lsp--managed-mode-hook
(lambda nil
(interactive)
(remove-hook 'post-self-insert-hook 'lsp--on-self-insert t)))
(define-key
lsp-ui-mode-map
[remap xref-find-definitions]
#'lsp-ui-peek-find-definitions)
(define-key
lsp-ui-mode-map
[remap xref-find-references]
#'lsp-ui-peek-find-references)
(setq lsp-ui-flycheck-enable nil)
(add-hook 'flycheck-mode-hook 'lsp-ui-sideline-mode)
(require 'lsp-ui-flycheck)
(define-key
lsp-ui-flycheck-list-mode-map
(kbd "<M-RET>")
'lsp-ui-flycheck-list--visit)
(define-key
lsp-ui-flycheck-list-mode-map
(kbd "RET")
'lsp-ui-flycheck-list--view)
(setq lsp-ui-doc-header t)
(setq lsp-ui-doc-include-signature t)
(setq lsp-ui-doc-max-height 30)
(setq lsp-ui-doc-max-width 120)
(setq lsp-ui-doc-position (quote at-point))
(setq lsp-ui-doc-use-childframe nil)
(setq lsp-ui-doc-use-webkit nil)
:bind
(:map
lsp-ui-imenu-mode-map
("<return" . lsp-ui-imenu--view)
("RET" . lsp-ui-imenu--view)))
lsp-iedit
I’m now using iedit instead of my hand-crafted lsp-mc mode. It is integrated and works slightly better.
(use-package iedit :init (setq iedit-toggle-key-default (kbd "C-,")))
(defun my/lsp-iedit ()
(lsp-iedit-highlights)
)
(setq my/lsp-highlight-setup "iedit")
(pcase my/lsp-highlight-setup
("iedit" (use-package
iedit
:init
(setq iedit-toggle-key-default nil)
(setq iedit-auto-narrow t)
:bind
(:map
lsp-mode-map
("M-S" . lsp-iedit-highlights)
(:map iedit-mode-keymap ("C-g" . iedit--quit)))
:after (:all lsp-mode)
:custom-face (iedit-occurrence ((t (:background "Red"))))))
("lsp-mc" (straight-use-package
'(lsp-mc :type git :host github :repo "jsigman/lsp-mc"))
(require 'lsp-mc) (add-hook 'lsp-mode-hook 'lsp-mc-mode)))
LSP Docker
This mode is used to run language servers in a docker container. At some point I’d like to use this.
(use-package lsp-docker)
Pyright LSP
(add-hook 'python-mode-hook #'lsp-deferred)
(setq lsp-diagnostics-disabled-modes
'(python-mode python-mode python-ts-mode))
(use-package
lsp-pyright
:straight
'(lsp-pyright :type git :host github :repo "emacs-lsp/lsp-pyright")
:init (setq lsp-pyright-multi-root nil)
(setq lsp-pyright-auto-import-completions nil)
(setq lsp-pyright-diagnostic-mode "workspace")
(setq lsp-pyright-typechecking-mode "basic")
(setq lsp-pyright-disable-organize-imports t)
:config (setq lsp-pyright-log-level "trace"))
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
org-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")))
(add-to-list 'eglot-server-programs '(org-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))
(use-package
highlight-indent-guides
:hook (python-mode . highlight-indent-guides-mode))
(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
(when (not (eq system-type 'gnu/linux))
(require 'html-ts-mode)
(define-key html-ts-mode-map (kbd "M-o M-o") nil)
(define-key html-mode-map (kbd "M-o M-o") nil)
(define-key sgml-mode-map (kbd "M-o M-o") nil)
(define-key html-mode-map (kbd "M-o") nil)
(define-key sgml-mode-map (kbd "M-o") nil)
(define-key html-mode-map (kbd "M-o") 'next-multiframe-window)
(define-key html-mode-map (kbd "M-i") 'previous-multiframe-window)
(define-key html-ts-mode-map (kbd "M-o M-o") nil)
(define-key html-ts-mode-map (kbd "M-o") nil)
(define-key html-ts-mode-map (kbd "M-o") 'next-multiframe-window)
(define-key
html-ts-mode-map (kbd "M-i") 'previous-multiframe-window))
Web mode
(use-package
web-mode
:config
(add-to-list 'auto-mode-alist '("\\.liquid\\'" . web-mode)))
Lua
Non-gnu ELPA version
(use-package
lua-mode
:ensure t
:mode ("\\.lsyncd\\.conf\\'" . lua-mode))
lua-ts-mode
(use-package
lua-ts-mode
:straight
'(lua-ts-mode
:type git
:host nil
:repo "https://git.sr.ht/~johnmuhl/lua-ts-mode"
:files ("*.el"))
:mode (("\\.lua\\'" . lua-ts-mode) ("lsyncd\\.conf\\'" . lua-ts-mode))
:config
;; Any additional configuration you might want goes here
)
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)
Docker
(use-package
dockerfile-mode
:config (setq dockerfile-mode-command "docker"))
(use-package docker
:ensure t
:bind ("C-c d" . docker))
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))
Java/Javascript/JSON
(use-package jq-mode)
(use-package
jsonian
:straight '(jsonian :type git :host github :repo "iwahbe/jsonian" :build (:not autoloads)))
Large Language Models in Emacs
Github Copilot
(defcustom my-copilot-server-executable nil
"Path to the Copilot server executable."
:type 'string
:group 'my-customizations)
(defcustom my-copilot-install-dir nil
"Directory in which the Copilot server is installed."
:type 'string
:group 'my-customizations)
(defun set-copilot-server-path ()
"Set the path to the Copilot server executable."
(let
((global-copilot-server-path
"/opt/homebrew/lib/node_modules/copilot-node-server/copilot/dist/language-server.js"))
(if (file-exists-p global-copilot-server-path)
(progn
(setq my-copilot-server-executable
global-copilot-server-path)
(setq my-copilot-install-dir
"/opt/homebrew/lib/node_modules/copilot-node-server"))
;; Fallback to local installation if global server is not found
(let ((local-copilot-server-path
(expand-file-name
"~/.emacs.d/.cache/copilot/bin/copilot-node-server")))
(unless (file-exists-p local-copilot-server-path)
(copilot-install-server))
(setq my-copilot-server-executable local-copilot-server-path)
(setq my-copilot-install-dir
(expand-file-name "~/.emacs.d/.cache/copilot"))))))
;; (set-copilot-server-path)
(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 . copilot-mode)
:hook (yaml-mode . copilot-mode)
:hook (org-mode . copilot-mode)
:hook (direnv-envrc-mode . copilot-mode)
:hook (conf-mode . copilot-mode)
;; :init
;; (setq copilot--server-executable my-copilot-server-executable)
;; (setq copilot-install-dir my-copilot-install-dir)
:config
(setq copilot-max-char -1)
(setq copilot-indent-offset-warning-disable t)
;; (setq copilot-log-level 'debug)
;;
:ensure t)
Codeium
;; we recommend using use-package to organize your init.el
(use-package codeium
;; if you use straight
:straight '(:type git :host github :repo "Exafunction/codeium.el")
;; otherwise, make sure that the codeium.el file is on load-path
:init
;; use globally
(add-to-list 'completion-at-point-functions #'codeium-completion-at-point)
;; or on a hook
;; (add-hook 'python-mode-hook
;; (lambda ()
;; (setq-local completion-at-point-functions '(codeium-completion-at-point))))
;; if you want multiple completion backends, use cape (https://github.com/minad/cape):
;; (add-hook 'python-mode-hook
;; (lambda ()
;; (setq-local completion-at-point-functions
;; (list (cape-super-capf #'codeium-completion-at-point #'lsp-completion-at-point)))))
;; an async company-backend is coming soon!
;; codeium-completion-at-point is autoloaded, but you can
;; optionally set a timer, which might speed up things as the
;; codeium local language server takes ~0.2s to start up
;; (add-hook 'emacs-startup-hook
;; (lambda () (run-with-timer 0.1 nil #'codeium-init)))
;; :defer t ;; lazy loading, if you want
:config
(setq use-dialog-box nil) ;; do not use popup boxes
;; if you don't want to use customize to save the api-key
;; (setq codeium/metadata/api_key "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx")
;; get codeium status in the modeline
(setq codeium-mode-line-enable
(lambda (api) (not (memq api '(CancelRequest Heartbeat AcceptCompletion)))))
(add-to-list 'mode-line-format '(:eval (car-safe codeium-mode-line)) t)
;; alternatively for a more extensive mode-line
;; (add-to-list 'mode-line-format '(-50 "" codeium-mode-line) t)
;; use M-x codeium-diagnose to see apis/fields that would be sent to the local language server
(setq codeium-api-enabled
(lambda (api)
(memq api '(GetCompletions Heartbeat CancelRequest GetAuthToken RegisterUser auth-redirect AcceptCompletion))))
;; you can also set a config for a single buffer like this:
;; (add-hook 'python-mode-hook
;; (lambda ()
;; (setq-local codeium/editor_options/tab_size 4)))
;; You can overwrite all the codeium configs!
;; for example, we recommend limiting the string sent to codeium for better performance
(defun my-codeium/document/text ()
(buffer-substring-no-properties (max (- (point) 3000) (point-min)) (min (+ (point) 1000) (point-max))))
;; if you change the text, you should also change the cursor_offset
;; warning: this is measured by UTF-8 encoded bytes
(defun my-codeium/document/cursor_offset ()
(codeium-utf8-byte-length
(buffer-substring-no-properties (max (- (point) 3000) (point-min)) (point))))
(setq codeium/document/text 'my-codeium/document/text)
(setq codeium/document/cursor_offset 'my-codeium/document/cursor_offset))
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)
(use-package flymake-ruff :ensure t :config (flymake-ruff-load))
(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" "-"))
(add-hook 'python-ts-mode-hook 'flymake-mode)
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))
Dir-locals helper function
I’m still trying to make all my editing features work inside TRAMP with Emacs.
(defun set-remote-venv-path ()
"Set PATH for TRAMP to include the venv/bin directory."
(with-parsed-tramp-file-name default-directory tramp-info
(let* ((absolute-local-directory (if (string= tramp-info-localname "~/")
(tramp-call-process tramp-info-method tramp-info-user tramp-info-host "pwd" nil t nil)
tramp-info-localname))
(remote-path (concat "/" tramp-info-method ":" tramp-info-user "@" tramp-info-host ":" absolute-local-directory)))
(set (make-local-variable 'process-environment)
(append
(list (format "PATH=%svenv/bin:%s"
remote-path
(getenv "PATH")))
process-environment)))))
(defun set-remote-venv-path ()
"Set PATH for TRAMP to include the venv/bin directory."
(with-parsed-tramp-file-name default-directory tramp-info
(let ((remote-path (if (string= tramp-info-localname "~/")
(concat "/" tramp-info-method ":" tramp-info-user "@" tramp-info-host ":/")
(concat "/" tramp-info-method ":" tramp-info-user "@" tramp-info-host ":" tramp-info-localname))))
(set (make-local-variable 'process-environment)
(append
(list (format "PATH=%svenv/bin:%s"
remote-path
(getenv "PATH")))
process-environment)))))
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
(defun basic-remote-try-completion (string table pred point)
(and (vertico--remote-p string)
(completion-basic-try-completion string table pred point)))
(defun basic-remote-all-completions (string table pred point)
(and (vertico--remote-p string)
(completion-basic-all-completions string table pred point)))
(add-to-list
'completion-styles-alist
'(basic-remote
basic-remote-try-completion basic-remote-all-completions nil))
(setq
completion-styles '(orderless basic)
completion-category-defaults nil
completion-category-overrides '((file (styles basic-remote partial-completion))))
;; (defun my-filtered-tramp-ssh-completions ()
;; "Filter TRAMP completions to exclude specific patterns."
;; (let ((completions
;; (tramp-completion-handle-file-name-all-completions "")))
;; (debug)
;; (seq-filter
;; (lambda (c)
;; (not
;; (or (string-suffix-p ".workspace" c)
;; (string-suffix-p "." c)
;; (member c '("github.com" "gitlab.com")))))
;; completions)))
;; ;; Append the custom completion function without overwriting existing handlers
;; (let ((existing-handler
;; (assoc
;; 'tramp-completion-file-name-handler
;; tramp-completion-function-alist)))
;; (if existing-handler
;; (setcdr existing-handler 'my-filtered-tramp-ssh-completions)
;; (setq tramp-completion-function-alist
;; (append
;; tramp-completion-function-alist
;; (list
;; (cons
;; 'tramp-completion-file-name-handler
;; 'my-filtered-tramp-ssh-completions))))))
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)
Org Mode
(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))
(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)
:bind
(:map
org-mode-map
;; ("\C-cl" . org-store-link)
;; ("\C-ca" . org-agenda)
("C-'" . nil)
("C-c C-c" . org-ctrl-c-ctrl-c)
("C-M-<return>" . my/org-babel-execute-and-next)
("C-c <" . nil)
: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)
(org-babel-do-load-languages
'org-babel-load-languages
'((emacs-lisp . t)
(sqlite . t)
(shell . t)
(jq . t)
(jupyter . nil)
(python . t)))
(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)
(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)
(defun my/org-babel-execute-and-next ()
(interactive)
(org-babel-execute-src-block)
(org-babel-next-src-block))
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.
(straight-use-package 'org-roam)
(use-package
org-roam
:ensure t
:custom (org-roam-directory (file-truename "~/Documents/PERSONAL/ORG_ROAM/"))
: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))
:config (org-roam-setup) (require 'org-roam-protocol))
Jupyter
(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)))
(use-package
jupyter
:defer t
:custom (jupyter-repl-echo-eval-p t)
:config
(defun my-switch-to-python-ts-mode-if-jupyter ()
"Switches to python-ts-mode if the org source block is of type jupyter-python."
(when (and (eq major-mode 'python-mode)
(string=
(org-element-property
:language (org-element-at-point))
"jupyter-python"))
(python-ts-mode)))
(add-hook
'org-src-mode-hook 'my-switch-to-python-ts-mode-if-jupyter)
;; (add-to-list 'org-src-lang-modes '("python" . python-ts))
)
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)
(setq vc-handled-backends 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")))
(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 "")
(defun my/magit-todos-safe-pcre-conversion (orig-fun &rest args)
(let ((search-regexp-elisp (plist-get args :search-regexp-elisp)))
(condition-case err
(apply orig-fun args)
(error
(message "Failed to convert regexp to PCRE: %S" err)
(let ((safe-regexp (regexp-opt magit-todos-keywords)))
(apply orig-fun
(plist-put
args
:search-regexp-pcre safe-regexp)))))))
(advice-add
'magit-todos--scan-with-rg
:around #'my/magit-todos-safe-pcre-conversion)
(magit-todos-mode 1))
Homegrown Magit TODOs
(defun magit-insert-todos ()
"Insert a section for TODOs found in comments."
(magit-insert-section (todos)
(magit-insert-heading "TODOs")
(let* ((root (magit-toplevel))
;; Update regex to match common comment formats followed by TODO
(todos-output
(shell-command-to-string
(concat "rg --vimgrep '(#|//|;|<!--)\\s*TODO' " root))))
(dolist (line (split-string todos-output "\n" t))
(when (string-match
"\\([^:]+\\):\\([0-9]+\\):\\([0-9]+\\):\\(.*\\)" line)
(let ((file (match-string 1 line))
(line-number (string-to-number (match-string 2 line)))
(content (match-string 4 line)))
(magit-insert-section (todo)
(insert (format "%s:%d: %s\n" (file-relative-name file root) line-number content))
(magit-insert-heading))))))))
;; Define the minor mode to integrate TODOs into Magit status buffer
(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."
(add-hook 'magit-status-sections-hook 'magit-insert-todos t))
(defun magit-todos-mode-disable ()
"Disable Magit-Todos integration."
(remove-hook 'magit-status-sections-hook 'magit-insert-todos))
(provide 'magit-todos) ;; Optional: only if you plan to use this as a separate package or file.
(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
))
Diminish/Blackout
I find these frustrating as I have to manually include every mode which might appear in the modeline. I’m not sure if there’s a better way to do this.
(use-package diminish)
(diminish 'anzu-mode)
(diminish 'ivy-mode)
(diminish 'rainbow-mode)
(diminish 'eldoc-mode)
(diminish 'projectile-mode)
(diminish 'blacken-mode)
(diminish 'python-black-on-save-mode)
(diminish 'which-key-mode)
(diminish 'flycheck-mode)
(add-hook 'undo-tree-mode-hook (lambda () (diminish 'undo-tree-mode)))
(diminish 'auto-revert-mode)
(diminish 'beacon-mode)
(diminish 'all-the-icons-dired-mode)
(diminish 'tree-sitter-hl-mode)
(use-package blackout)
(blackout 'ivy-mode)
(blackout 'company-posframe-mode)
(blackout 'company-mode)
(diminish 'yas-mode)
(blackout 'yas-minor-mode)
(blackout 'rainbow-mode)
(blackout 'which-key-mode)
(blackout 'projectile-mode)
(blackout 'git-gutter-mode)
(blackout 'git-gutter+-mode)
(blackout 'goggles-mode)
(blackout 'org-roam-mode)
(blackout 'polymode)
(blackout 'poly-org-mode)
(blackout 'smartparens-mode)
(blackout 'visual-line-mode)
(blackout 'anzu-mode)
(blackout 'apheleia-mode)
(blackout 'explain-pause-mode)
(blackout 'volatile-highlights-mode)
(blackout 'all-the-icons-dired-mode)
(blackout 'jupyter-repl-interaction-mode)
(blackout 'page-break-lines-mode)
(blackout 'org-src-mode)
(blackout 'whole-line-or-region-local-mode)
(blackout 'smartparens-mode)
(blackout 'tree-sitter-hl-mode)
(blackout 'tree-sitter-mode)
(blackout 'highlight-indent-guides-mode)
(blackout 'copilot-mode)
(diminish 'jupyter-repl-interaction-mode)
(diminish 'page-break-lines-mode)
(diminish 'org-src-mode)
(diminish 'highlight-indent-guides)
Modeline
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))))
(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
latex
make
markdown
proto
python
ruby
toml
typescript
yaml))
:config (global-treesit-auto-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 '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 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: