Skip to content

Latest commit

 

History

History
945 lines (766 loc) · 29.5 KB

File metadata and controls

945 lines (766 loc) · 29.5 KB

My Emacs literate configuration

For the longest time I wanted to try the Org + Babel mode of writing my configuration file, and 2024 seemed like a good year to start doing that.

Preface

All Emacs files require some comments that identify what the file is for and all that crap.

;;; -*- lexical-binding: t; -*-
;;; init.el --- My Emacs configuration

;;; Commentary:

;;; Code:

Early init

This file is read by Emacs before anything else. It shouldn’t depend on any package or frame configurations. Used mostly for basic initializations.

;; Set some sane defaults
(setopt ring-bell-function 'ignore ; visual bell
        visible-bell t
        frame-title-format '("%b")
        use-dialog-box t
        use-file-dialog nil
        use-short-answers t ; no more yes-or-no
        x-stretch-cursor t
        require-final-newline t
        inhibit-splash-screen t
        inhibit-startup-screen t
        inhibit-x-resources t
        inhibit-startup-echo-area-message user-login-name
        inhibit-startup-buffer-menu t)

;; Indentation
(setopt standard-indent 2
        tab-always-indent 'complete)
(setq-default tab-width 4)
(setq-default indent-tabs-mode nil)

;; I don't want some UI elements enabled, so let's disable them.
(when (display-graphic-p)
  (tool-bar-mode -1)
  (scroll-bar-mode -1)
  (global-hl-line-mode -1))

(delete-selection-mode t)

(global-auto-revert-mode 1) ; reload files when changed

We don’t want Emacs performing too many GC runs on startup, to make it boot as fast as possible.

(setopt gc-cons-threshold most-positive-fixnum
        gc-cons-percentage 0.5)

(defvar inkel/emacs--file-name-handler-alist file-name-handler-alist)
(defvar inkel/emacs--vc-handled-backends vc-handled-backends)

(setopt file-name-handler-alist nil
        vc-handled-backends inkel/emacs--vc-handled-backends)

(defun inkel/emacs--startup-hook ()
  "Restore defaults and report initialization time and GC collections."
  (setopt gc-cons-threshold (* 1000 1000 8)
          gc-cons-percentage 0.1
          file-name-handler-alist inkel/emacs--file-name-handler-alist
          vc-handled-backends inkel/emacs--vc-handled-backends)
  (message "Emacs ready in %.2fs with %d garbage collections."
           (float-time (time-subtract after-init-time before-init-time))
           gcs-done))

(add-hook 'emacs-startup-hook #'inkel/emacs--startup-hook)

;; force GC run
(add-hook 'after-init-hook #'garbage-collect t)

I don’t want my installed packages to be initialized automatically, as sometimes I install packages just to test them.

(setopt package-enable-at-startup nil)

The Go team released nice fonts which are the ones I’ve been using for a long time:

(condition-case nil
  (set-face-attribute 'default nil :family "Go Mono" :height 140))

I want my Emacs to be maximized, and that upon restart it doesn’t create a new frame (maybe this was a bug?)

;; Start Emacs in full screen mode
(add-hook 'window-setup-hook 'toggle-frame-fullscreen t)
(advice-add 'restart-emacs :before 'toggle-frame-fullscreen)

This will make some things below easier

(eval-and-compile
  (defsubst emacs-path (path)
    (expand-file-name path user-emacs-directory)))

(defconst user-data-directory (emacs-path "data"))

(defun user-data (dir)
  "Expands DIR filename within `user-data-directory'."
  (expand-file-name dir user-data-directory))

Some binaries are installed outside the typical PATH locations, let’s use the one that we have in ~/.bashrc:

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

Init proper

No custom file

;; Disable the damn thing by making it disposable.
(setopt custom-file (make-temp-file "emacs-custom-"))

No backups

(setopt make-backup-files nil
        backup-inhibited nil
        create-lockfiles nil)

Be silent with native compilation

;; Make native compilation silent and prune its cache.
(when (native-comp-available-p)
  (setq-default native-comp-async-report-warnings-errors 'silent
                native-compile-prune-cache t))

Focus on the help buffer automatically

(setopt help-window-select t)

Keep track of opened files and placements

(use-package savehist
  :unless noninteractive
  :custom
  (savehist-additional-variables '(file-name-history
				     kmacro-ring
				     compile-history
				     compile-command))
  (savehist-ignored-variables '(load-history
				  flyspell-auto-correct-ring
				  org-roam-node-history
				  magit-revision-history
				  org-read-date-history
				  query-replace-history
				  yes-or-no-p-history
				  kill-ring))
  (savehist-autosave-interval 60)
  (savehist-file (user-data "history"))
  (savehist-mode t)
  :config
  (savehist-mode 1))

(use-package saveplace
  :unless noninteractive
  :custom
  (save-place-file (user-data "places"))
  :config
  (save-place-mode 1))

Better buffer selection

(use-package ibuffer
  :bind (("C-x C-b" . ibuffer))
  :config
  (setopt ibuffer-default-sorting-mode 'filename/process))

Where to display buffers

;;; https://protesilaos.com/codelog/2024-02-08-emacs-window-rules-display-buffer-alist/
(setopt display-buffer-alist
        '(

          ("\\*xref\\*"
           (display-buffer-reuse-mode-window display-buffer-below-selected)
           (dedicated . t)
           (window-height . fit-window-to-buffer))

          ))

Packages

Configuring package.el and use-package

(require 'package)

(when (not (package-installed-p 'use-package))
  (package-install 'use-package))

(defvar use-package-enable-imenu-support t)
(require 'use-package)

(add-hook 'package-menu-mode-hook #'hl-line-mode)

(setopt package-archives '(("gnu"   . "https://elpa.gnu.org/packages/")
                           ("org"   . "https://orgmode.org/elpa/")
                           ("melpa" . "https://melpa.org/packages/"))
        use-package-always-ensure t
        use-package-always-defer nil
        use-package-verbose init-file-debug
        use-package-expand-minimally (not init-file-debug)
        debug-on-error init-file-debug)

(setopt package-archive-priorities
        '(("melpa" . 3)
          ("org" . 2)
          ("gnu" . 1)))

(defvar use-package-enable-imenu-support t)

Undoing is important

(use-package vundo)

Minibuffer

Vertico
(use-package vertico
  :ensure t
  :config
  (setopt vertico-cycle t
          vertico-resize nil)
  (vertico-mode 1))
Marginalia
(use-package marginalia
  :ensure t
  :config
  (marginalia-mode 1))

Orderless

(use-package orderless
  :ensure t
  :config
  (setopt completion-styles '(orderless basic)))

Consult

Not really minibuffer only, but great addition.

(use-package consult
  :hook (completion-list-mode . consult-preview-at-point-mode)

  :bind (("M-y" . consult-yank-pop) ;; Better yank?
         ;; M-g bindings in `goto-map'
          ("M-g g" . consult-goto-line)
         ;; ("M-g M-g" . consult-goto-line)
         ("M-s M-b" . consult-buffer)
         ("M-s M-f" . consult-find)
         ("M-g o" . consult-outline)
         ("M-g i" . consult-imenu)
         ("M-g I" . consult-imenu-multi)))

;; This is useful for navigating through xref history
;; https://takeonrules.com/2024/06/08/adding-a-consult-function-for-visualizing-xref/
(defvar consult--xref-history nil
  "History for the `consult-recent-xref' results.")

(defun consult-recent-xref (&optional markers)
  "Jump to a marker in MARKERS list (defaults to `xref--history'.

The command supports preview of the currently selected marker position.
The symbol at point is added to the future history."
  (interactive)
  (consult--read
    (consult--global-mark-candidates
      (or markers (flatten-list xref--history)))
    :prompt "Go to Xref: "
    :annotate (consult--line-prefix)
    :category 'consult-location
    :sort nil
    :require-match t
    :lookup #'consult--lookup-location
    :history '(:input consult--xref-history)
    :add-history (thing-at-point 'symbol)
    :state (consult--jump-state)))

Completion

Corfu is a great package enhancing in-buffer completion in an interactive way.

(use-package corfu
  :custom
  (corfu-cycle t)
  (corfu-auto t)
  (corfu-auto-prefix 2)
  (corfu-auto-delay 0.5)
  (corfu-separator ?\s)
  (corfu-quit-at-boundary 'separator)
  (corfu-echo-documentation 0.25)
  (corfu-preview-current 'insert)
  (corfu-preselect-first nil)
  :commands
  (global-corfu-mode corfu-history-mode)
  :init
  (global-corfu-mode)
  (corfu-history-mode))

Keybindings

Which key

;; Glorious package to let you know what binding are available
(use-package which-key
  :ensure t
  :defer nil
  :diminish which-key-mode
  :hook (after-init . which-key-mode))

Set some useful keybindings

(keymap-set global-map "C-z" nil)
(keymap-set global-map "s-q" nil)
(keymap-set global-map "s-p" nil)
(keymap-set global-map "C-x C-p" #'find-file-at-point)

(keymap-unset global-map "s-n")

Navigation & movement

Moving between windows is easier with ace-window:

(use-package ace-window
  :ensure t
  :bind ("M-o" . ace-window))
(use-package imenu
  :bind ("s-i" . imenu))

And we want to find things using the Silver Seacher:

(use-package ag)

Project management

(use-package project)

Theme

;; Doom-themes
(use-package doom-themes
  :ensure t
  :custom
  (doom-themes-enable-bold t)
  (doom-themes-enable-italic t)

  :commands (doom-themes-visual-bell-config
             doom-themes-org-config)

  :config
  ;; Enable flashing mode-line on errors
  (doom-themes-visual-bell-config)

  ;; Corrects (and improves) org-mode's native fontification.
  (doom-themes-org-config))

(load-theme 'doom-solarized-light t)

;; Losely inspired by http://github.com/LionyxML/auto-dark-emacs
(setopt inkel/current-theme 'light)
(defun inkel/toggle-theme ()
  "Set the custom theme."
  (interactive)
  (cond ((eq inkel/current-theme 'light)
         (setopt inkel/current-theme 'dark)
         (load-theme 'doom-ayu-mirage t))
        ((eq inkel/current-theme 'dark)
         (setopt inkel/current-theme 'light)
         (load-theme 'doom-solarized-light t))))

Editing

I like editing with knowing which line number am I in. I also would like to know the column number in the modeline.

(use-package display-line-numbers
  :config (global-display-line-numbers-mode))
(setopt column-number-mode t)

Having trailing whitespace is bad, really bad. Let’s clear those pesky trailing whitespaces when saving.

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

Crux

;; Use crux for some useful things, like C-a moving to the first
;; character of the line instead of the beggining of the line!.
;; https://github.com/bbatsov/crux
;; http://pragmaticemacs.com/emacs/open-files-with-the-system-default-application/
;; http://pragmaticemacs.com/emacs/move-to-the-beginning-of-a-line-the-smart-way/
(use-package crux
  :ensure t
  :bind (("C-c M-o" . crux-open-with)
         ("C-k" . crux-kill-and-join-forward)
         ("C-x 4 t" . crux-transpose-windows)
         ("C-x D" . crux-delete-file-and-buffer)
         ("C-x M-r" . crux-rename-file-and-buffer)
         ("s-k" . crux-kill-whole-line)
         ("C-a" . crux-move-beginning-of-line)))

Very useful to edit multiple occurrences

(use-package multiple-cursors
  :bind (("C-S-c C-S-c" . mc/edit-lines)
	   ("C->" . mc/mark-next-like-this)
	   ("C-S->" . mc/unmark-next-like-this)
	   ("C-<" . mc/mark-previous-like-this)
	   ("C-S-<" . mc/unmark-previous-like-this)
	   ("C-c C->" . mc/mark-all-like-this)
	   ("C-S-<mouse-1>" . mc/add-cursor-on-click)))

Completions

These are useful and simple to use, so let’s use them!

(use-package yasnippet
  :hook
  (prog-mode . yas-minor-mode)
  :defines (yas-snippet-dirs)
  :commands (yas-reload-all)
  :bind
  (("C-c y n" . yas-new-snippet)
   ("C-c y v" . yas-visit-snippet-file)
   ("C-c y i" . yas-insert-snippet))
  :config
  (yas-reload-all)
  (setopt yas-snippet-dirs
          '("~/.emacs.d/snippets")))

Magit

At Grafana Labs we have a nice script to cleanup merged branches when using squash commits for merging (yuck)

#!/usr/bin/env bash
# set -x

BRANCH=$(git symbolic-ref --short HEAD)

PRIMARY_BRANCH='master'
if [ -n "$(git branch -l main)" ]; then
  PRIMARY_BRANCH=main
fi

XARGS_FLAGS="-r"

if [[ $(uname -s) = "Darwin" ]]; then XARGS_FLAGS=""; fi

if [ "$BRANCH" != "$PRIMARY_BRANCH" ]; then
  git checkout "$PRIMARY_BRANCH"
fi

git pull
git remote | xargs -n1 git remote prune
git branch --merged | grep -v $PRIMARY_BRANCH | grep -v '^[*+]' | xargs $XARGS_FLAGS git branch -d
git for-each-ref refs/heads/ "--format=%(refname:short)" | while read branch; do mergeBase=$(git merge-base $PRIMARY_BRANCH $branch) && [[ $(git cherry $PRIMARY_BRANCH $(git commit-tree $(git rev-parse $branch\^{tree}) -p $mergeBase -m _)) == "-"* ]] && git branch -D $branch; done

if [ "$BRANCH" != "$PRIMARY_BRANCH" -a -n "$(git branch -l $BRANCH)" ]; then
  git checkout "$BRANCH"
fi
(defun inkel/magit--log-edit-mode-hook ()
  "Editing options for writing commit messages in Magit."
  (setopt fill-column 72)
  (flyspell-mode t)
  (turn-on-auto-fill))

(defun inkel/magit-cleanup ()
  "Run git cleanup"
  (interactive)
  (magit-run-git-async "cleanup")
  (magit-process-buffer))

(use-package magit
  :bind (("C-x g" . magit-status))

  :custom
  (magit-bind-magit-project-status t)
  (magit-display-buffer-function #'magit-display-buffer-fullframe-status-v1)
  (magit-bury-buffer-function #'magit-restore-window-configuration)

  :config
  ;; Speeding up magit-status
  ;; https://jakemccrary.com/blog/2020/11/14/speeding-up-magit/
  (remove-hook 'magit-status-headers-hook 'magit-insert-tags-header) ;; There are ways to see this when needed, most of the time I don't need it.
  (remove-hook 'magit-status-headers-hook 'magit-insert-diff-filter-header)
  (remove-hook 'magit-status-headers-hook 'magit-insert-upstream-branch-header)
  (magit-add-section-hook 'magit-status-sections-hook
                          'magit-insert-unpushed-to-upstream
                          'magit-insert-unpushed-to-upstream-or-recent
                          'replace) ;; Hide "Recent Commits"

  (transient-append-suffix 'magit-run "p"
    '("C" "Cleanup Git" inkel/magit-cleanup))

  (add-hook 'magit-log-edit-mode-hook 'inkel/magit--log-edit-mode-hook))

;; (use-package magit-file-icons
;;   :ensure t
;;   :after magit
;;   :init
;;   (magit-file-icons-mode 1)
;;   :custom
;;   ;; These are the default values:
;;   (magit-file-icons-enable-diff-file-section-icons t)
;;   (magit-file-icons-enable-untracked-icons t)
;;   (magit-file-icons-enable-diffstat-icons t))

(use-package forge :after magit)

(use-package git-link
  :bind ("C-c g l" . git-link-dispatch)
  :config (require 'git-link-transient))

Programming

Shell scripts

If I write a shell script I want Emacs to make it an executable file if it contains a hash-bang.

(add-hook 'after-save-hook
          'executable-make-buffer-file-executable-if-script-p)

Flymake

Flymake is Emacs native and what Eglot uses.

(global-set-key (kbd "s-n") #'flymake-goto-next-error)
(global-set-key (kbd "s-p") #'flymake-goto-prev-error)
(global-set-key (kbd "M-g f") #'flymake-show-buffer-diagnostics)

(use-package sideline-flymake
  :hook (flymake-mode . sideline-mode)
  :init
  (setopt sideline-flymake-display-mode 'point) ; 'point to show errors only on point
                                        ; 'line to show errors on the current line
  (setopt sideline-backends-right '(sideline-flymake)))

Eglot

(use-package eglot
  :ensure t
  :custom
  (eglot-autoshutdown t)
  :commands (eglot-format-buffer)
  :bind (:map eglot-mode-map
      ("C-c C-d" . eldoc)
      ("C-c C-r" . eglot-rename)
      ("C-c C-a" . eglot-code-actions)
      ("s-i" . imenu)))

Tree-sitter

(defvar treesit-language-source-alist '((elisp "https://github.com/Wilfred/tree-sitter-elisp")
                                        (hcl "https://github.com/tree-sitter-grammars/tree-sitter-hcl")
                                        (go "https://github.com/tree-sitter/tree-sitter-go")
                                        (gomod "https://github.com/camdencheek/tree-sitter-gomod")))

(use-package tree-sitter
  :custom
  (treesit-auto-install-grammar t)
  :config
  (global-tree-sitter-mode)
  (add-hook 'tree-sitter-after-on-hook #'tree-sitter-hl-mode))

(use-package tree-sitter-langs)

One neat feature is code folding, that is, the ability to hide/display regions based on the programming language.

(use-package fringe-helper)

(use-package ts-fold
  :load-path "/Users/inkel/dev/emacs-tree-sitter/ts-fold"

  :bind ("C-c C-f" . ts-fold-toggle)

  :config
  (add-to-list 'ts-fold-range-alist
               (cons 'go-ts-mode (assoc 'go-mode ts-fold-range-alist)))

  (use-package ts-fold-indicators
    :load-path "/Users/inkel/dev/emacs-tree-sitter/ts-fold"

    :config
    (setopt ts-fold-indicators-fringe 'right-fringe))

  ;; enable wherever tree-sitter is enabled
  (global-ts-fold-mode 1)
  )

Go

One thing I do all the time in Go is browsing the documentation of a package in pkg.go.dev, so I made a helper to allow me to quickly browse to the package documentation of any of the imports in the current buffer:

 (defun inkel/go-file-imports (filename)
   "Return a list of the current imports in Go FILENAME.

 The returnes list is sorted by stdlib first and third-party then."
   (cl-flet ((stdlib-p (pkg) (not (string-match-p "\\." pkg))))
     (let ((imports (process-lines "go" "list" "-f" "{{range .Imports}}{{println .}}{{end}}" filename)))
	(seq-concatenate 'list
			 (sort (seq-filter #'stdlib-p imports) #'string-lessp)
			 (sort (seq-remove #'stdlib-p imports) #'string-lessp)))))

 (defun inkel/go-package-docs (&optional package)
   "Open up Go package PACKAGE documentation in pkg.go.dev."
   (interactive (list (let ((packages (inkel/go-file-imports buffer-file-name))
			     (vertico-sort-function nil))
			 (completing-read "Browse pkg.go.dev documentation for: " packages))))
   (browse-url (concat "https://pkg.go.dev/" package)))

In order to have project.el properly understand Go projects we need to define how to identify a Go project. This is done by traversing the current directory and its parents until it finds a go.mod file.

(defun project-find-go-module (dir)
  "Find DIR first parent directory defining a go.mod file."
  (when-let* ((root (locate-dominating-file dir "go.mod")))
    (cons 'go-module root)))

(cl-defmethod project-root ((project (head go-module)))
  "Return root directory of the current Go PROJECT."
  (cdr project))

(add-hook 'project-find-functions #'project-find-go-module)

Running tests is integral to development, and these helpers should make it a bit easier.

(use-package gotest-mode
  :ensure nil
  :bind ("s-t" . gotest-dwim)
  :load-path "~/repos/gotest-mode.el/"
  ; :vc (:url "https://github.com/inkel/gotest-mode.el" :rev :newest)
  :hook (go-ts-mode . gotest-mode))

Because we are using Tree-sitter we have to use the go-ts-mode package. We also add some hooks to organize the imports and format the buffer every time we save.

(defun inkel/eglot--go-organize-imports-on-save ()
  "Hook for organizing Go imports when saving buffer."
  (call-interactively 'eglot-code-action-organize-imports))

(defun inkel/eglot--go-format-buffer-on-save ()
  "Hook for Go projects when saving buffer."
  (add-hook 'before-save-hook #'inkel/eglot--go-organize-imports-on-save)
  (add-hook 'before-save-hook #'eglot-format-buffer -10 t))

(use-package go-ts-mode
  :ensure t
  :custom (go-ts-mode-indent-offset tab-width)
  :mode "\\.go"
  :bind (:map go-ts-mode-map
              ;("C-c C-t" . inkel/go-test)
              ("C-c M-p" . inkel/go-package-docs))
  :hook ((go-ts-mode . eglot-ensure)
         (go-ts-mode . inkel/eglot--go-format-buffer-on-save))
  :config
  (add-to-list 'tree-sitter-major-mode-language-alist '(go-ts-mode . go))
  (setq-default eglot-workspace-configuration
                '((:gopls . ((staticcheck . t)
                             (matcher . "CaseSensitive")))))
  (add-to-list 'major-mode-remap-alist '(go-mode . go-ts-mode)))
Debugging with Delve
(use-package go-dlv)

YAML

Yet Another Mistaken Language

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

Docker

(use-package dockerfile-mode)

Org

(defun inkel/org--edit-options ()
  "Enable truncate lines and word wrapping on Org files."
  (toggle-truncate-lines nil)
  (toggle-word-wrap t))

(use-package org
  :pin org
  :ensure t

  :bind (("C-c a" . org-agenda)
         ("C-c c" . org-capture)
         ("C-c b" . org-switchb)
         ("C-c l" . org-store-link))

  :custom
  (org-directory (expand-file-name "~/org"))
  (org-imenu-depth 7)
  ;; Do not indent lines according to node level.
  (org-adapt-indentation nil)
  ;; Do not ask for confirmation when evaluating source blocks.
  (org-babel-config-evaluate nil)
  (org-confirm-babel-evaluate nil)
  ;; do not create a popup when editing a source block
  (org-src-window-setup 'current-window)

  (org-capture-templates '(("t" "TODO" entry (file+headline org-default-nodes-file "Tasks")
                            "* TODO %?\n  %i\n  %a")
                           ("j" "Journal" entry (file+datetree "journal.org")
                            "* %?\nEntered on %U\n  %^g%i")
                           ("o" "On-Call" item (file+datetree "on-call.org")
                            "")
                           ))

  ;; Make org the default mode when starting Emacs
  (initial-major-mode 'org-mode)
  (initial-scratch-message "#\n# Temporary buffer for notes\n#\n")

  :config
  ;; do not create a popup when editing a source block
  ;;(setq org-src-window-setup 'current-window)

  ;; enable inserting structured templates easily
  (add-to-list 'org-modules 'org-tempo)

  (add-hook 'org-mode-hook #'inkel/org--edit-options))

As part of my job I usually write commands that feed onto other commands but I’d like to see the intermediate results or having better options for parsing their output. Following Literate DevOps practices makes my life much easier and nicer. And for this, I need some some languages availabel in Babel.

(use-package org
  :commands (org-edit-src-exit)

  :config
  (org-babel-do-load-languages 'org-babel-load-languages
                               '((emacs-lisp . t)
                                 (awk . t)
                                 (shell . t))))

Editing code blocks is great, but I keep pressing C-x C-s to exit them instead of C-c '; this should fix it:

(eval-after-load 'org-src
  '(define-key org-src-mode-map
     (kbd "C-x C-s") #'org-edit-src-exit))

Last but not list, I interact lots with GitHub, so let’s allow Org to export to GitHub Flavored Markdown:

(use-package ox-gfm
  :ensure t
  :after org)

HCL - Terraform

(use-package terraform-mode
  :config (setopt terraform-format-on-save t)
  :hook ((terraform-mode . eglot-ensure)
         (before-save . terraform-format-buffer)))

Miscellaneous

Presentations, screencasting and so on

I haven’t done any of this yet, but if I ever do (and I would like to), this is a neat package that will show which keys & commands got executed on the mode line:

(use-package keycast
  :config
  (keycast-mode-line-mode -1))

Work

I have some configurations that are mostly only work related. I should probably move this to a module, but for now, let’s have them here.

JSON & Jsonnet

For some things we use Jsonnet, which is an awful language, but what can I do?

(setopt js-indent-level 2)

(add-to-list 'treesit-language-source-alist
             '(json5 "https://github.com/Joakker/tree-sitter-json5"))

(use-package json5-ts-mode
  :mode "\\.json5"
  :custom (json5-ts-mode-indent-offset 2))

(defun inkel/jsonnet-lib ()
  (let
      ((dtroot (locate-dominating-file buffer-file-name "ksonnet")))
    (if dtroot
        (progn
          (setopt jsonnet-library-search-directories
                  (list (expand-file-name "ksonnet/lib" dtroot)))))))

(use-package jsonnet-mode
  :hook (jsonnet-mode . inkel/jsonnet-lib)
  :custom (jsonnet-indent-level 2))

;; Jsonnet LSP
(with-eval-after-load 'eglot
  (add-to-list 'eglot-server-programs
               '(jsonnet-mode . ("jsonnet-language-server" "--tanka"))))

Projects

In order to use project.el I need it to instruct how to identify the project, which should be simply a directory, but because I use Git worktrees extensively, it’s a good idea to tell project.el how to find them automatically:

(defun project-find-dt (dir)
  "Find DIR first parent directory with setup-context."
  (when-let* ((root (locate-dominating-file dir "setup-context")))
    (cons 'gdt-root root)))

(cl-defmethod project-root ((project (head gdt-root)))
  "Return root directory of the current deployment_tools PROJECT."
  (cdr project))

(add-hook 'project-find-functions #'project-find-dt)

AI

Yes, it’s time to accept that AI is not important. At work I’ve been trying Claude Code, so let’s start with it:

;; Claude Code IDE integration with Emacs
(add-to-list 'vc-handled-backends 'Git)

(use-package vterm)

(use-package claude-code-ide
  :ensure t
  :vc (:url "https://github.com/manzaltu/claude-code-ide.el" :rev :newest)
  :bind ("C-c C-'" . claude-code-ide-menu)
  :config
  ; Enable Emacs MCP tools - https://github.com/manzaltu/claude-code-ide.el#emacs-mcp-tools
  (claude-code-ide-emacs-tools-setup))
(defun inkel/claude-find-skill ()
  "Open a Claude skill directory from ~/.claude/skills/ using completion."
  (interactive)
  (let* ((skills-dir (expand-file-name "~/.claude/skills/"))
         (skills (when (file-directory-p skills-dir)
                   (seq-filter
                    (lambda (f)
                      (file-directory-p (expand-file-name f skills-dir)))
                    (directory-files skills-dir nil "^[^.]"))))
         (skill (completing-read "Claude skill: " skills nil t))
         (skill-path (expand-file-name (concat skill "/SKILL.md") skills-dir)))
    (find-file skill-path)))

(keymap-set global-map "C-c K" #'inkel/claude-find-skill)

I might also try the Pi coding agent, so let’s see how it integrates:

(use-package md-ts-mode)

(use-package pi-coding-agent
  :bind ("C-c p" . pi-coding-agent-toggle))

Epilogue

Sometimes I want to open up a file from my terminal directly into my main Emacs frame, let’s start a server for it:

(server-start)

As with the preface, let’s add the final lines.

(provide 'init)

;;; init.el ends here

;; Local Variables: ;; eval: (add-hook ‘after-save-hook ‘org-babel-tangle nil t) ;; End: