emacs-config/init.org

75 KiB
Raw Blame History

My Emacs Configuration

Literate Configuration for Emacs

Goals

  • Manage init.el as an org-file
  • Use a simple robust tangling solution which works together with Crafted Emacs
  • Leverage inclusion of use-package in Emacs29 for configuration and loading
  • Reduce dependencies : read evaluate the value a package brings before including it
  • Refactor existing configuration

First Things First

Make tangled file read-only

Add a preamble to the file to ensure some global settings, most importantly make the file read-only to avoid editing the tangled file by accident instead of the source in the org-mode file.

  ;;; -*- lexical-binding: t; read-only-mode: t; -*-
  ;;
  ;; DO NOT EDIT!!!
  ;;
  ;; This file is automatically generated from the source in *init.org*.
  ;;

Also immediately set lexical binding mode.

Set the garbage collector threshold, to avoid collections

(setq gc-cons-percentage 0.5
      gc-cons-threshold (* 128 1024 1024))

Report time spent loading the configuration

(defconst emacs-start-time (current-time))

(defun report-time-since-load (&optional suffix)
  (message "Loading init... (%.3fs) %s"
           (float-time (time-subtract (current-time) emacs-start-time))
           suffix))

(add-hook 'after-init-hook
          #'(lambda () (report-time-since-load " [after-init]"))
          t)

When looking for where the time goes, the `report-time-since-load` with a string indicating the location in the init process can report on the time since start.

Save customizations in a separate file

By default customization settings are saved at the end of the init.el file. This wreaks havoc with managing the files in git and will not work with the tangled version anyway as it will be removed/overwritten each time the file is regenerated. Here we set the location of the file to save the customizations and then load it.

  ;;; Code:
  (setq custom-file (concat user-emacs-directory "custom.el"))
  (when (and custom-file
             (file-exists-p custom-file))
    (load custom-file nil :nomessage))

Crafted Emacs

When I could not solve the persistent slowness I experienced on Windows using Doom Emacs, I wanted to switch to a more hands-on minimal Emacs configuration. I just watched the 'Rational Emacs' episode and that seemed as good a starting point as any. Then in August '22 it was renamed to Crafted Emacs which prompted a (minor) refactoring. I used for a quite a long term Doom on Linux and Rational/Crafted on Windows till I was comfortable enough and not started Doom anymore.

Initialize Crafted Emacs

Loads the Crafted Emacs initialization configuration:

  ;; initialize crafted-emacs
  (setq crafted-startup-inhibit-splash t)
  (load "~/.local/share/crafted-emacs/modules/crafted-init-config")

Add and configure packages and load crafted and custom modules

Lists and installs a variety of packages and includes custom configurations for them:

  (require 'crafted-completion-packages)
  (require 'crafted-evil-packages)
  (require 'crafted-lisp-packages)
  (require 'crafted-writing-packages)
  (require 'crafted-ui-packages)

  (message "loading packages")
  (crafted-package-install-selected-packages)
  (elpaca-wait)

  (require 'crafted-defaults-config)
  (require 'crafted-evil-config)
  (require 'crafted-completion-config)
  (require 'crafted-org-config)
  (require 'crafted-lisp-config)
  (require 'crafted-writing-config)
  (require 'crafted-ui-config):
pti-org-config

Integration with Environment

Set default Coding System to Use UTF-8 Everywhere

Ensures UTF-8 is the default coding system everywhere.

  (set-default-coding-systems 'utf-8)
  (set-language-environment 'utf-8)
  (setq locale-coding-system 'utf-8)
  (prefer-coding-system 'utf-8)


  ;; Treat clipboard input as UTF-8 string first; compound text next, etc.
  (setq x-select-request-type '(UTF8_STRING COMPOUND_TEXT TEXT STRING))

Heres a breakdown of what each line does:

  1. (set-default-coding-systems 'utf-8) This line sets the default coding system for new buffers. When you create a new buffer or open a file, Emacs will use UTF-8 encoding by default. It will also set the default terminal and keyboard coding systems. This applies to all internal operations where a specific coding system has not been specified.
  2. (set-language-environment 'utf-8) This sets the language environment to UTF-8. Emacs uses the language environment to guess the preferred coding systems for reading and writing files and for other operations. Setting this to UTF-8 ensures that UTF-8 is preferred in all language-related contexts.
  3. (setq locale-coding-system 'utf-8) This sets the coding system for locale data, such as environment variables and system messages. It ensures that Emacs correctly interprets UTF-8 encoded data coming from the operating system.
  4. (prefer-coding-system 'utf-8) This makes UTF-8 the preferred coding system for any situation where Emacs needs to choose an encoding. It ensures that Emacs prefers UTF-8 over other encodings.
  5. (setq x-select-request-type …) Treat clipboard input as UTF8_STRING first, compound text next, etc… .

Set Path from shell configuration

In order to get the paths in Emacs to be consistent with the ones in the terminals we get them from a started shell instead of the current environment which can be considerably different in X, Wayland or on Mac because the shell initialization scripts have not run yet.

    ;; set path from shell when started in UI-RELATED
    (use-package exec-path-from-shell
      :ensure t
      :defer 1
      :if (or (daemonp) (memq window-system '(mac ns x)))
      :config (exec-path-from-shell-initialize))

Setup backup directories

Configures where backup files are stored:

  ;; setup backup directories
  ;; see https://www.emacswiki.org/emacs/BackupDirectory
  (setq backup-directory-alist
   `(("." . ,(file-name-concat user-emacs-directory "backups"))))

Enable integration with the Unix Password Store aka pass

The pass command gives a super practical way to store secrets encrypted using gpg and use them in .envrc files, batch scripts on the command line and, of course, in Emacs.

  ;; enable unix password-store
  ;;(use-package epg)
  ;;(setq epg-pinentry-mode 'loopback)
  (auth-source-pass-enable)

This enables pass secrets to be used for all subsystems supporting auth-source (which are probably all of them nowadays). It does require some finagling to map the parts on the name in the pass system.

Use of Pass Secrets in ELisp

It is very convenient to get secrets from this store (once gpg is set up, which a totally different can of worms). A function `auth-source-pass-get` is provided :

(auth-source-pass-get 'secret "dummy/password")
shht!secret

GUIX support

Emacs-guix is a module to interact with the guix system and help manage packages and profiles. It also offers support for creating additional profiles and packages for Guix.

    (use-package guix
      :ensure t
      :defer 5)

I find it a bit a confusing module.

It provides a minor-mode

Editor Features

Evil Vim Keybindings

I configure all my apps to use vim keybindings if possible to maximise muscle memory.

  ;; some provided automations
  (crafted-evil-discourage-arrow-keys)
  (crafted-evil-vim-muscle-memory)
  ;; set leader keys
  (evil-set-leader nil (kbd "C-SPC"))
  (evil-set-leader 'normal (kbd "SPC"))
  (evil-set-leader 'normal "," t) ;; set localleader
  ;; make vc commands available via leader key
  (evil-define-key 'normal 'global
    (kbd  "<leader>v") 'vc-prefix-map)
  ;; make project commands available via leader key
  (evil-define-key 'normal 'global
    (kbd  "<leader>p") 'project-prefix-map)
  (evil-define-key 'normal 'global
    (kbd  "<leader>ff") #'find-file
    (kbd  "<leader>fo") #'recentf
    (kbd  "<leader>fr") #'revert-buffer
    (kbd  "<leader>fd") #'diff-buffer-with-file)
  ;; dired integration
  (evil-collection-dired-setup)
  (use-package harpoon
    :ensure t
    :commands (harpoon-add-file harpoon-go-to-1 harpoon-go-to-2 harpoon-toggle-quick-menu)
    :config
    (let ((pti-harpoon-map (make-sparse-keymap)))
      (keymap-set pti-harpoon-map (kbd  "h") 'harpoon-toggle-quick-menu)
      (keymap-set pti-harpoon-map (kbd  "a") 'harpoon-add-file)
      (keymap-set pti-harpoon-map (kbd  "f") 'harpoon-toggle-file)
      (keymap-set pti-harpoon-map (kbd  "j") 'harpoon-go-to-1)
      (keymap-set pti-harpoon-map (kbd  "k") 'harpoon-go-to-2)
      (keymap-set pti-harpoon-map (kbd  "l") 'harpoon-go-to-3)
      (keymap-set pti-harpoon-map (kbd  ";") 'harpoon-go-to-4)
      (keymap-set pti-harpoon-map (kbd  "h") 'harpoon-toggle-quick-menu)
      (evil-define-key 'normal 'global
        (kbd "<leader>h") (pti-harpoon-map))))

User Interface

Do not show default splash screen
;; Show splash screen
(unless crafted-startup-inhibit-splash
  (setq initial-buffer-choice #'crafted-startup-screen))

**

;; Profile emacs startup
(add-hook 'emacs-startup-hook
          (lambda ()
            (message "Emacs started in %s."
                     (emacs-init-time))))
Configure Fonts with Fontaine
  ;;
  ;; Font configuration
  ;;

  (defun pti-find-installed-font (fonts)
    "Find the first font in FONTS which is installed on this system."
    (seq-find
     (lambda (f) (find-font (font-spec :family f)))
     fonts))

  (defun pti-configure-fonts ()
    "Set configuration of fonts based on display size."
    (defvar pti-font-size
      (if (> (display-pixel-height) 1600) 22 14))

    ;; set startup and default fontsets to make org-bullet work

    (set-fontset-font "fontset-startup" nil "DejaVu Sans Mono" nil)
    (set-fontset-font "fontset-default" nil "DejaVu Sans Mono" nil)


    (setq fontaine-presets
          `((t
             :default-family ,(pti-find-installed-font
                               '("FiraCode NF" "FiraCode Nerd Font"))
             :default-weight regular
             :default-height ,pti-font-size
             :variable-pitch-family ,(pti-find-installed-font
                                      '("Lato" "Literation Sans" "FiraCode NF Propo" "FiraCode Nerd Font Propo" "DejaVu Sans"))
             :variable-pitch-height 1.33
             :bold-weight heavy
             :line-spacing 1
             )
            (regular
             :default-height 100)
            (small
             :default-height 75)
            (medium
             :default-height 125)
            (large
             :default-height 150)))

    (fontaine-set-preset 'regular))

  (use-package fontaine
    :ensure t
    :if (display-graphic-p)
    :demand t 
    :config (progn
              (pti-configure-fonts)
              (add-hook 'after-make-frame-functions #'pti-configure-fonts)))
Update configuration of each created frame

When running as daemon there is no graphical context. This means that all graphical related settings cannot be set properly at initial startup if we need to interrogate the capabilities of the current screen.

  ;;
  ;; remove chrome from the frames
  ;; also support client frames
  ;;
  (defun pti-display-tweaks (&optional frame)
    "Configure a newly created frame."
    
    (unless frame
      (setq frame (selected-frame)))
    (when (display-graphic-p)
      (with-selected-frame frame
        ;; remove chrome from UI
        (menu-bar-mode -1)
        (tool-bar-mode -1)
        (scroll-bar-mode -1)
        )))

  (add-hook 'after-make-frame-functions #'pti-display-tweaks)
  ;; run it in the current frame, because the hooks have already fired
  (pti-display-tweaks)
Set Theme
  ;; Set theme
  (use-package modus-themes
    :ensure t
    :demand t
    :custom
    (modus-themes-italic-constructs t)
    (modus-themes-bold-constructs t)
    (modus-themes-mixed-fonts t "enable mixed fonts in org and markdown et al.")

    (modus-themes-to-toggle '(modus-operandi-tinted modus-vivendi-tinted))

    (modus-themes-completions '((matches . (extrabold background intense))
                                (selection . (semibold accented intense))))
    (modus-themes-org-blocks 'tinted-background)
    (modus-themes-mixed-fonts t)

    (modus-themes-headings '((1 . (monochrome extrabold background overline variable-pitch 1.6))
                             (2 . (monochrome bold overline 1.4))
                             (3 . (monochrome semibold overline 1.3))
                             (4 . (monochrome 1.2))
                             (5 . (monochrome 1.1))
                             (agenda-date . (semilight 1.5))
                             (agenda-structure . (variable-pitch light 1.9))
                             (t . (monochrome light))))
    :config
    (load-theme 'modus-operandi-tinted :no-confirm)
    :bind
    (("<f5>" . #'modus-themes-toggle)))

There is a keybinding on <F5> to toggle between light and dark mode.

modus-themes-toggle
Limit Height of Selected Popup Windows
  (push '("\\*Occur\\*"
          ;; display-buffer functions, first one that succeeds is used
          (display-buffer-reuse-mode-window
           display-buffer-below-selected)
          ;; Parameters
          (window-height . (lambda (window) (fit-window-to-buffer window 12)))) ;; fit window, max 12 lines
        display-buffer-alist)
  (push '("\\*Warnings\\*"
          ;; display-buffer functions, first one that succeeds is used
          (display-buffer-reuse-mode-window
           display-buffer-below-selected)
          ;; Parameters
          (window-height . (lambda (window) (fit-window-to-buffer window 12)))) ;; fit window, max 12 lines
        display-buffer-alist)
  (push '("\\*Geiser Debug\\*"
          ;; display-buffer functions, first one that succeeds is used
          (display-buffer-reuse-mode-window
           display-buffer-below-selected)
          ;; Parameters
          (window-height . (lambda (window) (fit-window-to-buffer window 12)))) ;; fit window, max 12 lines
        display-buffer-alist)

  ;; set custom info folder
  (add-to-list 'Info-default-directory-list "~/.local/share/info/")

Yasnippet configuration

Enables and configures Yasnippet, a template system for Emacs:

  ;; configure yasnippet
  (use-package yasnippet
    :ensure nil
    :defer 5
    :config
    (yas-global-mode 1))
[nil 26279 35446 711851 nil elpaca-process-queues nil nil 81000 nil]

Enable LLM access with Ellama

Configures access to language models using Ellama. I don't know whether to put it under writing, comms or programming as it is equally useful(?) for either activity. So I promoted it to an Editor feature.

    (use-package llm
      :ensure t
      :defer 10)
    (use-package llm-openai
      :ensure nil
      :after llm)
    ;; enable LLM access with ellama
    (use-package ellama
      :ensure t
      :after llm-openai
      :commands (ellama-chat ellama-ask-about ellama-ask-line ellama-ask-selection)
      :config
      (setopt ellama-language "English")
      (setopt ellama-keymap-prefix "<leader>a")
      (setopt ellama-provider
              (make-llm-openai
               :key (auth-source-pass-get 'secret "snamellit/openai-api-key")
               :chat-model "gpt-4o"
               ))
      (message "Ellama is available"))
[nil 26280 51581 64425 nil elpaca-process-queues nil nil 916000 nil]

It seems the gpt-4o model provides better responses. I should investigate local models more.

Dired Configuration

Enables an alternative file navigation behavior in Dired, Emacs' directory editor:

  (put 'dired-find-alternate-file 'disabled nil)

Use Magit for version control

        ;; default to magit for version control
        (use-package magit
          :ensure t
          :commands (magit-status)
          :bind
          (("C-x p v" . magit-status)
           ("<leader> p v" . magit-status)))

Better EDiff support

  ;; better ediff setting suggested by cwebber
  (use-package ediff
    :commands (ediff ediff-files ediff3 ediff-files3)
    :config
    (setq
     ediff-window-setup-function 'ediff-setup-windows-plain
     ediff-split-window-function 'split-window-horizontally))
t

Replace normal Buffer Support with IBuffer

  ;;; enhance ibuffer with ibuffer-project if it is available.
  (defun crafted-ide-enhance-ibuffer-with-ibuffer-project ()
    "Set up integration for `ibuffer' with `ibuffer-project'."
    (setq ibuffer-filter-groups (ibuffer-project-generate-filter-groups))
    (unless (eq ibuffer-sorting-mode 'project-file-relative)
      (ibuffer-do-sort-by-project-file-relative)))
  
  (use-package ibuffer-project
    :ensure t 
    :hook
    (ibuffer-mode . crafted-ide-enhance-ibuffer-with-ibuffer-project)
    :bind (("C-x C-b" . #'ibuffer)))

Enable EditorConfig Standard Support

  ;;; load editorconfig support
  (use-package editorconfig
    :hook
    (prog-mode . (lambda() (editorconfig-mode 1))))
editorconfig-mode

Enable Direnv Integration

Direnv is the key to have isolated project folders which maintain their own bubble to support whatever is done in that folder.

  • set environment variables
  • run prep scripts or start services
  • load nix or guix profiles to provide support applications

Emacs' direnv module gives first class support to Emacs projects so they use the right tools for the project.

  ;; enable direnv mode
  (use-package direnv
    :ensure t
    :config
    (direnv-mode))

This enables direnv globally.

Writing

Org Mode

Mixed Pitch Support by Default in Org
    (defun pti-org-mode-config ()
      "Set options for better writing in org buffers."
      (mixed-pitch-mode)
      (visual-line-mode)
      (turn-on-auto-fill)
      )

    (use-package org-bullets
      :ensure t
      :if (display-graphic-p)
      :hook (org-mode . org-bullets-mode))
      
    (use-package mixed-pitch
      :ensure t
      :if (display-graphic-p)
      :hook
      (org-mode . mixed-pitch-mode))
[nil 26279 36119 854408 nil elpaca-process-queues nil nil 376000 nil]
Org Configuration
      (use-package org
        :ensure nil
        :custom
        (org-agenda-skip-scheduled-if-done t)
        (org-agenda-skip-deadline-if-done t)
        (org-todo-keywords '((sequence "TODO(t)" "NEXT(n)" "WAITING(w)" "SOMEDAY(s)" "PROJ(p)"
                                       "|" "DONE(d)" "CANCELED(c)")
                             (sequence "[ ](T)" "[-](S)" "[?](W)"
                                       "|" "[X](D)")
                             (sequence "|" "OKAY(o)" "YES(y)" "NO(n)")))
        (org-todo-keywords-for-agenda '((sequence "NEXT(n)" "TODO(t)" "WAITING(w)" "SOMEDAY(s)" "PROJ(p)" "|" "DONE(d)" "CANCELED(c)")))
        (line-spacing 0.1)
        (left-margin-width 2)
        (right-margin-width 2)
        (org-hide-emphasis-markers t)
        (org-agenda-files (list "~/Dropbox/org/" "~/org/snamellit/" "~/org/customer/" "~/org/personal/"))
        (org-refile-targets '(
                              (org-agenda-files . (:level . 1))
                              ("~/org/personal/bijen.org" . (:level . 1))
                              ("~/org/personal/fitness.org" . (:level . 1))
                              ))
        :config 
        ;; set files for agenda views
        (setq +org-capture-todo-file "~/Dropbox/org/inbox.org"
              +org-capture-notes-file "~/Dropbox/org/inbox.org"
              +org-capture-journal-file "~/Dropbox/org/journal.org"
              +org-capture-projects-file "~/Dropbox/org/projects.org")
        :hook
        (org-mode . pti-org-mode-config)
    )
((\.org\' . org) (\.rs\' . rust-mode) (\.ya?ml\' . yaml-ts-mode) (\.ts\' . typescript-ts-mode) (\.tsx\' . tsx-ts-mode) (\.toml\' . toml-ts-mode) (\.rs\' . rust-ts-mode) (\(?:\.\(?:rbw?\|ru\|rake\|thor\|jbuilder\|rabl\|gemspec\|podspec\)\|/\(?:Gem\|Rake\|Cap\|Thor\|Puppet\|Berks\|Brew\|Vagrant\|Guard\|Pod\)file\)\' . ruby-ts-mode) (\.py[iw]?\' . python-ts-mode) (\.lua\' . lua-ts-mode) (\.json\' . json-ts-mode) (\.js\' . js-ts-mode) (\.java\' . java-ts-mode) (\.html\' . html-ts-mode) (\.heex\' . heex-ts-mode) (go\.mod\' . go-mod-ts-mode) (\.go\' . go-ts-mode) (\.ex\' . elixir-ts-mode) ([/\]\(?:Containerfile\|Dockerfile\)\(?:\.[^/\]*\)?\' . dockerfile-ts-mode) (\.css\' . css-ts-mode) (\.cpp\' . c++-ts-mode) (\.cmake\' . cmake-ts-mode) (\.cs\' . csharp-ts-mode) (\.c\' . c-ts-mode) (\.sh\' . bash-ts-mode) (\.tf\(vars\)?\' . terraform-mode) (\.nomad\' . hcl-mode) (\.hcl\' . hcl-mode) ([./]opam_?\' . tuareg-opam-mode) (\.mly\' . tuareg-menhir-mode) (\.eliomi?\' . tuareg-mode) (\.ml[ip]?\' . tuareg-mode) (\.rs\' . rustic-mode) (\.envrc\' . direnv-envrc-mode) (/git-rebase-todo\' . git-rebase-mode) (/guix/drvs/[[:alnum:]]\{2\}/[[:alnum:]]\{30\}-\(?:[+._[:alnum:]-]+\)\.drv\' . guix-build-log-mode) (/gnu/store/\(?:[+._[:alnum:]-]+\)\.drv\' . guix-derivation-mode) (/etc/profile\' . guix-env-var-mode) (/tmp/guix-build-\(?:[+._[:alnum:]-]+\)\.drv-[[:digit:]]+/environment-variables\' . guix-env-var-mode) (/guix/profiles/system\(?:[+._[:alnum:]-]+\)*/\(?:boot\|parameters\)\' . guix-scheme-mode) (/gnu/store/\(?:[0-9a-df-np-sv-z]\{32\}\)-\(?:activate\|activate-service\|boot\|parameters\|shepherd\.conf\|shepherd\(?:[+._[:alnum:]-]+\)\.scm\|\(?:[+._[:alnum:]-]+\)-builder\)\' . guix-scheme-mode) (\(?:build\|profile\)\.boot\' . clojure-mode) (\.cljs\' . clojurescript-mode) (\.cljc\' . clojurec-mode) (\.\(clj\|cljd\|dtm\|edn\|lpy\)\' . clojure-mode) (\.\(?:md\|markdown\|mkd\|mdown\|mkdn\|mdwn\)\' . markdown-mode) (\.hva\' . LaTeX-mode) (\.gpg\(~\|\.~[0-9]+~\)?\' nil epa-file) (\.elc\' . elisp-byte-code-mode) (\.zst\' nil jka-compr) (\.dz\' nil jka-compr) (\.xz\' nil jka-compr) (\.lzma\' nil jka-compr) (\.lz\' nil jka-compr) (\.g?z\' nil jka-compr) (\.bz2\' nil jka-compr) (\.Z\' nil jka-compr) (\.editorconfig\' . editorconfig-conf-mode) (\.vr[hi]?\' . vera-mode) (\(?:\.\(?:rbw?\|ru\|rake\|thor\|axlsx\|jbuilder\|rabl\|gemspec\|podspec\)\|/\(?:Gem\|Rake\|Cap\|Thor\|Puppet\|Berks\|Brew\|Fast\|Vagrant\|Guard\|Pod\)file\)\' . ruby-mode) (\.re?st\' . rst-mode) (/\(?:Pipfile\|\.?flake8\)\' . conf-mode) (\.py[iw]?\' . python-mode) (\.m\' . octave-maybe-mode) (\.less\' . less-css-mode) (\.scss\' . scss-mode) (\.cs\' . csharp-mode) (\.awk\' . awk-mode) (\.\(u?lpc\|pike\|pmod\(\.in\)?\)\' . pike-mode) (\.idl\' . idl-mode) (\.java\' . java-mode) (\.m\' . objc-mode) (\.ii\' . c++-mode) (\.i\' . c-mode) (\.lex\' . c-mode) (\.y\(acc\)?\' . c-mode) (\.h\' . c-or-c++-mode) (\.c\' . c-mode) (\.\(CC?\|HH?\)\' . c++-mode) (\.[ch]\(pp\|xx\|\+\+\)\' . c++-mode) (\.\(cc\|hh\)\' . c++-mode) (\.\(bat\|cmd\)\' . bat-mode) (\.[sx]?html?\(\.[a-zA-Z_]+\)?\' . mhtml-mode) (\.svgz?\' . image-mode) (\.svgz?\' . xml-mode) (\.x[bp]m\' . image-mode) (\.x[bp]m\' . c-mode) (\.p[bpgn]m\' . image-mode) (\.tiff?\' . image-mode) (\.gif\' . image-mode) (\.png\' . image-mode) (\.jpe?g\' . image-mode) (\.webp\' . image-mode) (\.te?xt\' . text-mode) (\.[tT]e[xX]\' . tex-mode) (\.ins\' . tex-mode) (\.ltx\' . latex-mode) (\.dtx\' . doctex-mode) (\.org\' . org-mode) (\.dir-locals\(?:-2\)?\.el\' . lisp-data-mode) (\.eld\' . lisp-data-mode) (eww-bookmarks\' . lisp-data-mode) (tramp\' . lisp-data-mode) (/archive-contents\' . lisp-data-mode) (places\' . lisp-data-mode) (\.emacs-places\' . lisp-data-mode) (\.el\' . emacs-lisp-mode) (Project\.ede\' . emacs-lisp-mode) (\(?:\.\(?:scm\|sls\|sld\|stk\|ss\|sch\)\|/\.guile\)\' . scheme-mode) (\.l\' . lisp-mode) (\.li?sp\' . lisp-mode) (\.[fF]\' . fortran-mode) (\.for\' . fortran-mode) (\.p\' . pascal-mode) (\.pas\' . pascal-mode) (\.\(dpr\|DPR\)\' . delphi-mode) (\.\([pP]\([Llm]\|erl\|od\)\|al\)\' . perl-mode) (Imakefile\' . makefile-imake-mode) (Makeppfile\(?:\.mk\)?\' . makefile-makepp-mode) (\.makepp\' . makefile-makepp-mode) (\.mk\' . makefile-gmake-mode) (\.make\' . makefile-gmake-mode) ([Mm]akefile\' . makefile-gmake-mode) (\.am\' . makefile-automake-mode) (\.texinfo\' . texinfo-mode) (\.te?xi\' . texinfo-mode) (\.[sS]\' . asm-mode) (\.asm\' . asm-mode) (\.css\' . css-mode) (\.mixal\' . mixal-mode) (\.gcov\' . compilation-mode) (/\.[a-z0-9-]*gdbinit . gdb-script-mode) (-gdb\.gdb . gdb-script-mode) ([cC]hange\.?[lL]og?\' . change-log-mode) ([cC]hange[lL]og[-.][0-9]+\' . change-log-mode) (\$CHANGE_LOG\$\.TXT . change-log-mode) (\.scm\.[0-9]*\' . scheme-mode) (\.[ckz]?sh\'\|\.shar\'\|/\.z?profile\' . sh-mode) (\.bash\' . sh-mode) (/PKGBUILD\' . sh-mode) (\(/\|\`\)\.\(bash_\(profile\|history\|log\(in\|out\)\)\|z?log\(in\|out\)\)\' . sh-mode) (\(/\|\`\)\.\(shrc\|zshrc\|m?kshrc\|bashrc\|t?cshrc\|esrc\)\' . sh-mode) (\(/\|\`\)\.\([kz]shenv\|xinitrc\|startxrc\|xsession\)\' . sh-mode) (\.m?spec\' . sh-mode) (\.m[mes]\' . nroff-mode) (\.man\' . nroff-mode) (\.sty\' . latex-mode) (\.cl[so]\' . latex-mode) (\.bbl\' . latex-mode) (\.bib\' . bibtex-mode) (\.bst\' . bibtex-style-mode) (\.sql\' . sql-mode) (\(acinclude\|aclocal\|acsite\)\.m4\' . autoconf-mode) (\.m[4c]\' . m4-mode) (\.mf\' . metafont-mode) (\.mp\' . metapost-mode) (\.vhdl?\' . vhdl-mode) (\.article\' . text-mode) (\.letter\' . text-mode) (\.i?tcl\' . tcl-mode) (\.exp\' . tcl-mode) (\.itk\' . tcl-mode) (\.icn\' . icon-mode) (\.sim\' . simula-mode) (\.mss\' . scribe-mode) (\.f9[05]\' . f90-mode) (\.f0[38]\' . f90-mode) (\.indent\.pro\' . fundamental-mode) (\.\(pro\|PRO\)\' . idlwave-mode) (\.srt\' . srecode-template-mode) (\.prolog\' . prolog-mode) (\.tar\' . tar-mode) (\.\(arc\|zip\|lzh\|lha\|zoo\|[jew]ar\|xpi\|rar\|cbr\|7z\|squashfs\|ARC\|ZIP\|LZH\|LHA\|ZOO\|[JEW]AR\|XPI\|RAR\|CBR\|7Z\|SQUASHFS\)\' . archive-mode) (\.oxt\' . archive-mode) (\.\(deb\|[oi]pk\)\' . archive-mode) (\`/tmp/Re . text-mode) (/Message[0-9]*\' . text-mode) (\`/tmp/fol/ . text-mode) (\.oak\' . scheme-mode) (\.sgml?\' . sgml-mode) (\.x[ms]l\' . xml-mode) (\.dbk\' . xml-mode) (\.dtd\' . sgml-mode) (\.ds\(ss\)?l\' . dsssl-mode) (\.js[mx]?\' . javascript-mode) (\.har\' . javascript-mode) (\.json\' . js-json-mode) (\.[ds]?va?h?\' . verilog-mode) (\.by\' . bovine-grammar-mode) (\.wy\' . wisent-grammar-mode) (\.erts\' . erts-mode) ([:/\]\..*\(emacs\|gnus\|viper\)\' . emacs-lisp-mode) (\`\..*emacs\' . emacs-lisp-mode) ([:/]_emacs\' . emacs-lisp-mode) (/crontab\.X*[0-9]+\' . shell-script-mode) (\.ml\' . lisp-mode) (\.ld[si]?\' . ld-script-mode) (ld\.?script\' . ld-script-mode) (\.xs\' . c-mode) (\.x[abdsru]?[cnw]?\' . ld-script-mode) (\.zone\' . dns-mode) (\.soa\' . dns-mode) (\.asd\' . lisp-mode) (\.\(asn\|mib\|smi\)\' . snmp-mode) (\.\(as\|mi\|sm\)2\' . snmpv2-mode) (\.\(diffs?\|patch\|rej\)\' . diff-mode) (\.\(dif\|pat\)\' . diff-mode) (\.[eE]?[pP][sS]\' . ps-mode) (\.\(?:PDF\|EPUB\|CBZ\|FB2\|O?XPS\|DVI\|OD[FGPST]\|DOCX\|XLSX?\|PPTX?\|pdf\|epub\|cbz\|fb2\|o?xps\|djvu\|dvi\|od[fgpst]\|docx\|xlsx?\|pptx?\)\' . doc-view-mode-maybe) (configure\.\(ac\|in\)\' . autoconf-mode) (\.s\(v\|iv\|ieve\)\' . sieve-mode) (BROWSE\' . ebrowse-tree-mode) (\.ebrowse\' . ebrowse-tree-mode) (#\*mail\* . mail-mode) (\.g\' . antlr-mode) (\.mod\' . m2-mode) (\.ses\' . ses-mode) (\.docbook\' . sgml-mode) (\.com\' . dcl-mode) (/config\.\(?:bat\|log\)\' . fundamental-mode) (/\.?\(authinfo\|netrc\)\' . authinfo-mode) (\.\(?:[iI][nN][iI]\|[lL][sS][tT]\|[rR][eE][gG]\|[sS][yY][sS]\)\' . conf-mode) (\.la\' . conf-unix-mode) (\.ppd\' . conf-ppd-mode) (java.+\.conf\' . conf-javaprop-mode) (\.properties\(?:\.[a-zA-Z0-9._-]+\)?\' . conf-javaprop-mode) (\.toml\' . conf-toml-mode) (\.desktop\' . conf-desktop-mode) (/\.redshift\.conf\' . conf-windows-mode) (\`/etc/\(?:DIR_COLORS\|ethers\|.?fstab\|.*hosts\|lesskey\|login\.?de\(?:fs\|vperm\)\|magic\|mtab\|pam\.d/.*\|permissions\(?:\.d/.+\)?\|protocols\|rpc\|services\)\' . conf-space-mode) (\`/etc/\(?:acpid?/.+\|aliases\(?:\.d/.+\)?\|default/.+\|group-?\|hosts\..+\|inittab\|ksysguarddrc\|opera6rc\|passwd-?\|shadow-?\|sysconfig/.+\)\' . conf-mode) ([cC]hange[lL]og[-.][-0-9a-z]+\' . change-log-mode) (/\.?\(?:gitconfig\|gnokiirc\|hgrc\|kde.*rc\|mime\.types\|wgetrc\)\' . conf-mode) (/\.mailmap\' . conf-unix-mode) (/\.\(?:asound\|enigma\|fetchmail\|gltron\|gtk\|hxplayer\|mairix\|mbsync\|msmtp\|net\|neverball\|nvidia-settings-\|offlineimap\|qt/.+\|realplayer\|reportbug\|rtorrent\.\|screen\|scummvm\|sversion\|sylpheed/.+\|xmp\)rc\' . conf-mode) (/\.\(?:gdbtkinit\|grip\|mpdconf\|notmuch-config\|orbital/.+txt\|rhosts\|tuxracer/options\)\' . conf-mode) (/\.?X\(?:default\|resource\|re\)s\> . conf-xdefaults-mode) (/X11.+app-defaults/\|\.ad\' . conf-xdefaults-mode) (/X11.+locale/.+/Compose\' . conf-colon-mode) (/X11.+locale/compose\.dir\' . conf-javaprop-mode) (\.~?[0-9]+\.[0-9][-.0-9]*~?\' nil t) (\.\(?:orig\|in\|[bB][aA][kK]\)\' nil t) ([/.]c\(?:on\)?f\(?:i?g\)?\(?:\.[a-zA-Z0-9._-]+\)?\' . conf-mode-maybe) (\.[1-9]\' . nroff-mode) (\.art\' . image-mode) (\.avs\' . image-mode) (\.bmp\' . image-mode) (\.cmyk\' . image-mode) (\.cmyka\' . image-mode) (\.crw\' . image-mode) (\.dcr\' . image-mode) (\.dcx\' . image-mode) (\.dng\' . image-mode) (\.dpx\' . image-mode) (\.fax\' . image-mode) (\.heic\' . image-mode) (\.hrz\' . image-mode) (\.icb\' . image-mode) (\.icc\' . image-mode) (\.icm\' . image-mode) (\.ico\' . image-mode) (\.icon\' . image-mode) (\.jbg\' . image-mode) (\.jbig\' . image-mode) (\.jng\' . image-mode) (\.jnx\' . image-mode) (\.miff\' . image-mode) (\.mng\' . image-mode) (\.mvg\' . image-mode) (\.otb\' . image-mode) (\.p7\' . image-mode) (\.pcx\' . image-mode) (\.pdb\' . image-mode) (\.pfa\' . image-mode) (\.pfb\' . image-mode) (\.picon\' . image-mode) (\.pict\' . image-mode) (\.rgb\' . image-mode) (\.rgba\' . image-mode) (\.tga\' . image-mode) (\.wbmp\' . image-mode) (\.webp\' . image-mode) (\.wmf\' . image-mode) (\.wpg\' . image-mode) (\.xcf\' . image-mode) (\.xmp\' . image-mode) (\.xwd\' . image-mode) (\.yuv\' . image-mode) (\.tgz\' . tar-mode) (\.tbz2?\' . tar-mode) (\.txz\' . tar-mode) (\.tzst\' . tar-mode) (\.drv\' . LaTeX-mode))
Org Babel Support
  ;; enable verb package to do REST calls
  (use-package verb
    :ensure t
    :defer 3
    :bind
    (:map org-mode-map
                ("C-c C-r" . verb-command-map)))

  (use-package ob-verb
    :after verb
    :defer 3)
  ;; configure babel languages

  (use-package org-babel
    :no-require
    :after '(ob-verb)
    :config
    (org-babel-do-load-languages
     'org-babel-load-languages
     '((emacs-lisp . t)
       (shell . t)
       (python . t)
       (latex . t)
       (verb . t)
       (scheme . t)
       (plantuml . t))))

  ;; fix a bug in ob-scheme which causes an error to be thrown when evaluating
  ;; a cons cell or improper list
  ;; see https://list.orgmode.org/87bkk3x1bu.fsf@gajsin.name/T/
  (defun org-babel-scheme--table-or-string (results)
    "Convert RESULTS into an appropriate elisp value.
  If the results look like a list or tuple, then convert them into an
  Emacs-lisp table, otherwise return the results as a string."
    (let ((res (org-babel-script-escape results)))
      (cond ((proper-list-p res)
             (mapcar (lambda (el)
                       (if (or (null el) (eq el 'null))
                           org-babel-scheme-null-to
                         el))
                     res))
            (t res))))
org-babel-scheme--table-or-string
Org Export
    (use-package ox
      :ensure nil)
Org Export to Markdown
  (use-package ox-md
    :ensure nil
    :after ox
    )
Org Latex Export
Syntax Highlighting requires Multiple Passes
  ;; support for minted in LaTeX for code highlighting
  (use-package ox-latex
    :ensure nil
    :after ox
    :config
    (setq org-latex-compiler "lualatex")
    (setq org-latex-pdf-process
          '("%latex -shell-escape -interaction nonstopmode -output-directory %o %f"
            "%latex -interaction nonstopmode -output-directory %o %f"
            "%latex -interaction nonstopmode -output-directory %o %f"))
    )
Load Customer Latex Classes
    ;; Customer LaTeX classes
    (setq mlx-latex-classes
          '(
            ("mlx-beamer" "\\documentclass{mlx-beamer}
          [NO-DEFAULT-PACKAGES]
          [NO-PACKAGES]"
             ("\\section{%s}" . "\\section*{%s}")
             ("\\subsection{%s}" . "\\subsection*{%s}")
             ("\\subsubsection{%s}" . "\\subsubsection*{%s}"))
            ("mlx-book" "\\documentclass{mlx-book}
          [NO-DEFAULT-PACKAGES]
          [NO-PACKAGES]"
             ("\\part{%s}" . "\\part*{%s}")
             ("\\chapter{%s}" . "\\chapter*{%s}")
             ("\\section{%s}" . "\\section*{%s}")
             ("\\subsection{%s}" . "\\subsection*{%s}")
             ("\\subsubsection{%s}" . "\\subsubsection*{%s}"))
            ("mlx-report" "\\documentclass{mlx-report}
          [NO-DEFAULT-PACKAGES]
          [NO-PACKAGES]"
             ("\\part{%s}" . "\\part*{%s}")
             ("\\chapter{%s}" . "\\chapter*{%s}")
             ("\\section{%s}" . "\\section*{%s}")
             ("\\subsection{%s}" . "\\subsection*{%s}")
             ("\\subsubsection{%s}" . "\\subsubsection*{%s}"))
            ("mlx-article" "\\documentclass{mlx-article}
          [NO-DEFAULT-PACKAGES]
          [NO-PACKAGES]"
             ("\\section{%s}" . "\\section*{%s}")
             ("\\subsection{%s}" . "\\subsection*{%s}")
             ("\\subsubsection{%s}" . "\\subsubsection*{%s}")
             ("\\paragraph{%s}" . "\\paragraph*{%s}")
             ("\\subparagraph{%s}" . "\\subparagraph*{%s}"))
            ("mlx-project-charter" "\\documentclass{mlx-project-charter}
          [NO-DEFAULT-PACKAGES]
          [NO-PACKAGES]"
             ("\\section{%s}
    \\begin{mdframed}" "\\end{mdframed}")
             ("\\subsection{%s}" . "\\subsection*{%s}")
             ("\\subsubsection{%s}" . "\\subsubsection*{%s}")
             ("\\paragraph{%s}" . "\\paragraph*{%s}")
             ("\\subparagraph{%s}" . "\\subparagraph*{%s}"))))
Load My Latex Classes
    (setq snm-latex-classes
          '(
            ("snm-beamer" "\\documentclass{snm-beamer}
          [NO-DEFAULT-PACKAGES]
          [NO-PACKAGES]"
             ("\\section{%s}" . "\\section*{%s}")
             ("\\subsection{%s}" . "\\subsection*{%s}")
             ("\\subsubsection{%s}" . "\\subsubsection*{%s}"))
            ("snm-book" "\\documentclass{snm-book}
          [NO-DEFAULT-PACKAGES]
          [NO-PACKAGES]"
             ("\\part{%s}" . "\\part*{%s}")
             ("\\chapter{%s}" . "\\chapter*{%s}")
             ("\\section{%s}" . "\\section*{%s}")
             ("\\subsection{%s}" . "\\subsection*{%s}")
             ("\\subsubsection{%s}" . "\\subsubsection*{%s}"))
            ("snm-report" "\\documentclass{snm-report}
          [NO-DEFAULT-PACKAGES]
          [NO-PACKAGES]"
             ("\\part{%s}" . "\\part*{%s}")
             ("\\chapter{%s}" . "\\chapter*{%s}")
             ("\\section{%s}" . "\\section*{%s}")
             ("\\subsection{%s}" . "\\subsection*{%s}")
             ("\\subsubsection{%s}" . "\\subsubsection*{%s}"))
            ("snm-invoice" "\\documentclass{snm-invoice}
          [NO-DEFAULT-PACKAGES]
          [NO-PACKAGES]"
             ("\\section{%s}" . "\\section*{%s}")
             ("\\subsection{%s}" . "\\subsection*{%s}")
             ("\\subsubsection{%s}" . "\\subsubsection*{%s}")
             ("\\paragraph{%s}" . "\\paragraph*{%s}")
             ("\\subparagraph{%s}" . "\\subparagraph*{%s}"))
            ("snm-article" "\\documentclass{snm-article}
          [NO-DEFAULT-PACKAGES]
          [NO-PACKAGES]"
             ("\\section{%s}" . "\\section*{%s}")
             ("\\subsection{%s}" . "\\subsection*{%s}")
             ("\\subsubsection{%s}" . "\\subsubsection*{%s}")
             ("\\paragraph{%s}" . "\\paragraph*{%s}")
             ("\\subparagraph{%s}" . "\\subparagraph*{%s}"))))

    (with-eval-after-load 'ox-latex
      (dolist
          (class (append mlx-latex-classes snm-latex-classes))
        (add-to-list 'org-latex-classes class)))
Org Blogging
  ;; I started blogging again. To stimulate this let's add a template to get started
  ;; on a new post.
  (defvar snamellit/blog-title "test-title")
  (defun snamellit/capture-blog-post-file ()
    "Create a new blog post file with a title and date."
    (let* ((title (read-from-minibuffer "Post Title: "))
           (slug (replace-regexp-in-string "[^a-z0-9]+" "-" (downcase title))))
      (setq snamellit/blog-title title)
      (format "~/src/website/content/blog/%s-%s.md"
              (format-time-string "%Y-%m-%d" (current-time))
              slug)))
Org Capture Customization
  (use-package org-capture
    :ensure nil
    :config (progn
              (setq org-capture-templates '())
              (add-to-list 'org-capture-templates
                           '("t" "Todo" entry (file+headline +org-capture-todo-file "Tasks")
                             "* TODO %?\n  %i\n  %a"))
              (add-to-list 'org-capture-templates
                           '("n" "Note" entry (file+headline +org-capture-todo-file "Notes")
                             "* %?\n  %i\n  %a"))
              (add-to-list 'org-capture-templates
                           '("j" "Journal" entry (file+datetree +org-capture-journal-file)
                             "* %?\nEntered on %U\n  %i\n  %a"))
              (add-to-list 'org-capture-templates
                           '("b" "Blog Post" plain
                             (file snamellit/capture-blog-post-file)
                             "+++\ntitle = %(progn snamellit/blog-title)\ndate = %t\nauthor = Peter Tillemans\nemail = pti@snamellit.com\n[taxonomies]\ntags = []\ncategories = []\n+++\n\n%?"))

              ;; configure support for recipes
              (add-to-list 'org-capture-templates
                           '("c" "Cookbook" entry (file "~/org/cookbook.org")
                             "%(org-chef-get-recipe-from-url)"
                             :empty-lines 1))
              (add-to-list 'org-capture-templates
                           '("m" "Manual Cookbook" entry (file "~/org/cookbook.org")
                             "* %^{Recipe title: }\n  :PROPERTIES:\n  :source-url:\n  :servings:\n  :prep-time:\n  :cook-time:\n  :ready-in:\n  :END:\n** Ingredients\n   %?\n** Directions\n\n")))
    :bind (("<leader>X" . org-capture)))
t
Evil Support for Org

Better keybinding for evil mode from the evil-org github repo.

  (use-package evil-org
    :ensure t
    :after org
    :hook
    (org-mode . evil-org-mode)
    :config
    (require 'evil-org-agenda)
    (evil-org-agenda-set-keys))

Here is a snapshot of the keybindings dd <2024-07-30 Tue>.

Quick overview
key explanation
gh, gj, gk, gl navigate between elements
vae select an element
Headings and items
key explanation
M-ret insert heading
<tab>, g TAB fold / unfold headings
M-h or << promote a heading
M-l or >> demote a heading
M-k move subtree up
M-j move subtree down
M-S-h or <aR promote a subtree
M-S-l or >aR demote a subtree
vaR select a subtree
Tables
key explanation
( previous table cell
) next table cell
{ beginning of table
} end of table
M-h / M-l move table column left / right
M-k / M-j move table column up / down
vae select table cell
vaE select table row
var select whole table
Agenda
Evil key Emacs key explanation
gH Move cursor to the top of window
gM Move cursor to the middle of window
gL Move cursor to the bottom of window
<tab>, S-<return> <tab> go to the corresponding entry at point
g TAB <tab> go to the corresponding entry at point
<return> <return> go to the Org mode file which contains the item at point
M-<return> L Display Org file and center around the item
<space> <space> scroll up
<delete> or <backspace> <delete> or <backspace> scroll down
j, k n, p next, previous line
gj, gk, C-j, C-k N, P next, previous item
[, ] b, f previous, next week
J, K -, +, S-down, S-up down, up priority
H, L S-left, S-right modify date to earlier, later
t t cycle TODO keywords
M-j, M-k M-down, M-up drag line forward, backward
C-S-h, C-S-l C-S-left, C-S-right previous, next keyword
u C-_, C-/ undo
dd C-k delete item
da a ask and archive item
dA $ archive item
ct : set tags
ce e set effort
cT ; set timer
i i insert entry in diary
a z add note
A A append to agenda
C k capture
m m mark
* * toggle all marks
% % mark regexp
M U remove all marks
x B execute action on marks
gr r refresh agenda
gR g refresh all agendas
ZQ x exit agenda
ZZ Q quit agenda
gD v tweak display (deadlines, diary, follow/log-mode, entry text, grid, day/week/year
ZD # dim blocked tasks
sc, sr, se, st, s^ <, =, _, /, ^ filter by category, regexp, effort, tag, top headline
S | remove all filters
ss ~ filter/limit interactively
I I clock in
O O clock out
cg J jump to the currently clocked in task within the agenda
cc X cancel the current running clock
cr R toggle clocktable mode in an agenda buffer
. . go to today's date
gc c pop up calendar
gC C pop up date converter
p > pop up date selector
gh H pop up holiday calendar
gm M pop up phases of the moon
gs S pop up sunrise/sunset times
gt T pop up tag list
+, - [, ] manipulate the query by adding a search term with positive or negative selection
Org GCal Support
  ;; configure support for google calendar
  (use-package org-gcal
    :ensure t
    :defer 10
    :init
    (setq
     org-gcal-client-id (auth-source-pass-get 'secret "snamellit/org-gcal-client")
     org-gcal-client-secret (auth-source-pass-get "id" "snamellit/org-gcal-client")
     org-gcal-fetch-file-alist '(("pti@snamellit.com" .  "~/org/schedule.org"))))
Org Jira Integration
  ;; configure org-jira


  (use-package org-jira
    :ensure t
    :defer 10
    :config 
    (make-directory "~/.org-jira" 'parents)
    (setq jiralib-url (auth-source-pass-get "url" "customer/jira"))
    (jiralib-login
     (auth-source-pass-get  "user" "customer/jira")
     (auth-source-pass-get 'secret "customer/jira")))

Denote

  ;; configure denote
  (use-package denote
    :ensure t
    :config (progn
              (setq denote-directory (file-name-concat (expand-file-name "~") "Dropbox/denote"))
              (setq denote-dired-directories
                    (list denote-directory
                          (expand-file-name "~/Documents/denote"))))
    :hook (dired-mode . denote-dired-mode-in-directories)
    :bind (
           ("<leader> n d" . (lambda () (interactive) (dired denote-directory)))
           ("<leader> n n" . #'denote)
           ("<leader> n N" . #'denote-type)
           ("<leader> n c" . #'denote-link-or-create)
           ("<leader> n t" . #'denote-template)
           ("<leader> n z" . #'denote-signature)
           ("<leader> n l" . #'denote-link)
           ("<leader> n L" . #'denote-find-link)
           ("<leader> n k" . #'denote-keywords-add)
           ("<leader> n b" . #'denote-link-backlink)
           ("<leader> n B" . #'denote-find-backlink)
           ("<leader> n r" . #'denote-rename-file)
           ("<leader> n R" . #'denote-rename-file-using-front-matter)
           ("<leader> n f" . (lambda () (interactive) (consult-find denote-directory)))))
TODO explain what denote-dired-mode-in-directories does.

Programming

Programming Support Infrastructure

Integration with LSP Servers for language support
  ;; configure eglot-mode
  (use-package eglot
    :config
    (evil-define-key 'normal eglot-mode-map
      (kbd "<leader>c a") 'eglot-code-actions
      (kbd "<leader>c d") 'eglot-find-declaration
      (kbd "<leader>c i") 'eglot-find-implementation
      (kbd "<leader>c k") 'eglot-find-typeDefinition
      (kbd "<leader>c f") 'eglot-format
      (kbd "<leader>c F") 'eglot-format-buffer
      (kbd "<leader>c r") 'eglot-rename
      (kbd "<leader>c Q") 'eglot-shutdown
      (kbd "<leader>c q") 'eglot-reconnect
      (kbd "<leader>c n") 'flymake-goto-next-error
      (kbd "<leader>c p") 'flymake-goto-prev-error)

    ;; Shutdown server when last managed buffer is killed
    (setq eglot-autoshutdown t)

    ;; from https://www.reddit.com/r/emacs/comments/ye18nd/setting_up_eglot_for_python/
    (add-to-list 'eglot-server-programs '(python-mode . ("pylsp")))

    (setq-default eglot-workspace-configuration
                '((:pylsp . (:configurationSources ["flake8"]
                             :plugins (:pycodestyle (:enabled nil)
                                       :mccabe (:enabled nil)
                                       :flake8 (:enabled t))))))

    :hook
    (go-ts-mode . eglot-ensure)
    (rust-ts-mode . eglot-ensure)
    (java-ts-mode . eglot-ensure)
    (python-ts-mode . eglot-ensure)
    )
Use Treesitter parser support
  ;; set locations for treesitter grammars
  (use-package treesit
    :config
    (setq treesit-language-source-alist
          '((bash "https://github.com/tree-sitter/tree-sitter-bash")
            (cmake "https://github.com/uyha/tree-sitter-cmake")
            (css "https://github.com/tree-sitter/tree-sitter-css")
            (elisp "https://github.com/Wilfred/tree-sitter-elisp")
            (go "https://github.com/tree-sitter/tree-sitter-go")
            (gomod "https://github.com/camdencheek/tree-sitter-go-mod.git")
            (haskell "https://github.com/tree-sitter/tree-sitter-haskell" "master" "src" nil nil)
            (html "https://github.com/tree-sitter/tree-sitter-html")
            (java "https://github.com/tree-sitter/tree-sitter-java.git")
            (javascript "https://github.com/tree-sitter/tree-sitter-javascript" "master" "src")
            (json "https://github.com/tree-sitter/tree-sitter-json")
            (lua "https://github.com/tjdevries/tree-sitter-lua")
            (make "https://github.com/alemuller/tree-sitter-make")
            (markdown "https://github.com/ikatyang/tree-sitter-markdown")
            (ocaml "https://github.com/tree-sitter/tree-sitter-ocaml" nil "ocaml/src")
            (python "https://github.com/tree-sitter/tree-sitter-python")
            (toml "https://github.com/tree-sitter/tree-sitter-toml")
            (tsx "https://github.com/tree-sitter/tree-sitter-typescript" "master" "tsx/src")
            (typescript "https://github.com/tree-sitter/tree-sitter-typescript" "master" "typescript/src")
            (yaml "https://github.com/ikatyang/tree-sitter-yaml")))
    :hook
    ((prog . treesit-inspect-mode)))
    
  (use-package treesit-auto
    :ensure t
    :defer 5
    :custom
    (treesit-auto-install 'prompt)
    :config
    (treesit-auto-add-to-auto-mode-alist 'all)
    (global-treesit-auto-mode))
Recompiling all Treesitter Grammars

To recompile all treesitter grammars, execute following block with C-c C-c.

  (mapc #'treesit-install-language-grammar (mapcar #'car treesit-language-source-alist))
bash cmake css elisp go gomod haskell html java javascript json lua make markdown ocaml python toml tsx typescript yaml
Treesitter Grammars for Windows

Windows does not come with a handy-dandy C-compiler available as cc or gcc or even c99 and treesitter does not have a handy dandy feature to remap that to zig cc and similar.

However we can download it from the release page of the tree-sitter-langs repo or even better install the tree-sitter-langs package.

This will download the dynamic library to _user-emacs-directory_/elpa/tree-sitter-langs/bin

However they'll have the wrong filename and are not in the path where treesitter looks in.

  • Open the folder with dired
  • switch to writable with `C-xC-q` to enable wdired
  • :%s/[a-z-]\*.dll/libtree-sitter-\\0/
  • `C-cC-c` to confirm the changes if it looks ok
  • `%m` to mark all files starting with libtree
  • `R` them to the ~/.config/emacs/tree-sitter folder

and they will now be installed.

Normally that should work for other OSes too, but there is no real need for it.

see also How to get started with tree sitter .

Create a folder to store downloaded LSP servers
  ;; LSP support
  (let ((lsp_dir (file-name-concat user-emacs-directory "lsp")))
    (if (not (file-exists-p lsp_dir))
        (mkdir lsp_dir t)))

Configure Selected Languages

Rust Support
  ;; configure rust support
  (let* ((release-date "2023-10-30")
         (os (pcase system-type
               ('darwin "x86_64-apple-darwin")
               ('gnu/linux "x86_64-unknown-linux-gnu")
               ('windows-nt "x86_64-pc-windows-msvc")
               (_ "unknown")))
         (releases-url "https://github.com/rust-lang/rust-analyzer/releases/download/")
         (download-url (concat releases-url release-date "/rust-analyzer-" os ".gz"))
         (rust-analyzer
          (file-name-concat
           user-emacs-directory
           (concat "lsp/rust-analyzer" (if (eq system-type 'windows-nt) ".exe" "")))))
    (if (not (file-exists-p rust-analyzer))
        (let ((rust-analyzer-archive (concat rust-analyzer ".gz" )))
          (message "install rust-analyzer from %s at %s" download-url rust-analyzer)
          (url-copy-file download-url rust-analyzer-archive t)
          (call-process "gzip" nil "*snam-install*" t "-d" (concat rust-analyzer ".gz"))
          (call-process "chmod" nil "*snam-install*" t "+x" rust-analyzer)
          (message "rust-analyzer installed at %s" rust-analyzer)))
    (with-eval-after-load 'eglot
      (add-to-list 'eglot-server-programs `((rust-mode) rust-analyzer))))

  (use-package rustic
    :ensure t
    :init
    (setq rustic-lsp-client 'eglot))
OCaml Support
  ;; configure Ocaml support
  (setq opam-emacs-dir (file-name-concat (expand-file-name "~") "/.opam/default/share/emacs/site-lisp"))
  (use-package ocp-indent
    :ensure t
    :load-path opam-emacs-dir
    :if (file-exists-p opam-emacs-dir))
  (use-package tuareg
    :ensure t
    :hook
    (tuareg-mode . eglot-ensure))
Go Support
        ;; configure go support
        (use-package go-ts-mode
          :init
          (add-to-list 'auto-mode-alist '("\\.go\\'" . go-ts-mode))
          :hook
          (go-ts-mode . eglot-ensure))
Javascript, Typescript, TSC, etc…
        ;; configure typescript support
        (use-package typescript-ts-mode
          :init
          (add-to-list 'auto-mode-alist '("\\.ts\\'" . typescript-ts-mode))
          :hook
          (typescript-ts-mode . eglot-ensure))

        ;; configure javascript support
        (use-package javascript-ts-mode
          :init
          (add-to-list 'auto-mode-alist '("\\.js\\'" . js-ts-mode))
          :hook
          (javascript-ts-mode . eglot-ensure))

        ;; configure react support
        (use-package tsx-ts-mode
          :init
          (add-to-list 'auto-mode-alist '("\\.[jt]sx\\'" . tsx-ts-mode))
          :hook
          (tsx-ts-mode . eglot-ensure))
Java and other JVM languages
        ;; configure java support
        (use-package java-ts-mode
          :hook
          (javascript-ts-mode . eglot-ensure)
          :config
          (let* ((download-url"https://www.eclipse.org/downloads/download.php?file=/jdtls/snapshots/jdt-language-server-latest.tar.gz")
                 (jdtls-dir (file-name-concat user-emacs-directory "lsp/jdtls"))
                 (jdtls-prog (concat jdtls-dir "/bin/jdtls" (if (eq system-type 'windows-nt) ".bat" "")))
                 (archive (file-name-concat jdtls-dir "jdtls.tgz")))
            (if (not (file-exists-p jdtls-dir))
                (mkdir jdtls-dir t))
            (if (not (file-exists-p jdtls-prog))
                (progn
                  (message "install jdtls at %s" jdtls-dir)
                  (if (not (file-exists-p archive))
                      (url-copy-file download-url archive))
                  (call-process "tar" nil "*snam-install*" t "-C" jdtls-dir  "-xzvf" archive )
                  (message "jdtls installed at %s" jdtls-prog)))
            (with-eval-after-load 'eglot
              (progn
                (setenv "JAVA_HOME" (getenv "GUIX_PROFILE"))
                (add-to-list 'eglot-server-programs `((java-mode java-ts-mode) ,jdtls-prog))))))
Lisp and Scheme Support
Enable ParEdit in lispy modes
  ;; Lisp support
  (use-package paredit
    :ensure nil
    :commands (enable-paredit-mode evil-paredit-mode)
    :init
  (dolist (mode '(emacs-lisp-mode-hook
                  lisp-interaction-mode-hook
                  lisp-mode-hook
                  scheme-mode-hook))
    (add-hook mode #'enable-paredit-mode)))
  (use-package evil-paredit
    :ensure t
    :commands (evil-paredit-mode)
    :init
    (dolist (mode '(emacs-lisp-mode-hook
                  lisp-interaction-mode-hook
                  lisp-mode-hook
                  scheme-mode-hook))
    (add-hook mode #'evil-paredit-mode)))
Enable Geiser Mode in Scheme Mode

Configure Geiser and Scheme

  • map .scm file by default to Guile
  • configure chicken scheme interpreter and compiler
  (use-package geiser
    :ensure nil
    :defer t
    :commands (geiser-mode)
    :config
    ;; chicken-install -s srfi-18 apropos chicken-doc
    (setq-default geiser-chicken-binary "csi")
    (setq-default flycheck-scheme-chicken-executable "csc")
    ;; configure geiser to assume guile for .scm files
    (setq geiser-implementation-alist
          ,(add-to-list 'geiser-implementation-alist '((regexp "\\.scm\\'") guile))))
  (use-package scheme-mode
    :ensure nil
    :defer t
    :commands (scheme-mode)
    :hook (scheme-mode . geiser-mode))
Terraform Support
  ;; configure terraform support
  (use-package terraform-mode
    :ensure t
    :config
    (setq
     terraform-indent-level 2
     terraform-format-on-save t)
    (evil-define-key 'normal terraform-mode-map
      (kbd "<leader>c k") #'terraform-open-doc
      (kbd "<leader>c f") #'terraform-format
      (kbd "<leader>c F") #'terraform-format-buffer
      (kbd "<leader>c n") 'flymake-goto-next-error
      (kbd "<leader>c p") 'flymake-goto-prev-error)
    :hook
    (terraform-mode . (lambda () (outline-minor-mode 1)))
    :bind)
pti-ide-config

Debugger Support

        ;; install DAP servers
        (setq snam-vscode-js-debug-dir (file-name-concat user-emacs-directory "dape/vscode-js-debug"))
        (defun snam-install-vscode-js-debug ()
          "Run installation procedure to install JS debugging support."
          (interactive)
          (mkdir snam-vscode-js-debug-dir t)
          (let ((default-directory (expand-file-name snam-vscode-js-debug-dir)))

            (vc-git-clone "https://github.com/microsoft/vscode-js-debug.git" "." nil)
            (message "git repository created")
            (call-process "npm" nil "*snam-install*" t "install")
            (message "npm dependencies installed")
            (call-process "npx" nil "*snam-install*" t "gulp" "dapDebugServer")
            (message "vscode-js-debug installed")))

        (setq snam-codelldb-dir (file-name-concat user-emacs-directory "dape/codelldb"))
        (defun snam-install-codelldb ()
          "Install Vadimcn.Vscode-Lldb DAP server for C/C++/RUST."
          (interactive)
          (let* ((default-directory snam-codelldb-dir)
                 (arch (car (split-string system-configuration "-" nil nil)))
                 (os (pcase system-type
                       ('windows-nt "windows")
                       ('gnu/linux "linux")
                       ('darwin "darwin")
                       (_ "unknown")))
                 (version "1.10.0")
                 (release-url (concat "https://github.com/vadimcn/codelldb/releases/download/v" version "/codelldb-" arch "-" os ".vsix")))
            (mkdir default-directory t)
            (url-copy-file release-url "codelldb.zip" t)
            (message "codelldb archive downloaded")
            (call-process "unzip" nil "*snam-install*" t "codelldb.zip")
            (message "codelldb installed")
            ))

        ;; configure dape (dap-mode)
        (use-package dape
          :ensure (dape :host github :repo "svaante/dape" :wait t)
          :config (progn
                    ;; Use n for next etc. in REPL
                    ;; (setq dape-repl-use-shorthand t)

                    ;; By default dape uses gdb keybinding prefix
                    ;; (setq dape-key-prefix "<space>d")
                    (evil-define-key 'normal 'global (kbd "<leader>d") dape-global-map)

                    ;; Kill compile buffer on build success
                    ;; (add-hook 'dape-compile-compile-hooks 'kill-buffer)

                    ;; Projectile users
                    ;; (setq dape-cwd-fn 'projectile-project-root))
                    (add-to-list 'dape-configs
                                 `(vscode-js-node
                                   modes (js-mode js-ts-mode typescript-mode typescript-ts-mode)
                                   host "localhost"
                                   port 8123
                                   command "node"
                                   command-cwd ,(file-name-concat snam-vscode-js-debug-dir "dist")
                                   command-args ("src/dapDebugServer.js" "8123")
                                   :type "pwa-node"
                                   :request "launch"
                                   :cwd dape-cwd-fn
                                   :program dape-find-file-buffer-default
                                   :outputCapture "console"
                                   :sourceMapRenames t
                                   :pauseForSourceMap nil
                                   :enableContentValidation t
                                   :autoAttachChildProcesses t
                                   :console "internalConsole"
                                   :killBehavior "forceful"))
                    (add-to-list 'dape-configs
                                 `(delve
                                   modes (go-mode go-ts-mode)
                                   command "dlv"
                                   command-args ("dap" "--listen" "127.0.0.1:55878")
                                   command-cwd dape-cwd-fn
                                   host "127.0.0.1"
                                   port 55878
                                   :type "debug"       ;; needed to set the adapterID correctly as a string type
                                   :request "launch"
                                   :cwd dape-cwd-fn
                                   :program dape-cwd-fn))
                    (add-to-list 'dape-configs
                                 `(codelldb
                                   modes (c-mode c-ts-mode
                                                 c++-mode c++-ts-mode
                                                 rust-ts-mode rust-mode)
                                   ;; Replace vadimcn.vscode-lldb with the vsix directory you just extracted
                                   command ,(expand-file-name
                                             (file-name-concat
                                              snam-codelldb-dir
                                              (concat "extension/adapter/codelldb"
                                                      (if (eq system-type 'windows-nt)
                                                          ".exe"
                                                        ""))))
                                   host "localhost"
                                   port 5818
                                   command-args ("--port" "5818")
                                   :type "lldb"
                                   :request "launch"
                                   :cwd dape-cwd-fn
                                   :program dape-find-file))
                    (add-to-list 'dape-configs
                                 `(debugpy
                                   modes (python-ts-mode python-mode)
                                   command "python"
                                   command-args ("-m" "debugpy.adapter")
                                   :type "executable"
                                   :request "launch"
                                   :cwd dape-cwd-fn
                                   :program dape-find-file-buffer-default))

                    ))

Copilot Support

  (use-package copilot
    :ensure (:host github :repo "zerolfx/copilot.el"
                   :branch "main"
                   :files ("dist" "*.el"))
    :hook
    (prog-mode . copilot-mode))
[nil 26279 35677 893347 nil elpaca-process-queues nil nil 951000 nil]
TODO move scheme configuration to the scheme section

or leave it here but move it to config, whatever makes most sense at the moment.

Flymake Error Diagnostics

  (evil-define-key 'normal #'eglot-mode-map
    (kbd "]d") #'flymake-goto-next-error
    (kbd "[d") #'flymake-goto-prev-error
    )

Electric Return

  ;; enable electric return to automatically indent code
  (defvar electrify-return-match
    "[\]}\)\"]"
    "The text after the cursor to do an \"electric\" return.")

  (defun electrify-return-if-match (arg)
    "Insert a newline, and indent it when needed.

    If the text after the cursor matches `electrify-return-match' then
    open and indent an empty line between the cursor and the text.  Move the
    cursor to the new line.

    With a prefix argument ARG, insert that many newlines"
    (interactive "P")
    (let ((case-fold-search nil))
      (if (looking-at electrify-return-match)
          (save-excursion (newline-and-indent)))
      (newline arg)
      (indent-according-to-mode)))

  ;; Using local-set-key in a mode-hook is a better idea.
                                          ;(global-set-key (kbd "RET") 'electrify-return-if-match)
TODO Evaluate if Electric Return is still useful

Communication and Interwebs

ERC configuration

Set up ERC, an IRC client for Emacs, to automatically join specific channels and log in:

  (use-package erc
    :ensure t
    :defer 5
    :config
    (erc-services-mode 1)
    (erc-autojoin-mode 1)
    (erc-update-modules)
    (setq erc-autojoin-channels-alist
          '(('znc
             "#emacs" "#erc" "#spritely" "#guix"
             "#systemcrafters" "#systemcrafters-guix" "#systemcrafters-emacs"))))
t

Setup all channels which are joined by default.

ERC connect function

Define a function to login to ERC when needed. In principle ERC reconnects after suspend, and sometimes it even works, but mostly I run this function again to reconnect, which will update the buffers with the rooms I joined.

  (defun snam-erc ()
    "Login to znc bouncer and join rooms."
    (interactive)
    (erc-tls :id 'znc
             :server (auth-source-pass-get "server" "snamellit/znc")
             :port (auth-source-pass-get "port" "snamellit/znc")
             :user (auth-source-pass-get "user" "snamellit/znc")
             :nick (auth-source-pass-get "nick" "snamellit/znc")
             :password (auth-source-pass-get 'secret "snamellit/znc")))
snam-erc

In practice this has proven to be a very useful function to reconnect in all kind of weird situation.

TODO Figure out how to make ERC reconnect more reliably

Although running `snam-erc` is not a big deal, it should not be needed so much. I should figure out in what cases reconnects fail or dropping connections fail to be recognized. I often see that ERC is reconnecting however this seems to silently fail. I dunno, there is something fishy here…

Integrate ERC with i3 Desktops

I like to have my IRC channels on workspace 5 in i3.

  (defun my-erc ()
    "Create new frame and move to ws5 and launch erc."
    (interactive)
    (let ((frame (make-frame))
          (channel "#systemcrafters"))
      (call-process "i3" nil nil nil "move window to workspace 5")
      (snam-erc)
      (call-process "i3" nil nil nil "workspace 5")
      (message "Waiting to connect to IRC...")
      (dotimes (i 12)
        (unless (member channel (mapcar 'buffer-name (erc-channel-list nil)))
          (sleep-for 0.25)))
      (set-window-buffer
       (frame-selected-window frame) channel)))
my-erc

Elfeed configuration

Configures Elfeed, an RSS feed reader for Emacs:

  ;; configure elfeed
  (use-package elfeed
    :ensure t
    :defer 5
    :config
    (setq elfeed-feeds
          '("https://www.spritelyproject.org/feed.xml"
            "https://www.emacswiki.org/emacs?action=rss"
            ;; "https://www.reddit.com/r/emacs.rss"
            ;; "https://www.reddit.com/r/guix.rss"
            ;; "https://www.reddit.com/r/linux.rss"
            "https://sachachua.com/blog/feed/"
            "https://blog.benoitj.ca/posts/index.xml"
            "https://tylerdback.com/index.xml"
            "https://www.snamellit.com/rss.xml"
            "https://richarddavis.xyz/en/blog/rss.xml"
            "https://protesilaos.com/master.xml"
            )))
[nil 26279 35504 577569 nil elpaca-process-queues nil nil 161000 nil]

Enable 0x0 to share snippets and files easily

Configures the 0x0 package for easily sharing code snippets and files:

  ;; configure 0x0
  (use-package 0x0
    :ensure t
    :config (setq 0x0-use-curl t))
[nil 26279 35497 183387 nil elpaca-process-queues nil nil 963000 nil]

The commands this package offers:

  • 0x0-dwim chooses the right one of the following, based on location or previous commands.
  • 0x0-upload-text will upload the active region or if not existent the entire buffer
  • 0x0-upload-file will upload a selected file
  • 0x0-upload-kill-ring uploads the content of the kill ring
  • 0x0-popup creates and displays a buffer, lets you upload its contents later
  • 0x0-shorten-uri prompts for a uri, shortens it IMSMR the shorten-uri functionality is disabled in the 0x0 service as apparently is was abused too much.

Finishing Touches

Tell the module system that init.el is available

Indicates the init.el file is provided by and ends here:

  (provide 'init)
  ;;; init.el ends here