Emacs Configuration File

Introduction

This is my Emacs configuration, written in Org mode thanks to Howard Abrahams. Other people that have inspired this are Mike Zamansky, and David Wilson. Make sure to check out their work!

Boostrapping the configuration

Emacs configuration directory

Emacs expects to find configurations in one of several different locations. I will use ~/.emacs.d/ on MacOS and ~/.config/emacs/ on Linux. For more details, you can read about the The Emacs Initialization File in the Emacs manual.

For Emacs to use the configurations in this file, clone the repository and then create symbolic links from the configuration directory to files init.el and emacs.org in this repo.

cd
git clone git@gitlab.com:rickardsundin/dotfiles.git
cd ~/.emacs.d # or ~/.config/emacs
ln -s ~/dotfiles/init.el
ln -s ~/dotfiles/emacs.org

Emacs on MacOS

Installation

MacOS no longer comes with Emacs preinstalled, (versions before Catalina has it at /usr/bin/emacs), but it is easy to install the latest version using Homebrew. I like to use the emacs-plus formula, since it comes with some nifty features and options.

brew install emacs-plus

Running as a service

Homebrew can make Emacs run as a service in the background, and ensure it starts automatically at login. When the service is running, emacsclient can be used to open new editor windows connected to the already running Emacs service.

brew services start emacs-plus

If you want to be able to launch emacsclient from Spotlight (or Alfred), follow this instruction from StackOverflow to create a MacOS application using Automator.

Choose to make an "Application", then choose "Run Shell Script", and add a [the] call to emacsclient:

/usr/local/bin/emacsclient -n -c -a "" – "$@"

Then change "Pass input": use "as arguments" instead of "to stdin".

The added "$@" is where any optional arguments passed to this shell script will be placed.

Feel free too change the application icon to something nicer than the default Automator icon.

I have also defined some shell aliases for quickly opening files using emacsclient from the terminal. See Emacs related in the file .bashrc.

Permission issues

This solves some issues with MacOS permissions and Emacs. See Github issue in emacs-plus for more info.

(when (string= system-type "darwin")
  (setq dired-use-ls-dired t
        insert-directory-program "/opt/homebrew/bin/gls"
        dired-listing-switches "-aBhl --group-directories-first"))

Emacs on Linux

Building from source

Install required dependencies.

sudo apt build-dep emacs

Clone the git repository and branch of your choice (or the entire repo if you have enougth time and storage).

git clone --single-branch --branch emacs-29 https://git.savannah.gnu.org/git/emacs.git
cd emacs/
sudo apt build-dep emacs
./autogen.sh
./configure
make bootstrap -j16
sudo make install

Running as a service

Starting emacs as a service.

systemctl --user enable emacs

Restarting the Emacs service.

systemctl --user restart emacs

Basics

Personal info

Set some basic personal info.

(setq user-full-name "Rickard Sundin")

Appearance

Maximize the size of new windows (a.k.a "frames").

(add-to-list 'default-frame-alist '(fullscreen . maximized))

Hide the toolbar. I never use it, and am not that fond of the look of the icons.

(tool-bar-mode -1)

Hide the scrollbars, for a cleaner look. There is information in the mode line to help with figuring out what part of the current file that is being displayed.

(set-scroll-bar-mode nil)

Setting this value allows for the Emacs window (a.k.a frame) go all the way to the edge of my screen.

(setq frame-resize-pixelwise t)

Remove the startup message and the message explaining the scratch buffer.

(setq inhibit-startup-message t
      initial-scratch-message "")

Show row numbers by default in all modes that show code.

(add-hook 'prog-mode-hook 'display-line-numbers-mode)

Display the current column on the mode line. By default, it will only display the row number.

(setq column-number-mode t)

Show current time in mode line.

(setq display-time-24hr-format t)
(setq display-time-default-load-average nil)
(display-time-mode 1)

The default visible bell draws a big warning sign with an exclamation mark in the middle of the frame. I prefer this slick implementaion that flashes the mode line instead.

(setq visible-bell nil)
(setq ring-bell-function (lambda ()
                           (invert-face 'mode-line)
                           (run-with-timer 0.1 nil 'invert-face 'mode-line)))

Avoid having org-fill-paragraph insert double spaces between sentences.

(setq sentence-end-double-space nil)

No tabs, only spaces.

(setq-default indent-tabs-mode nil)

Delete trailing whitespace before saving buffers.

(add-hook 'before-save-hook 'delete-trailing-whitespace)

Make sure the calendar starts the week on Mondays.

(setq calendar-week-start-day 1)

Show week numbers in calendar.

(setq calendar-intermonth-text
      '(propertize
        (format "%2d"
                (car
                 (calendar-iso-from-absolute
                  (calendar-absolute-from-gregorian (list month day year)))))
        'font-lock-face 'font-lock-warning-face))

Add a header to the week number in calendar.

(setq calendar-intermonth-header
      (propertize "w" 'font-lock-face 'font-lock-keyword-face))

Font

Set fonts and sizes for MacOS and Linux.

(pcase system-type
  ('darwin
   (set-face-attribute 'default nil
                       :family "Menlo"
                       :height 180)

   (set-face-attribute 'variable-pitch nil
                       :family "Charter"
                       :height 220)

   (set-face-attribute 'fixed-pitch nil
                       :family "Menlo"
                       :height 180))

  ('gnu/linux
   (set-face-attribute 'default nil
                       :family "Fira Code"
                       :height 120)

   (set-face-attribute 'variable-pitch nil
                       :family "Bitstream Charter"
                       :height 140)

   (set-face-attribute 'fixed-pitch nil
                       :family "Fira Code"
                       :height 120)))

Backup files

Keep all backup files in one place.

(setq backup-directory-alist
      `(("." . ,(locate-user-emacs-file "backups"))))

Lock files config from the Emacs wiki.

(setq lock-file-name-transforms
      '(("\\`/.*/\\([^/]+\\)\\'" "/var/tmp/\\1" t)))

Custom file

Move customization variables to a separate file (not in init.el) and load it.

(setq custom-file (locate-user-emacs-file "custom-vars.el"))
(load custom-file 'noerror 'nomessage)

Package archives

By default, packages are loaded from GNU Elpa and NonGNU Elpa, but I want to also load some packages that are only published in Melpa. Make sure to use https here.

(with-eval-after-load 'package
  (add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") t))

Use package

By using use-package, which is included in Emacs since version 29, package management and configuration is kept together which gives better readabiltiy and maintainablility.

I want to ensure that all packages are automatically installed if not already present on the current system. Alternatively it can be specified by adding :require t for each package.

(use-package use-package
  :ensure nil ;; rely on the built in version
  :custom
  (use-package-always-ensure t))

Mac stuff

When launching a GUI application in MacOS it does not by default pick up environment variables from .profile. Here is a solution.

(use-package exec-path-from-shell
  :config
  (when (memq window-system '(mac ns))
    (exec-path-from-shell-initialize)))

Color themes

The default color scheme is not my cup of tea, but fortunately it is easy to change. All of the themes I use are created by Protesilaos.

Modus-themes

The Modus themes, Operandi (light) and Vivendi (dark) are sofisticated and very well designed themes, that are built into Emacs (as of version 28).

(use-package emacs
  :custom
  (modus-themes-mixed-fonts t "Use monospaced fonts in tables and blocks")
  :config
  (require-theme 'modus-themes)
  (load-theme 'modus-vivendi))

Ef-themes

Ef-themes are a set of more colorful, but still very legible, themes. It comes with the function ef-themes-load-random for when you have a hard time making up your mind.

(use-package ef-themes
  :custom
  (ef-themes-mixed-fonts t))

Synk theme with desktop

Change the theme when the surrounding environment changes between light and dark mode. On MacOS, emacs-plus provides a hook that triggers when the mode is swithed. On Linux (at least with KDE), we can hook into dbus and listen for a signal.

(pcase system-type

  ('darwin

   (defun my/apply-theme (appearance)
     "Load theme, taking current system APPEARANCE into consideration."
     (pcase appearance
       ('light (modus-themes-load-theme 'modus-operandi))
       ('dark (modus-themes-load-theme 'modus-vivendi))))

   (add-hook 'ns-system-appearance-change-functions #'my/apply-theme))

  ('gnu/linux

   (defun theme--handle-dbus-event (a setting values)
     "Handler for FreeDesktop theme changes."
     (when (string= setting "ColorScheme")
       (let ((scheme (car values)))
         (cond
          ((string-match-p "Dark" scheme)
           (ef-themes-load-random 'dark))
          ((string-match-p "Light" scheme)
           (ef-themes-load-random 'light))
          (t (message "I don't know how to handle scheme: %s" scheme))))))

   (require 'dbus)
   (dbus-register-signal :session
                         "org.freedesktop.portal"
                         "/org/freedesktop/portal/desktop"
                         "org.freedesktop.impl.portal.Settings"
                         "SettingChanged"
                         #'theme--handle-dbus-event)))

Vim

Evil-mode is a near-perfect vim-emulator, so if you are considering switching from Vim, you should enable it.

(use-package evil
  :init
  (setq evil-want-integration t)
  (setq evil-want-keybinding nil)
  :config
  (evil-mode 1))

A small fix to prevent an issue when loading evil-collection before magit-forge. See evil-collection#563 for more details.

Evil-collection is a collection of vim keybindings for many Emacs modules, that is not covered by default evil-mode.

(setq forge-add-default-bindings nil)

(use-package evil-collection
  :after evil
  :diminish evil-collection-unimpaired-mode
  :config
  (evil-collection-init))

Which key?

Enable which-key-mode, bundled with Emacs as of version 30, which displays available keybindings in a popup.

(which-key-mode)

When it is installed, I can type a partial command sequence, for exampel C-x, and after one second a list of all keyboard shortcuts starting with that sequence is presented.

If there are more commands than will fit in the buffer window, is is possible to press C-h n to go to the next page and C-h p to go back again.

Undo tree

Undo-tree allows you to recover any past state of a buffer.

(use-package undo-tree
  :diminish undo-tree-mode
  :custom
  (undo-tree-history-directory-alist `(("." . ,(locate-user-emacs-file ".cache"))))
  :config
  (global-undo-tree-mode))

Dired

Dired is a major mode for directory browsing and editing, so plays the role of a file manager.

Default to the directory of another displayed Dired buffer for operations like copy or move (rename) files.

(setq dired-dwim-target t)

Iedit

Iedit helps renaming things in the same file.

Hitting C-; triggers iedit-mode that will select the word you are currently standing on and all other instances of the same word in the current buffer. Use TAB and Shift-TAB to navigate to next and previous occurance. Editing within the selection will affect all instances. Press C-; again to exit iedit-mode.

(use-package iedit)

All the icons

If running this for the first time, ensure to run M-x all-the-icons-install-fonts to install the required fonts on the system.

(use-package all-the-icons
  :if (display-graphic-p))

Search tools

Isearch

Configure isearch to display a number for the current match and the total number of matches in the minibuffer.

(setq isearch-lazy-count t)

Ripgrep (rg)

Ripgrep is a line-oriented search tool that recursively searches the current directory for a regex pattern.

brew install ripgrep

Alternatives

Completions

Vertico provides a completion UI which is a bit nicer than what Emacs provides by default. It is used to navigate candidates and select one when opening files, running commands, or switching between projects.

(use-package vertico
  :config
  (vertico-mode))

Save minibuffer history.

(savehist-mode)

Orderless solves the problem when you can't remember if the command was named package-install or install-package and matches the segments you type regardless of the order.

(use-package orderless
  :custom
  (completion-styles '(orderless basic))
  (completion-category-overrides '((file (styles partial-completion)))))

Marginalia adds more information to completion candidates in the minibuffer.

(use-package marginalia
  :config
  (marginalia-mode))

Vertico-posframe shows selection candidates in a modal frame instead of using the minibuffer.

(use-package vertico-posframe
  :custom
  (vertico-posframe-mode 1))

Mode line

The mode line is the bottom part of the editor, where things like filename and cursor position is usually displayed. The mood-line is a nice clean configuration.

(use-package mood-line
  :custom
  (mood-line-show-encoding-information t)
  :config
  (mood-line-mode))

Utilities

Define a function to indent a region if selected, otherwise the whole buffer. Credits to Bozhidar Batsov.

(defun my-indent-buffer ()
  "Indent the currently visited buffer."
  (interactive)
  (indent-region (point-min) (point-max)))

(defun my-indent-region-or-buffer ()
  "Delete trailing whitespace and indent. Applies to a region if selected, otherwise the whole buffer."
  (interactive)
  (save-excursion
    (if (region-active-p)
        (progn
          (delete-trailing-whitespace (region-beginning) (region-end))
          (indent-region (region-beginning) (region-end))
          (message "Indented selected region."))
      (progn
        (delete-trailing-whitespace)
        (my-indent-buffer)
        (message "Indented buffer.")))))

(global-set-key (kbd "M-s-l") #'my-indent-region-or-buffer)

Buffers

(use-package ibuffer-project
  :bind (("C-x C-b" . ibuffer))
  :init
  (add-hook
   'ibuffer-hook
   (lambda ()
     (setq ibuffer-filter-groups (ibuffer-project-generate-filter-groups))
     (unless (eq ibuffer-sorting-mode 'project-file-relative)
       (ibuffer-do-sort-by-project-file-relative)))))

Project

A project in this context is a set of files that belong together. Usually, this means files that are in the same Git repository.

Operations you might want to do on a project includes:

  • jump to a file
  • search (and possibly replace) within files
  • run the code, or run tests

This config makes it easier to open Magit or Vterm in a project.

(with-eval-after-load 'project

  (defun project-vterm ()
    "Start Vterm in the current project's root directory.
If a buffer already exists for running Vterm in the project's root,
switch to it.  Otherwise, create a new Vterm buffer.
With \\[universal-argument] prefix arg, create a new Vterm buffer even
if one already exists."
    (interactive)
    (defvar vterm-buffer-name)
    (let* ((default-directory (project-root (project-current t)))
           (vterm-buffer-name (project-prefixed-buffer-name "vterm"))
           (vterm-buffer (get-buffer vterm-buffer-name)))
      (if (and vterm-buffer (not current-prefix-arg))
          (pop-to-buffer vterm-buffer (bound-and-true-p display-comint-buffer-action))
        (vterm))))

  (define-key project-prefix-map "m" #'magit-project-status)
  (define-key project-prefix-map "t" #'project-vterm)
  (add-to-list 'project-switch-commands '(magit-project-status "Magit") t)
  (add-to-list 'project-switch-commands '(project-vterm "Vterm") t)
  (add-to-list 'project-kill-buffer-conditions '(major-mode . vterm-mode)))

Alternatives

In the past, I have used the excellent Projectile library, but have realized that all features I was using exists in project.el, which is already built into Emacs.

Shell

Kill the buffer when the shell process exits.

(defun my-shell-mode-hook ()
  "Custom `shell-mode' behaviours."
  ;; Kill the buffer when the shell process exits.
  (let* ((proc (get-buffer-process (current-buffer)))
         (sentinel (process-sentinel proc)))
    (set-process-sentinel
     proc
     `(lambda (process signal)
        ;; Call the original process sentinel first.
        (funcall #',sentinel process signal)
        ;; Kill the buffer on an exit signal.
        (and (memq (process-status process) '(exit signal))
             (buffer-live-p (process-buffer process))
             (kill-buffer (process-buffer process)))))))

(add-hook 'shell-mode-hook 'my-shell-mode-hook)

Enable executing shell code block in org-mode.

(org-babel-do-load-languages 'org-babel-load-languages '((shell . t)))

Vterm

Vterm is a better terminal emulator than eshell or shell.

(use-package vterm
  :config
  (setq vterm-copy-exclude-prompt t)
  (setq vterm-max-scrollback 100000))

Git

Starting to get the hang of using Git through Magit. If you only copy one thing from this file, this is probably it.

(use-package magit
  :bind
  ("C-x g" . magit-status)
  ("C-c h" . magit-log-buffer-file)
  :custom
  (magit-diff-refine-hunk 'all)
  (magit-clone-default-directory "~/Projects/"))

Show todos in the magit status page.

(use-package magit-todos)

The package Forge enables Magit to work better with Github and Gitlab.

(use-package forge
  :after magit
  :custom
  (forge-topic-list-limit '(60 . -5)))

Show git status for each file.

(use-package git-gutter
  :diminish git-gutter-mode
  :init
  (global-git-gutter-mode t))

Org-mode

Make org-mode documents look a bit better:

  • Replace the ... that is shown by default when there is hidden content.
  • Use the org-bullets package to make headers look cleaner.

    (use-package org
      :ensure nil ;; I rely on the built in version of org-mode
      :hook
      (org-mode . auto-fill-mode)
      (org-mode . variable-pitch-mode)
      (org-mode . (lambda () (display-line-numbers-mode 0)))
      :bind (("C-c a" . org-agenda)
             ("C-c c" . org-capture))
      :custom
      (org-hide-emphasis-markers t)
      (org-pretty-entities t)
      (org-ellipsis "↴")
      (org-fontify-whole-block-delimiter-line nil)
      (org-fontify-quote-and-verse-blocks t)
      (org-blank-before-new-entry '((heading . always) (plain-list-item . nil)))
      (org-export-with-section-numbers nil)
      (org-export-with-toc nil)
      (org-use-sub-superscripts :{})
      (org-export-with-sub-superscripts nil))
    
    (use-package org-modern
      :custom
      (org-modern-star 'replace)
      :init (global-org-modern-mode))
    
    

Tasks (a.k.a. Todos)

Log the date and time when a task is completed.

(setq org-log-done t)

Define my set of todo-states, and replace the keywords with symbols.

(setq org-todo-keywords
      '((sequence "TODO(t)" "DOING(i)" "WAITING(w)" "|" "DONE(d)" "CANCELED(c)")))

Display a small arrow next to the currently clocked in task. (Code by romto)

(defun org-clock-flag-active-task ()
  "Identify the clocked-in task with a fringe arrow on its header
line."
  (save-excursion
    (org-clock-goto)
    (make-variable-buffer-local 'overlay-arrow-position)
    (setq overlay-arrow-position (make-marker))
    (set-marker overlay-arrow-position (point))))

(defun org-clock-unflag-active-task ()
  "Remove the fringe arrow from the most recent clocked task."
  (with-current-buffer
      (marker-buffer (car org-clock-history))
    (setq overlay-arrow-position nil)))

(add-hook 'org-clock-in-hook 'org-clock-flag-active-task)
(add-hook 'org-clock-out-hook 'org-clock-unflag-active-task)

Capture

(setq org-default-notes-file "~/Documents/org-roam/Inbox.org")
(setq org-capture-templates
      '(("i" "Inbox" entry (file "" "~/Documents/org-roam/Inbox.org")
         "* TODO %?\n  %u\n  %a")
        ("t" "Task (scheduled)" entry (file "~/Documents/org-roam/Inbox.org")
         "* TODO %?\nSCHEDULED: %^{Scheduled}t\n  %u\n  %a")
        ("c" "Chore" entry (file "~/Documents/org-roam/Chores.org")
         "* TODO %?\n  %u\n  %a")))

Agenda

Populate the agenda using all org-roam files tagged with project. Code stolen from David Wilsons Build your Org agenda from Org Roam notes.

(with-eval-after-load 'org-roam
  (defun my/org-roam-filter-by-tag (tag-name)
    (lambda (node)
      (member tag-name (org-roam-node-tags node))))

  (defun my/org-roam-list-notes-by-tag (tag-name)
    (mapcar #'org-roam-node-file
            (seq-filter
             (my/org-roam-filter-by-tag tag-name)
             (org-roam-node-list))))

  (defun my/org-roam-refresh-agenda-list ()
    (interactive)
    (setq org-agenda-files (my/org-roam-list-notes-by-tag "project")))

  ;; Build the agenda list the first time for the session
  (my/org-roam-refresh-agenda-list))

When using agenda to view todo items, I want to hide items which are scheduled or have a deadline, since they will show up on the calendar. I also want to set the deadline warning to 4 days (the default is 14).

(setopt org-agenda-todo-ignore-scheduled 'all)
(setopt org-deadline-warning-days 4)
(setopt org-agenda-window-setup 'current-window)

Refile

(setq org-refile-targets
      '(("~/Documents/org/archived.org" :maxlevel . 1)
        ("~/Documents/org/projects.org" :maxlevel . 1)))

(advice-add 'org-refile :after 'org-save-all-org-buffers)

Paste images from copy buffer into org documents

For this to work on MacOS you first need to install the pngpaste utility.

brew install pngpaste

Then configure org-download to put pasted images in a directory named images in the same directory as the org file.

(use-package org-download
  :after org
  :defer nil
  :custom
  (org-download-method 'directory)
  (org-download-image-dir "images")
  (org-download-heading-lvl nil)
  (org-download-timestamp "%Y%m%d-%H%M%S_")
  (org-image-actual-width 300)
  (org-download-screenshot-method "/opt/homebrew/bin/pngpaste %s")
  :bind
  ("C-s-p" . org-download-screenshot)
  :config
  (require 'org-download))

Link to Magit buffers (like git commits) in org documents

The author of Magit also made the package orgit that provides a convenient way to create links to Magit buffers.

(use-package orgit)

Journal

I have found that org-journal works well for daily journaling.

(use-package org-journal
  :config
  (setq org-journal-date-format "%Y-%m-%d, %a"))

Org-roam

Org-roam is a knowledge management system built on top of org-mode. See explanation for the config in System Crafters videos.

(use-package org-roam
  :init
  (make-directory "~/Documents/org-roam/daily" t)
  :custom
  (org-roam-directory "~/Documents/org-roam")
  (org-roam-completion-everywhere t)
  :bind (("C-c n l" . org-roam-buffer-toggle)
         ("C-c n f" . org-roam-node-find)
         ("C-c n i" . org-roam-node-insert)
         :map org-mode-map
         ("C-M-i" . completion-at-point)
         :map org-roam-dailies-map
         ("Y" . org-roam-dailies-capture-yesterday)
         ("T" . org-roam-dailies-capture-tomorrow))
  :bind-keymap
  ("C-c n d" . org-roam-dailies-map)
  :config
  (require 'org-roam-dailies)
  (org-roam-db-autosync-enable))

Snippets

YASnippet is a template system for Emacs. It allows you to type an abbreviation and automatically expand it into function templates.

(use-package yasnippet
  :config
  (yas-global-mode 1))

Install the official collection of snippets for yasnippet.

(use-package yasnippet-snippets
  :after yasnippet)

Languages

Corfu provides in-buffer completion.

(use-package corfu
  :custom
  (corfu-auto t)
  (corfu-auto-delay 2.0)
  (corfu-popupinfo-delay '(2.0 0.4))
  :init
  (global-corfu-mode)
  (corfu-popupinfo-mode))

Flycheck provides on-the-fly syntax checking.

Most of my emacs lisp are Emacs configuration snippets, and not complete elisp packages. By disabling emacs-lisp-checkdoc, Flycheck will not complain about missing comments. Running M-x checkdoc will still perform that check, and also assist with creating documentation that is up to standards.

(use-package flycheck
  :diminish flycheck-mode
  :config
  (setq-default flycheck-disabled-checkers '(emacs-lisp-checkdoc))
  (add-hook 'after-init-hook #'global-flycheck-mode))

HTML

Emmet-mode is useful whenever you quickly need to type some HTML. Autostart it on any markup modes

(use-package emmet-mode
  :config
  (add-hook 'sgml-mode-hook 'emmet-mode))

Java

Make sure to install a Java Language Server.

brew install jdtls

Javascript

(setq js-indent-level 2)

(use-package js2-mode
  :mode "\\.[c|m]js[x]?\\'")

Typescript

(use-package typescript-mode
  :mode "\\.\\(ts\\|tsx\\)\\'"
  :config
  (setq typescript-indent-level 2))

Vue.js

New config:

(require 'eglot)
(define-derived-mode pbgc-vue-mode web-mode "pbVue"
  "A major mode derived from web-mode, for editing .vue files with LSP support.")


(add-to-list 'auto-mode-alist '("\\.vue\\'" . pbgc-vue-mode))

(defun pbgc-vue-custom ()
  (flycheck-mode t)
  (eglot-ensure))
(add-hook 'pbgc-vue-mode-hook 'pbgc-vue-custom)

(defun vue-eglot-init-options ()
  (let ((tsdk-path (expand-file-name
                    "lib"
                    (string-trim-right (shell-command-to-string "npm list --global --parseable typescript | head -n1"))
                    )))
    `(:typescript (:tsdk ,tsdk-path
                         :languageFeatures (:completion
                                            (:defaultTagNameCase "both"
                                                                 :defaultAttrNameCase "kebabCase"
                                                                 :getDocumentNameCasesRequest nil
                                                                 :getDocumentSelectionRequest nil)
                                            :diagnostics
                                            (:getDocumentVersionRequest nil))
                         :documentFeatures (:documentFormatting
                                            (:defaultPrintWidth 100
                                                                :getDocumentPrintWidthRequest nil)
                                            :documentSymbol t
                                            :documentColor t)))))

;; Volar
(add-to-list 'eglot-server-programs
             `(pbgc-vue-mode . ("vls" "--stdio" :initializationOptions ,(vue-eglot-init-options))))

Install prettier. It can be activated to run on save everywhere by running M-x global-prettier-mode.

(use-package prettier)

Flycheck

Ensure that flycheck reports on eslint warnings, using config borrowed from http://codewinds.com/blog/2015-04-02-emacs-flycheck-eslint-jsx.html

(flycheck-add-mode 'javascript-eslint 'pbgc-vue-mode)

(defun my/use-eslint-from-node-modules ()
  (let* ((root (locate-dominating-file
                (or (buffer-file-name) default-directory)
                "node_modules"))
         (eslint (and root
                      (expand-file-name "node_modules/eslint/bin/eslint.js"
                                        root))))
    (when (and eslint (file-executable-p eslint))
      (setq-local flycheck-javascript-eslint-executable eslint))))

(add-hook 'flycheck-mode-hook #'my/use-eslint-from-node-modules)

Restclient

Mode to use Emacs as a REST client.

(use-package restclient
  :mode ("\\.rest\\'" . restclient-mode))

Use restclient in org-mode source blocks.

(use-package ob-restclient
  :config
  (org-babel-do-load-languages
   'org-babel-load-languages
   '((restclient . t))))

SQL

Use SQL in org-mode source blocks.

(org-babel-do-load-languages
 'org-babel-load-languages
 '((sql . t)))

PlantUML

(use-package plantuml-mode
  :config
  (setq org-plantuml-jar-path "/usr/local/opt/plantuml/libexec/plantuml.jar")

  (org-babel-do-load-languages
   'org-babel-load-languages
   '((plantuml . t))))

Mermaid

https://emacstil.com/til/2021/09/19/org-mermaid/

(use-package ob-mermaid
  :config
  (setq ob-mermaid-cli-path "/usr/local/bin/mmdc"))

Redisplay inline images after generation

(add-hook 'org-babel-after-execute-hook 'org-redisplay-inline-images)

CSV

This helps when working with files containing comma-separated values.

(use-package csv-mode)

Yaml

(use-package yaml-mode
  :mode "\\.yml\\'")

Simple HTTP server

This package provides a simple way of serving up files from a local directory, by invoking `M-x httpd-serve-directory`.

(use-package simple-httpd)

Klondike

A relaxing solitary card game.

(use-package klondike
  :hook ((klondike-mode . turn-off-evil-mode)
         (klondike-picker-mode . turn-off-evil-mode)
         (klondike-select-mode . turn-off-evil-mode)))

Utility functions

(defun my/func-region (start end func)
  "run a function over the region between START and END in current buffer."
  (save-excursion
    (let ((text (delete-and-extract-region start end)))
      (insert (funcall func text)))))

Interactive functions to encode and decode url strings.

(defun url-encode-region (start end)
  "urlencode the region between START and END in current buffer."
  (my/func-region start end #'url-hexify-string))

(defun url-decode-region (start end)
  "de-urlencode the region between START and END in current buffer."
  (my/func-region start end #'url-unhex-string))

Created: 2025-10-10 Fri 21:39