miklos1 / dotemacs

My GNU Emacs configuration

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Preamble

;; config.el -*- lexical-binding: t; -*-

;; In interactive sessions, save a little IO time by skipping the
;; mtime checks on every *.elc file.
(setq load-prefer-newer noninteractive)

Package management

Bootstrap straight.el

Configuration

;; Optimise start-up time
(setq straight-check-for-modifications '(find-when-checking)
      straight-enable-package-integration nil)

Bootstrap code snippet

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

Set up use-package

;; Get use-package and integrate it with straight.el
(straight-use-package 'use-package)
(setq straight-use-package-by-default t)

;; Enable the :chords keyword in use-package
(use-package use-package-chords
  :config (key-chord-mode t))

;; Get delight for the :delight keyword in use-package
(use-package delight)

Configure for host operating system

Trash

(unless (executable-find "trash")
  (warn "Command-line utility `trash' not found in PATH!"))

(defvar macos-trash-use-finder t
  "Whether `trash' should use Finder to move files to Trash.")

(defun system-move-file-to-trash (filename)
  "Move the file (or directory) named FILENAME to Trash."
  (if macos-trash-use-finder
      (call-process "trash" nil nil nil "-F" "--" filename)
    (call-process "trash" nil nil nil "--" filename)))

GNU ls

(defconst have-gnu-ls
  (let ((gls-path (executable-find "gls")))
    (cond (gls-path
           (setq insert-directory-program gls-path)
           t)
          (t
           (warn "Failure to find GNU `ls' on the host system.")
           nil)))
  "Whether GNU `ls' is available on the system.")

Load personal settings

(let ((personal-file (concat user-emacs-directory "personal.el")))
  (when (file-exists-p personal-file)
    (load personal-file nil nil 'nosuffix)))

Settings not specific to particular buffers

Miscellaneous general settings

(setq inhibit-startup-screen t
      confirm-nonexistent-file-or-buffer t
      ediff-window-setup-function 'ediff-setup-windows-plain)

(global-set-key (kbd "s-b") #'bury-buffer)

;; Disable transient history
(set 'transient-save-history nil)

Layout and movement settings

(unless (eq window-system 'ns)
  (menu-bar-mode -1))
(when (fboundp 'scroll-bar-mode)
  (scroll-bar-mode -1))
(when (fboundp 'tool-bar-mode)
  (tool-bar-mode -1))

(windmove-default-keybindings 'super)
(windmove-swap-states-default-keybindings)

macOS settings

Apple keyboard

(when (boundp 'ns-right-alternate-modifier)
  (setq ns-right-alternate-modifier 'none))

Automatically match Aqua theme

(when (boundp 'ns-system-appearance-change-functions)
  (add-hook
   'ns-system-appearance-change-functions
   (lambda (appearance)
     (mapc #'disable-theme custom-enabled-themes)
     (pcase appearance
       ('light
        (load-theme 'tango t)
        (load-theme 'tango-patch t))
       ('dark
        (load-theme 'tango-dark t))))))

macOS current location

(use-package osx-location
  :commands osx-location-watch
  :config
  (add-hook
   'osx-location-changed-hook
   (lambda ()
     (setq calendar-latitude osx-location-latitude
           calendar-longitude osx-location-longitude
           calendar-location-name "current location"))))

Auto-save and interlocking

Setting auto-save-default and create-lockfiles to nil disables them.

Backup settings

(setq backup-by-copying-when-linked t
      backup-by-copying-when-mismatch t
      backup-directory-alist `(("." . ,(concat user-emacs-directory "backups")))
      delete-old-versions t
      kept-new-versions 8
      kept-old-versions 1
      version-control t)

Revert individual buffers when files on disk change

(setq auto-revert-use-notify t)
(global-auto-revert-mode t)

Automatically save place in files

(setq save-place-version-control 'never)
(save-place-mode t)

Maintain a list of recent files

(use-package recentf
  :straight nil
  :config
  (dolist (path `(,(expand-file-name "../lisp" data-directory)
                  ,(expand-file-name "straight" straight-base-dir)))
    (add-to-list 'recentf-exclude (concat "^" (regexp-quote path))))
  (recentf-mode t))

Consider configuring recentf-auto-cleanup

Convenience packages

Minibuffer completion

(setq completion-styles '(basic partial-completion)
      completion-category-overrides '((file (styles basic substring))))
(setq read-buffer-completion-ignore-case
      read-file-name-completion-ignore-case)

Minibuffer selection

Vertico

(use-package vertico
  :config
  (define-key vertico-map (kbd "C-M-i") #'vertico-insert)
  (define-key vertico-map (kbd "TAB") #'minibuffer-complete)

  (vertico-mode t))

Fancy minibuffer and more features

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

(use-package embark
  :bind (("C-." . embark-act)))

Which-key

(use-package which-key
  :delight which-key-mode
  :config (which-key-mode t))

Ibuffer

(global-set-key [remap list-buffers] #'ibuffer)

Finding files by name and by content

Consult

(use-package consult
  :bind (("C-c f r" . consult-recent-file)
         ("C-c f d" . consult-find)
         ("C-c s g" . consult-git-grep)
         ("C-c s d" . consult-ripgrep)))

(use-package embark-consult
  :after consult)

Generic buffer editing, editing visuals, and buffer navigation

Miscellaneous settings

;; More often than not, files should end with a final newline
(setq require-final-newline t)

;; Maximum decoration unless otherwise specified later
(setq font-lock-maximum-decoration '((t . t)))

;; Highlight matching parenthesis
(show-paren-mode t)

;; Highlight some keywords
(use-package hl-todo
  :config
  (dolist (assoc hl-todo-keyword-faces)
    (rplacd assoc (list :inherit 'hl-todo)))
  (global-hl-todo-mode t)
  :custom-face
  (hl-todo ((t (:weight bold)))))

;; Prettify page break characters in Help buffers
(use-package page-break-lines
  :hook (help-mode . page-break-lines-mode))

Cut and paste and mouse use

(setq kill-do-not-save-duplicates t
      mouse-drag-and-drop-region 'meta
      mouse-yank-at-point t
      save-interprogram-paste-before-kill t)

No insidious hiding of tabs by default

;; Do not insert tabs by default
(setq-default indent-tabs-mode nil)

;; Do not hide tabs when deleting
(setq backward-delete-char-untabify-method 'hungry)

Whitespace highlighting

(use-package whitespace
  :straight nil
  :delight global-whitespace-mode
  :config
  (setq whitespace-global-modes '())
  (global-whitespace-mode t))

(defconst highlight-tabs-style '(face tabs tab-mark)
  "Whitespace highlighting when indenting with spaces.")

(defconst highlight-spaces-style
  '(face indentation space-before-tab space-after-tab)
  "Whitespace highlighting when indenting with tabs.")

(defun set-whitespace-style (style)
  "Configure whitespace highlighting with STYLE."
  (setq-local whitespace-style style)
  (whitespace-turn-off)
  (whitespace-turn-on-if-enabled))

(defun reset-whitespace-style ()
  "Reset whitespace highlighting to highlight everything."
  (interactive)
  (set-whitespace-style (default-value 'whitespace-style)))

(defun indent-tabs-mode (&optional arg)
  "Toggle or set `indent-tabs-mode'."
  (interactive (list (or current-prefix-arg 'toggle)))
  (let ((value
         (if (eq arg 'toggle)
             (not indent-tabs-mode)
           (> (prefix-numeric-value arg) 0))))
    (setq-local indent-tabs-mode value)
    (set-whitespace-style
     (if value highlight-spaces-style highlight-tabs-style))))

(defun indent-with-tabs ()
  "Configure indentation with tabs."
  (interactive)
  (indent-tabs-mode 1))

(defun indent-with-spaces ()
  "Configure indentation with spaces."
  (interactive)
  (indent-tabs-mode -1))

Unobtrusively remove trailing whitespace

(use-package ws-butler
  :delight ws-butler-mode
  :config (ws-butler-global-mode t))

Auto-completion

(use-package hippie-exp
  :straight nil
  :bind ("M-/" . hippie-expand)
  :config
  (setq hippie-expand-try-functions-list
        (cl-set-difference hippie-expand-try-functions-list
                           '(try-expand-line try-expand-list))))

(use-package company
  :defer 1
  :delight company-mode
  :config (global-company-mode))

Avy

(use-package avy
  :chords (",." . avy-goto-symbol-1)
  :bind (:map isearch-mode-map
         ("<C-return>" . avy-isearch)))

Specific minor modes

Outline mode

(use-package outline
  :straight nil
  :custom
  (outline-minor-mode-cycle t))

Lisp editing

(use-package paredit
  :straight (paredit :depth full)
  :demand t
  :hook ((emacs-lisp-mode
          eval-expression-minibuffer-setup
          ielm-mode
          lisp-mode
          sly-mrepl-mode) . enable-paredit-mode))

(defun avy-kill-sexp (char &optional arg)
  (interactive (list (read-char "char: " t)
                     current-prefix-arg))
  (save-excursion
    (avy-goto-char-2 ?\( char arg)
    (kill-sexp)
    (let ((col (current-column))
          end)
      (if (and (looking-at "[ \t]*$")
               (<= col
                   (save-excursion
                     ;; Move
                     (forward-line)
                     (back-to-indentation)
                     ;; Save point
                     (setq end (point))
                     ;; Return column
                     (current-column))))
          (delete-region (point) end)
        (just-one-space)))
    (paredit-close-round)))

(defun avy-yank-sexp (char &optional arg)
  (interactive (list (read-char "char: " t)
                     current-prefix-arg))
  (save-excursion
    (avy-goto-char-2 ?\( char arg)
    (kill-ring-save (point)
                    (save-excursion
                      (forward-sexp)
                      (point)))))

Flycheck

;; Flycheck
(use-package flycheck
  :hook (emacs-startup . global-flycheck-mode)
  :config
  (setq flycheck-global-modes '()
        flycheck-check-syntax-automatically '(mode-enabled save)))

Spelling and grammar

(use-package guess-language
  :defer t
  :commands guess-language
  :hook (flyspell-mode . (lambda ()
                           (guess-language-mode (if flyspell-mode 1 -1))))
  :config
  (setq guess-language-languages '(en de hu la)
        guess-language-langcodes
        '((en "en_GB" "English")
          (de "de_DE" "German")
          (hu "hu_HU" nil)
          (la "la" nil))))

;; Turn on Flyspell mode automatically in text modes when manually
;; asking to spell check a word.  In addition, guess language before
;; checking the word.
(define-advice ispell-word (:before (&rest ignored) "flyspell-mode")
  ignored
  (when (and (not flyspell-mode) (derived-mode-p 'text-mode))
    (flyspell-mode)
    (guess-language)))

(defun set-ispell-dictionary ()
  (interactive)
  ;; Disable guess language mode
  (guess-language-mode -1)
  ;; Change dictionary interactively
  (call-interactively #'ispell-change-dictionary))

(use-package langtool
  :defer t
  :bind (("s-;" . langtool-check)
         ("s-:" . langtool-check-done))
  :config
  (setq langtool-language-tool-server-jar
        "/opt/homebrew/opt/languagetool/libexec/languagetool-server.jar"
        langtool-server-user-arguments
        `("--port" "8082" "--config"
          ,(expand-file-name
            (concat user-emacs-directory "languagetool.properties")))))

Special major modes

Dired

(use-package dired
  :straight nil
  :init
  (setq delete-by-moving-to-trash t)
  :config
  (setq dired-dwim-target t)
  (when have-gnu-ls
    (setq dired-listing-switches
          (string-join '("-ahl" "-v" "--group-directories-first") " "))))

Emacs shell

(use-package eshell
  :straight nil
  :bind ("s-t" . eshell)
  :custom
  (eshell-last-dir-ring-file-name nil))

Magit

(use-package magit
  :commands magit-after-save-refresh-status
  :bind
  ("C-x g" . magit-status)
  :custom
  (magit-delete-by-moving-to-trash nil)
  (magit-diff-refine-hunk t)
  :config
  (add-hook 'after-save-hook #'magit-after-save-refresh-status))

(use-package forge
  :after magit)

Mail

(setq mail-envelope-from 'header
      mail-specify-envelope-from t
      message-auto-save-directory nil
      message-kill-buffer-on-exit t
      send-mail-function 'sendmail-send-it
      sendmail-program (executable-find "msmtp"))

Notmuch

(use-package notmuch
  :bind ("C-c m" . notmuch)
  :custom
  (mail-user-agent 'notmuch-user-agent)
  (read-mail-command 'notmuch)
  (notmuch-draft-folder (format "%s/Drafts" user-mail-address))
  (notmuch-fcc-dirs `((".*" . ,(format "%s/Sent" user-mail-address))))
  (notmuch-search-oldest-first nil)
  (notmuch-show-logo nil)
  :init
  (define-advice compose-mail (:before (&rest ignored) "notmuch")
    ignored
    (require 'notmuch)))

Live search

(use-package consult-notmuch
  :after notmuch
  :bind (:map notmuch-hello-mode-map
         ("s" . consult-notmuch)))

PDF tools

(use-package pdf-tools
  :mode ("\\.[pP][dD][fF]\\'" . pdf-view-mode)
  :magic ("%PDF" . pdf-view-mode)
  :config
  ;; Enable hiDPI support, but at the cost of memory!
  ;; See politza/pdf-tools#51
  (setq pdf-view-use-scaling t
        pdf-view-use-imagemagick nil)

  ;; Add retina support for MacOS users
  (when (eq system-type 'darwin)
    (advice-add #'pdf-util-frame-scale-factor :around
                #'+pdf--util-frame-scale-factor-a)
    (advice-add #'pdf-view-use-scaling-p :before-until
                #'+pdf--view-use-scaling-p-a)
    (dolist (fn '(pdf-annot-show-annotation
                  pdf-isearch-hl-matches
                  pdf-view-display-region))
      (advice-add fn :around #'+pdf--supply-width-to-create-image-calls-a)))

  ;; Install epdfinfo binary if needed
  (unless (file-executable-p pdf-info-epdfinfo-program)
    (pdf-tools-install))
  (pdf-tools-install-noverify))

(eval-when-compile
  (require 'pdf-tools))

(defun +pdf--util-frame-scale-factor-a (orig-fn)
  (if (and pdf-view-use-scaling
           (memq (pdf-view-image-type) '(imagemagick image-io))
           (fboundp 'frame-monitor-attributes))
      (funcall orig-fn)
    ;; Add special support for retina displays on MacOS
    (if (and (eq (framep-on-display) 'ns)
             (> emacs-major-version 26))
        2
      1)))

(defun +pdf--view-use-scaling-p-a ()
  "Returns t if on ns window-system on Emacs 27+."
  (and (eq (framep-on-display) 'ns)
       (> emacs-major-version 26)
       pdf-view-use-scaling))

(defun +pdf--supply-width-to-create-image-calls-a (orig-fn &rest args)
  (let ((create-image (symbol-function #'create-image)))
    (cl-letf
        (((symbol-function #'create-image)
          (lambda
            (file-or-data &optional type data-p &rest props)
            (apply create-image file-or-data type data-p
                   :width (car (pdf-view-image-size))
                   props))))
      (ignore create-image)
      (apply orig-fn args))))

Major editing modes

Emacs Lisp

;; Indent Emacs Lisp with spaces
(add-to-list 'whitespace-global-modes 'emacs-lisp-mode)
(add-hook 'emacs-lisp-mode-hook #'indent-with-spaces)

;; Limited decoration for Emacs Lisp code
(add-to-list 'font-lock-maximum-decoration '(emacs-lisp-mode . 1))

Common Lisp

;; Common Lisp
(use-package sly
  :defer t
  :init
  ;; Use SBCL
  (setq inferior-lisp-program "sbcl")
  ;; Hyperspec location
  (setq common-lisp-hyperspec-root
        "/usr/local/share/doc/hyperspec/HyperSpec/"
        common-lisp-hyperspec-symbol-table
        (concat common-lisp-hyperspec-root "Data/Map_Sym.txt")
        common-lisp-hyperspec-issuex-table
        (concat common-lisp-hyperspec-root "Data/Map_IssX.txt"))
  :config
  ;; Sly completions
  (setq sly-complete-symbol-function 'sly-simple-completions)
  :custom
  (org-babel-lisp-eval-fn 'sly-eval))

;; Indent Common Lisp with spaces
(add-to-list 'whitespace-global-modes 'lisp-mode)
(add-hook 'lisp-mode-hook #'indent-with-spaces)

;; Limited decoration for Common Lisp code
(add-to-list 'font-lock-maximum-decoration '(lisp-mode . 1))

;; Enable Outline minor mode
(add-hook 'lisp-mode-hook #'outline-minor-mode)

Org mode

use-package and customize modules

We need ol-notmuch to be able to link e-mails, which is part of org-contrib, i.e., part of the official org-mode repository, but not part of Emacs. So we install org-contrib which also pulls in the core org package, and enable the ol-notmuch module.

(use-package org
  :straight org-contrib
  :bind (("C-c a" . org-agenda)
         ("C-c c" . org-capture)
         ("C-c l" . org-store-link))
  :custom
  (org-modules '(ol-docview ol-info ol-notmuch))
  (org-image-actual-width nil)
  :config
  (add-hook 'org-mode-hook #'turn-on-auto-fill))

We use org-mime to compose HTML e-mails.

(use-package org-mime
  :bind (:map message-mode-hook
         ("C-c M-o" . org-mime-htmlize))
  :config
  (setq org-mime-export-options '(:with-latex dvipng
                                  :section-numbers nil
                                  :with-author nil
                                  :with-toc nil)))

Set TODO keywords and dependencies

By default, org-mode recognises the TODO and DONE keywords only. We add another two types of actionable items:

  • NEXT marks next actions according to GTD terminology. They denote actions where it is immediately obvious what to do and how; there is no need to further break them down.
  • WAIT marks actions to be done by someone else, that we are merely waiting for.

Thus TODO remains for what GTD calls projects: tasks to be accomplished that usually need a closer analysis.

(set 'org-todo-keywords nil)
(add-to-list 'org-todo-keywords
             '(type "TODO" "NEXT" "WAIT" "|" "DONE")
             'append)

For activities that happen or must be done on a specific day or at specific times, such as meetings and appointments, we define TIME and PAST, so that they can be used as dependencies to broader tasks.

(add-to-list 'org-todo-keywords '(type "TIME" "|" "PAST") 'append)

To be able to dim or hide blocked actions, we must enforce TODO dependencies:

(setq org-enforce-todo-dependencies t)

We are going to use scheduling to stop thinking about certain tasks until a more opportune time.

(setq org-agenda-todo-ignore-scheduled 'future
      org-agenda-todo-ignore-time-comparison-use-seconds t)
(setq org-log-done 'time)

For the incubator and the tickler, we add some more keywords:

  • REMIND marks headings that come up at the daily review. It is meant to be scheduled, so the reminder comes up first on the specified day.
  • WEEKLY marks headings that come up at the weekly review. It may also be scheduled for further delay.
  • CANCELLED marks project that we no longer wish to pursue.
(add-to-list 'org-todo-keywords
             '(sequence "REMIND" "WEEKLY" "|" "CANCELLED")
             'append)

Agenda settings

(setq org-agenda-start-on-weekday 0
      org-agenda-window-setup 'other-window)

;; Disable buggy selection and revert mere completion when setting
;; org-agenda filter
(defun disable-selection (orig &optional arg)
  (let ((vertico-map minibuffer-local-completion-map)
        (completion-cycle-threshold nil)
        (completion-styles '(basic)))
    (funcall orig arg)))

(advice-add #'org-agenda-filter :around #'disable-selection)

Org files

(defun org-file (filename)
  "Interpret relative file names relative to `org-directory'."
  (if (file-name-absolute-p filename)
      filename
    (progn
      ;; Make sure that `org-directory' is defined
      (unless (boundp 'org-directory)
        (require 'org))
      (concat (file-name-as-directory org-directory) filename))))
(setq org-default-notes-file
      (org-file "notes.org")
      org-agenda-files
      (mapcar #'org-file '("calendar/"
                           "incubator.org"
                           "ongoing.org"
                           "recurring.org"
                           "upcoming.org"))
      org-archive-location
      (org-file "archive/default.org::* From %s")
      org-attach-id-dir
      (org-file "data/"))

Note: setting the default notes file is actually superfluous if org-capture-templates is properly configured.

Capture templates

Not being familiar with Emacs bookmarks, we disable org-capture from messing with them.

(setq org-capture-bookmark nil)
(setq org-capture-templates
      '(("t" "Task" entry (file "ongoing.org")
         "* TODO %?")
        ("r" "Reminder" entry (file "incubator.org")
         "* REMIND %?")
        ("n" "Note" item (file+olp "notes.org" "Inbox")
         "- %i%? (%a)")
        ("s" "Scripture reading" item (file+olp "notes.org" "Scripture reading record")
         "- %? %u")
        ("c" "Calendar entry" entry (file+olp "upcoming.org" "Miscellaneous")
         "* %?\n%^{Calendar capture.}t")))

org-capture saves the target file after committing the captured item (unless the template is set up never to save the file). If there are already unsaved changes in the target buffer, they would be saved silently. To offer the user a choice, we advise org-capture-target-buffer:

(define-advice org-capture-target-buffer (:filter-return (target-buffer))
  ;; Offer to save buffer if there are unsaved changes
  (save-some-buffers nil #'(lambda () (eq target-buffer (current-buffer))))
  ;; Just return target buffer!
  target-buffer)

Custom agenda commands

We define the following agenda views:

Immediate view
Reminders from the “tickler”, 3-day overview, next actions list, current “waiting for” entries, and any stuck projects.
Daily view
Detailed calendar of the day.
Weekly review
Fortnight overview, list of active projects, weekly reminders.
(defun default-org-agenda-skipper ()
  "Skip incubator entries and scheduled entries."
  (or (org-agenda-skip-entry-if 'todo '("REMIND" "WEEKLY"))
      (org-agenda-skip-entry-if 'scheduled)))

(setq org-agenda-custom-commands
      '(("i" "Immediate view"
         ((agenda "" ((org-agenda-span 'day)
                      (org-agenda-skip-function #'default-org-agenda-skipper)))
          (tags-todo "today" ((org-agenda-overriding-header "For today:")))))
        ("d" "Daily review"
         ((todo "REMIND" ((org-agenda-overriding-header "Reminders:")
                          (org-agenda-dim-blocked-tasks 'invisible)))
          (agenda "" ((org-agenda-span 3)
                      (org-agenda-skip-function #'default-org-agenda-skipper)
                      (org-agenda-use-time-grid nil)))
          (todo "NEXT" ((org-agenda-overriding-header "Outstanding actions:")))
          (todo "TIME|WAIT" ((org-agenda-overriding-header "Waiting for:")
                             (org-agenda-todo-ignore-timestamp 'future)))
          (todo "TODO" ((org-agenda-overriding-header "Stuck projects:")
                        (org-agenda-dim-blocked-tasks 'invisible))))
         ((org-agenda-tag-filter-preset '("-everyday"))))
        ("w" "Weekly review"
         ((agenda "" ((org-agenda-span 'fortnight)
                      (org-agenda-skip-function #'default-org-agenda-skipper)
                      (org-agenda-use-time-grid nil)))
          (todo "TODO" ((org-agenda-overriding-header "Active projects:")))
          (todo "WEEKLY" ((org-agenda-overriding-header "Weekly reminders:"))))
         ((org-agenda-tag-filter-preset '("-everyday"))))))

Remainder

(add-hook
 'org-mode-hook
 (lambda ()
   (setq-local langtool-disabled-rules
               '("WHITESPACE_RULE"
                 "DE_CASE"
                 "DE_DU_UPPER_LOWER"
                 "FALSCHE_VERWENDUNG_DES_BINDESTRICHS"
                 "GERMAN_SPELLER_RULE"
                 "LEERZEICHEN_HINTER_DOPPELPUNKT"))))

Flashcards: org-fc

(use-package hydra
  :defer t)

(use-package org-fc
  :straight
  (org-fc
   :type git :repo "https://git.sr.ht/~l3kn/org-fc"
   :files (:defaults "awk" "demo.org"))
  :config
  (require 'org-fc-hydra))

Org-roam

(use-package 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
  ;; If you're using a vertical completion framework, you might want a
  ;; more informative completion interface
  (setq org-roam-node-display-template
        (concat "${title:*} " (propertize "${tags:10}" 'face 'org-tag)))
  (org-roam-db-autosync-mode))

Language Server Protocol

;; Language server protocol settings
(use-package lsp-mode
  :commands lsp
  :init
  (setq read-process-output-max (* 1024 1024))
  (setq lsp-diagnostics-provider :none
        lsp-modeline-diagnostics-enable nil))

(use-package lsp-ui
  :after lsp-mode
  :init (setq lsp-ui-doc-enable nil))

C

(eval-when-compile
  (require 'cc-vars))

(eval-after-load 'cc-vars
  (lambda ()
    (setcdr (assoc 'other c-default-style) "linux")
    (add-to-list 'whitespace-global-modes 'c-mode)
    (add-hook 'c-mode-common-hook #'indent-with-tabs)
    (add-hook 'c-mode-common-hook #'electric-pair-local-mode)))

Python

;; Use lsp-mode
(add-hook 'python-mode-hook #'lsp)

;; Use Flycheck, but not with pylint
(eval-after-load 'flycheck
  (lambda ()
    (add-to-list 'flycheck-global-modes 'python-mode)
    (push 'python-pylint (default-value 'flycheck-disabled-checkers))))

;; Python environment
(use-package pyvenv
  :defer t
  :init (setenv "WORKON_HOME" "~/.pyenv/versions")
  :config (pyvenv-mode t))

;; Emacs IPython Notebook
(use-package ein
  :defer t)

JavaScript

(use-package js
  :straight nil
  :defer t
  :custom
  (js-indent-level 2))

(use-package js2-mode
  :mode ("\\.js\\'" . js2-mode)
  :init
  (add-hook 'js2-mode-hook #'lsp))

LaTeX

(use-package auctex
  :defer t
  :init
  ;; Ask AUCTeX to use PDFLaTeX
  (setq TeX-PDF-mode t))

Zig

(use-package zig-mode
  :defer t
  :init
  (add-hook 'zig-mode-hook #'lsp)
  (add-hook 'zig-mode-hook #'electric-pair-local-mode))

;; Zig language server
(eval-after-load 'lsp-mode
  (lambda ()
    (setq lsp-zig-zls-executable
          (expand-file-name "zls/zls" lsp-server-install-dir))))

OCaml

(let ((opam-share
       (eval-when-compile
         (ignore-errors
           (car (process-lines "opam" "var" "share"))))))
  (when (and opam-share (file-directory-p opam-share))
    ;; Add opam load path
    (add-to-list 'load-path (expand-file-name "emacs/site-lisp" opam-share))))
(use-package tuareg
  :defer t)

(use-package merlin
  :straight nil
  :hook ((tuareg-mode . merlin-mode))
  :custom
  ;; Use opam switch to lookup ocamlmerlin binary
  (merlin-command 'opam))

Miscellaneous major modes

;; Writable grep buffers
(use-package wgrep
  :defer t)

;; Yet Another Markup Language
(use-package yaml-mode
  :defer t)

;; Gregorio GABC files
(use-package gregorio-mode
  :defer t)

Final personal settings and customisation

Post-init personal settings

(when (fboundp 'post-init-personal-settings)
  (post-init-personal-settings))

Customize

(setq custom-file (concat user-emacs-directory "custom.el"))
(load custom-file)

Start server

(use-package server
  :config
  (unless (server-running-p)
    (server-start)))

Tangle configuration automatically after each save

(eval-when-compile
  (require 'ob-tangle))

(defun tangle-config.org ()
  "If the current buffer is `config.org', tangle it!"
  (when (equal (file-truename (buffer-file-name))
               (expand-file-name "config.org" user-emacs-directory))
    (org-babel-tangle)))

(add-hook 'after-save-hook #'tangle-config.org)
(add-hook 'after-revert-hook #'tangle-config.org)

This approach is better than file-local hooks, because it also works with ~”C-x s”~ as well as revert-buffer.

About

My GNU Emacs configuration


Languages

Language:Emacs Lisp 100.0%