emacs configuration #

This is my emacs configuration, available also on my Github account. This page is extracted from the same org source, included in the org roam note with

#+INCLUDE: "~/.emacs.d/init.org"

emacs configuration #

Miscellaneous #

Make things faster, particularly in lsp-mode.

(setq gc-cons-threshold 100000000)
(setq byte-compile-warnings '(cl-functions))

Certain commands only make sense in a GUI.

(if (display-graphic-p)
    (progn
      (tool-bar-mode -1)            ; Disable the toolbar
      (tooltip-mode -1)             ; Disable tooltips
      (menu-bar-mode -1)            ; Disable the menu bar
      (scroll-bar-mode -1)))

Don’t let custom-set-variables dirty the init.el file.

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

Set file backup in a single directory.

(setq backup-directory-alist '(("" . "~/.emacs.d/backup")))

Send usage information to ActivityWatch.

(use-package activity-watch-mode)
(global-activity-watch-mode)

Package system #

Manage packages with use-package.

(require 'package)
(require 'use-package)
(setq package-archives '(("melpa" . "https://melpa.org/packages/")
			     ("elpa"  . "https://elpa.gnu.org/packages/")
			     ("org"   . "http://orgmode.org/elpa/")))
(unless package-archive-contents
  (package-refresh-contents))
(unless (package-installed-p 'use-package)
  (package-install 'use-package))
(setq use-package-always-ensure t)

Fix MacOS path issues with emacs.

(use-package exec-path-from-shell)
(when (memq window-system '(mac ns))
  (setenv "SHELL" "/bin/zsh")
  (exec-path-from-shell-initialize)
  (exec-path-from-shell-copy-envs
   '("PYTHONPATH")))
  • TODO Replace use-package with straight.el

Initial UI #


(defvar clinton/default-font-size 140)
(defvar clinton/default-variable-font-size 140)
(setq inhibit-startup-message t)

(tool-bar-mode -1)            ; Disable the toolbar
(tooltip-mode -1)             ; Disable tooltips
(menu-bar-mode -1)            ; Disable the menu bar
(column-number-mode 1)
(display-time-mode 1)
(display-battery-mode 1)
(global-display-line-numbers-mode t)
;; Disable line numbers for some modes
(dolist (mode '(org-mode-hook
		    org-agenda-mode-hook
		term-mode-hook
		shell-mode-hook
		treemacs-mode-hook
		eshell-mode-hook))
  (add-hook mode (lambda () (display-line-numbers-mode 0))))

;; Set the default face
(set-face-attribute 'default nil :font "Fira Code" :height clinton/default-font-size :weight 'light)
;; Set the fixed pitch face
(set-face-attribute 'fixed-pitch nil :font "Fira Code" :height clinton/default-font-size :weight 'light)
;; Set the variable pitch face
(set-face-attribute 'variable-pitch nil :font "Fira Code" :height clinton/default-variable-font-size)

Keybindings #

(global-set-key (kbd "<escape>") 'keyboard-escape-quit) ;; Make esc quit prompts
(global-set-key (kbd "C-x /") 'comment-region)
(global-unset-key (kbd "C-l"))
(global-unset-key (kbd "C-c h"))
(global-unset-key (kbd "C-x f"))
(global-unset-key (kbd "C-x d"))
(global-set-key (kbd "C-x d") 'dirvish)

 ;;;*** which-key

 (use-package which-key
   :init
   (setq which-key-separator " ")
   (setq which-key-prefix-prefix "+")
   (which-key-mode)
   :diminish which-key-mode
   :config
   (setq which-key-idle-delay 1))

(use-package general
  :config
  (general-create-definer clinton/leader-c
    :prefix "C-c")
  (general-create-definer clinton/leader-l
    :prefix "C-l"))

  (clinton/leader-c
    "a" 'org-agenda
    "b" 'counsel-buffer-or-recentf
    "c" 'org-capture
    "d" 'deft
    "h" 'call-my-script-with-word
    "p" 'projectile-command-map
    "r" 'revert-buffer
    "s" 'org-store-link
    "t" '(lambda () (interactive) (ansi-term "zsh"))
    "w" 'count-words)

  (clinton/leader-l
    "a" 'avy-goto-char-2
    "c" 'helm-make-projectile
    "d" 'xref-find-definitions
    "e" 'flymake-goto-next-error
    "f" 'follow-mode
    "j" 'json-pretty-print
    "k" 'eglot-code-actions
    "o" 'olivetti-mode
    "r" 'xref-find-references
    "s" 'counsel-rg
    "t" '(counsel-load-theme :which-key "choose theme")
    "v" 'valign-table
    "y" 'prettier-js
    "x" 'org-babel-execute-src-block)

   (set-register ?p (cons 'file "~/org/projects.org"))

Appearance #


(use-package doom-themes
  :init (load-theme 'doom-vibrant t))
(use-package all-the-icons)
(use-package dtrt-indent)

(setq dtrt-indent-original-indent 0)
(use-package doom-modeline
  :init (doom-modeline-mode 1)
  :custom ((doom-modeline-height 15)))

(add-to-list 'default-frame-alist '(ns-transparent-titlebar . t))
(add-to-list 'default-frame-alist '(ns-appearance . dark))
(setq ns-use-proxy-icon  nil)
(setq frame-title-format "emacs")

(setq-default line-spacing 0.25)

UX #

  • Ivy, amx, counsel, swiper

    (use-package ivy
      :diminish
      :init
      (use-package amx :defer t)
      (use-package counsel :diminish :config (counsel-mode 1))
      (use-package swiper :defer t)
      (ivy-mode 1)
      :bind
      (("C-s" . swiper-isearch)
       (:map ivy-minibuffer-map
    	 ("M-RET" . ivy-immediate-done))
       (:map counsel-find-file-map
    	 ("C-~" . counsel-goto-local-home)))
      :custom
      (ivy-use-virtual-buffers t)
      (ivy-height 10)
      (ivy-on-del-error-function nil)
      (ivy-magic-slash-non-match-action 'ivy-magic-slash-non-match-create)
      (ivy-count-format "【%d/%d】")
      (ivy-wrap t)
      :config
      (defun counsel-goto-local-home ()
          "Go to the $HOME of the local machine."
          (interactive)
        (ivy--cd "~/")))
    
  • Dirvish

    Dirvish is a nicer wrapper for dired.

    (use-package dirvish
      :ensure t
      :init
      ;; Let Dirvish take over Dired globally
      (dirvish-override-dired-mode))
    

Markdown #


(defun clinton/markdown-mode-setup ()
  (variable-pitch-mode 1)
  (visual-line-mode 1))

(use-package markdown-mode
  :ensure t
  :hook (markdown-mode . clinton/markdown-mode-setup)
  :commands (markdown-mode gfm-mode)
  :mode (("README\\.md\\'" . gfm-mode)
	 ("\\.md\\'" . markdown-mode)
	 ("\\.markdown\\'" . markdown-mode))
  :init (setq markdown-command "multimarkdown")
  )

Use Olivetti to have a nice Ulysses-style clean Markdown editing environment.

(require 'olivetti)

org-mode #

  • Basic setup

    
     (custom-set-faces
     '(default     ((t (:foreground "#BBC2CF"))))
     '(org-level-1 ((t (:foreground "#BF9D7A"))))
     '(org-level-2 ((t (:foreground "#E4E9CD"))))
     '(org-level-3 ((t (:foreground "#EBF2EA"))))
     '(org-level-4 ((t (:foreground "#0ABDA0"))))
     '(org-level-5 ((t (:foreground "#80ADD7")))))
    
    (defun clinton/org-mode-setup ()
      (org-indent-mode)
      (variable-pitch-mode 1)
      (visual-line-mode 1))
    
    (use-package org
      :hook (org-mode . clinton/org-mode-setup)
      :config
      (setq org-ellipsis " ▾")
      (setq org-agenda-start-with-log-mode t)
      (setq org-log-done 'time)
      (setq org-log-into-drawer t)
      (setq org-agenda-start-on-weekday 1)
    
      (setq org-agenda-files
    	'("~/org/inbox.org"
    	  "~/org/personal.org"
    	  "~/org/projects.org"
    	  "~/org/people.org"
    	  "~/roam/pages/20221206173616-2023_holiday_to_the_uk.org"
    	  "~/roam/pages/christmas_present.org"))
    
      (setq org-project-files
    	'("~/roam/pages/20200611173043-master_tracker.org" ; master-tracker
    	  "~/roam/pages/20221110161242-letters_of_life.org" ; letters-of-life
    	  ))
    
     (setq org-agenda-prefix-format
           '((agenda . " %i %?-12t% s")
    	 (todo   . " %i")
    	 (tags   . " %i %-12:c")
    	 (search . " %i %-12:c")))
    
     (require 'org-habit)
     (add-to-list 'org-modules 'org-habit)
     (setq org-habit-graph-column 60)
    
     (setq org-todo-keywords
       '((sequence "TODO(t)" "NEXT(n)" "|" "DONE(d!)")
         (sequence "BACKLOG(b)" "PLAN(p)" "READY(r)" "ACTIVE(a)" "REVIEW(v)" "WAIT(w@/!)" "HOLD(h)" "|" "COMPLETED(c)" "CANC(k@)"))
       )
    
     (setq org-refile-targets '((nil :maxlevel . 9)
    				(org-agenda-files :maxlevel . 9)))
     (setq org-refile-use-outline-path 'file)
     (setq org-outline-path-complete-in-steps nil)
     (advice-add 'org-refile :after 'org-save-all-org-buffers))
     (setq org-agenda-hide-tags-regexp ".")
     (setq org-agenda-log-mode-items '(closed clock state))
     (setq org-habit-show-all-today t)
    

    Define the main agenda views:

    • personal, a personal agenda with a daily calendar, items in the personal inbox for refile, and “next” tasks for all projects
    
    (setq org-agenda-custom-commands
          '(("a" "Personal"
    	 ((agenda ""
    		  ((org-agenda-span
    		    (quote day))
    		   (org-agenda-files
    		    (
    		     quote
    		     ("/Users/clinton/org/personal.org"
    		      "~/roam/pages/20221206173616-2023_holiday_to_the_uk.org"
    		      "~/roam/pages/20220425095242-buying_and_selling_electronics.org"
    		      "~/roam/pages/20221202220826-mum_s_trip_to_israel_2023.org"
    		      "~/roam/pages/20221106113748-moving_to_the_uk.org"
    		      "~/roam/pages/christmas_present.org"
    		      "~/org/projects.org")))
    		   (org-deadline-warning-days 7)
    		   (org-agenda-overriding-header "Agenda\n")))
    	  (tags-todo "+SCHEDULED<=\"<today>\""
    		     ((org-agenda-files
    		       (quote
    			("/Users/clinton/org/people.org")))
    		      (org-agenda-overriding-header "People\n")
    		      (org-agenda-prefix-format "  ")))
    	  (todo "TODO"
    		((org-agenda-overriding-header "To Refile\n")
    		 (org-agenda-prefix-format "  ")
    		 (org-agenda-files
    		  (quote
    		   ("/Users/clinton/org/inbox.org")))))
    	)
          )
    	("p" "Projects 1"
    	 (
    	  (tags "+blocked"
    		((org-agenda-overriding-header "Blocked or waiting\n")
    		 (org-agenda-prefix-format "  %c (%e) | ")
    		 (org-agenda-files
    		  (quote
    		   ("/Users/clinton/org/projects.org")))
    		 ))
    	  (tags "TODO=\"DOING\""
    		((org-agenda-overriding-header "Doing\n")
    		 (org-agenda-prefix-format "  %c (%e) | ")
    		 (org-project-files)
    		 ))
    	(tags "-onhold-prescope-blocked-longterm-ongoing-reading-future+TODO=\"NEXT\""
    	      ((org-agenda-overriding-header "Next\n")
    	       (org-agenda-prefix-format "  %c (%e) | ")
    	       (org-agenda-files
    		(quote
    		 ("/Users/clinton/org/projects.org")))
    	       ))
    	(tags "+reading+TODO=\"NEXT\""
    	      ((org-agenda-overriding-header "Technical reading\n")
    	       (org-agenda-prefix-format "  (%e) | ")
    	       (org-agenda-files
    		(quote
    		 ("/Users/clinton/org/projects.org")))
    	       ))
    	  ))
    	("q" "Projects 2"
    	 (
    	(tags "+prescope+TODO=\"NEXT\""
    	      ((org-agenda-overriding-header "Pre-scope\n")
    	       (org-agenda-prefix-format "  %c (%e) | ")
    	       (org-agenda-files
    		(quote
    		 ("/Users/clinton/org/projects.org")))
    	       ))
    	(tags "+onhold+TODO=\"NEXT\""
    	      ((org-agenda-overriding-header "On hold\n")
    	       (org-agenda-prefix-format "  %c | ")
    	       (org-agenda-files
    		(quote
    		 ("/Users/clinton/org/projects.org")))
    	       ))
           (tags "+future+TODO=\"NEXT\""
    	      ((org-agenda-overriding-header "Future\n")
    	       (org-agenda-prefix-format "  %c | ")
    	       (org-agenda-files
    		(quote
    		 ("/Users/clinton/org/projects.org")))
    	       ))
    	  ))
    	)
          )
    	      (setq org-capture-templates
    		      '(
    			("i" "Inbox" entry (file "/Users/clinton/org/inbox.org")
    			     "* TODO %?\n %i\n %a")
    			("p" "Personal" entry (file "/Users/clinton/org/personal.org")
    			       "* TODO %?\n %i\n SCHEDULED: %^t\n %a")
    			))
    	  (setq org-agenda-window-setup "current-window")
    
  • org roam

      (use-package org-roam
        :init
    	  (setq org-roam-v2-ack t)
      :custom
      (org-roam-dailies-directory "journals/")
      (org-roam-file-exclude-regexp "\\.st[^/]*\\|logseq/.*$")
      (org-roam-capture-templates
     '(("d" "default" plain
        "%?" :target
        (file+head "pages/${slug}.org" "#+title: ${title}\n")
        :unnarrowed t)))
      (org-roam-directory "~/roam") ; replace with your path
      :bind (("C-c n l" . org-roam-buffer-toggle)
    	 ("C-c n f" . org-roam-node-find)
    	 ("C-c i" . org-roam-node-insert)
    	 ("C-c n t" . org-roam-tag-add)
    	 ("C-c n r" . org-roam-db-sync)
    	 ("C-c n j" . org-roam-dailies-goto-today)
    	 ("C-c n y" . org-roam-dailies-goto-yesterday))
      ; global-page-break-lines-mode will break the org-roam buffer
      :hook ( org-roam-mode .  (lambda ()
    			   (make-variable-buffer-local 'visual-line-mode)
    			   (visual-line-mode)))
      :config
      (org-roam-setup))
    
    (setq org-roam-dailies-capture-templates '(("d" "default"
    					  entry
    					  "* %?"
    					  :target (file+head "%<%Y_%m_%d>.org" "#+title: %<%Y-%m-%d>\n"))))
    
    
    ;; (setq org-roam-dailies-capture-templates
    ;;       '(("d" "default" entry
    ;;          "* %?"
    ;;          :target (file+head "%<%Y_%m_%d>.org"
    ;;                             "#+title: %<%Y-%m-%d>\n#+filetags: :journal:\n\n* Journal"))))
    
    (add-hook 'org-roam-dailies-find-file-hook #'olivetti-mode)
    
    (defun clinton/deft-setup ()
       (visual-line-mode 0))
    
    
    (use-package deft
      :hook (deft-mode . clinton/deft-setup)
      :init
      (setq deft-directory "~/roam")
      (setq-default truncate-lines t)
    )
    (require 'deft)
    
    • org-roam-ui

      (add-to-list 'load-path "~/.emacs.d/private/org-roam-ui")
      (load-library "org-roam-ui")
      
  • Julia
  • org babel

    ;;(use-package jupyter
    ;;:ensure t)
    (org-babel-do-load-languages
    'org-babel-load-languages
    '((emacs-lisp . t)
    (org . t)
    (shell . t) ;; was (sh . t)
    (haskell .t)
    (julia .t)
    (python . t)
    (jupyter . t)
    (latex . t)
    (R . t)
    (rust . t)
    ))
    
    (setq org-babel-python-command
    "arch -x86_64 /usr/local/bin/python3")
    ;;(org-babel-jupyter-override-src-block "python")
    (setq ob-async-no-async-languages-alist '("python" "jupyter-python"))
    
  • Automatically tangle this configuration file

    On save, automatically tangle to `init.el`.

    
    (defvar clinton/init-org-file (concat user-emacs-directory "init.org"))
    (defvar clinton/init-el-file  (concat user-emacs-directory "init.el"))
    
    (defun clinton/tangle-on-save ()
      (when (equal (buffer-file-name)
    	       (expand-file-name clinton/init-org-file))
        (let ((org-confirm-babel-evaluate nil))
          (org-babel-tangle)
          (message "init.el tangled from init.org"))))
    
    (add-hook 'after-save-hook 'clinton/tangle-on-save)
    

magit #


(use-package magit)

Hugo support #

(use-package ox-hugo
  :ensure t
  :after ox)

(setq org-hugo-default-section-directory "/Users/clinton/Documents/technical/dev/digital-garden")

The following snippets were taken from Alex Kehayias and they ensure org-roam notes are exported to Hugo markdown files with backlinks, in a nicely readable way.

(defun title-capitalization (str)
  "Convert str to title case"
  (interactive)
  (with-temp-buffer
    (insert str)
    (let* ((beg (point-min))
	   (end (point-max))
	   ;; Basic list of words which don't get capitalized according to simplified rules
	   ;; http://karl-voit.at/2015/05/25/elisp-title-capitalization/
	   (do-not-capitalize-basic-words '("a" "ago" "an" "and" "as" "at" "but" "by" "for"
					    "from" "in" "into" "it" "next" "nor" "of" "off"
					    "on" "onto" "or" "over" "past" "so" "the" "till"
					    "to" "up" "yet"
					    "n" "t" "es" "s"))
	   ;; If user has defined 'my-do-not-capitalize-words, append to basic list
	   (do-not-capitalize-words (if (boundp 'my-do-not-capitalize-words)
					(append do-not-capitalize-basic-words my-do-not-capitalize-words )
				      do-not-capitalize-basic-words)))
      ;; Go to begin of car word
      (goto-char beg)
      (setq continue t)

      ;; Go through the region, word by word
      (while continue
	(let ((last-point (point)))
	  (let ((word (thing-at-point 'word)))
	    (if (stringp word)
		;; Capitalize current word except when it is list member
		(if (and (member (downcase word) do-not-capitalize-words)
			 ;; Always capitalize car word
			 (not (= (point) 1)))
		    (downcase-word 1)

		  ;; If it's an acronym, don't capitalize
		  (if (string= word (upcase word))
		      (progn
			(goto-char (+ (point) (length word) 1)))
		    (capitalize-word 1)))))

	  (skip-syntax-forward "^w" end)

	  ;; Break if we are at the end of the buffer
	  (when (= (point) last-point)
	    (setq continue nil))))

      ;; Always capitalize the last word
      (backward-word 1)

      (let ((word (thing-at-point 'word)))
	(if (and (>= (point) 0)
		 (not (member (or word "s")
			      '("n" "t" "es" "s")))
		 (not (string= word (upcase word))))
	    (capitalize-word 1))))

    (buffer-string)))
	 (defun clinton/org-roam--backlinks-list (file)
	   (if (org-roam--org-roam-file-p file)
	       (--reduce-from

		(concat acc
	   (format "- [[file:%s][%s]]\n"
	   (file-relative-name (car it) "/Users/clinton/roam")
	   (title-capitalization (replace-regexp-in-string "-" " " (replace-regexp-in-string "[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]-" "" (replace-regexp-in-string "-org" "" (org-roam--title-to-slug (file-relative-name (car it) "/Users/clinton/roam")))))))
		      )
		""
		(org-roam-db-query

		    [:select :distinct [links:source]
		     :from links :left :outer :join nodes :on (= links:source nodes:id)
		     :left :outer :join tags :on (= nodes:id tags:node-id)
		     :where (like tags:tag '%public%)]

;           [:select :distinct [links:source]
; v                  :from links
; 1                  :left :outer :join tags :on (= links:source tags:file)
;                    :where (and (= dest $s1) (like tags:tags '%public%))]
		 file))
	     ""))


   (defun clinton/org-roam--extract-note-body (file)
     (with-temp-buffer
       (insert-file-contents file)
       (org-mode)
       (car (org-element-map (org-element-parse-buffer) 'paragraph
		(lambda (paragraph)
		  (let ((begin (plist-get (car (cdr paragraph)) :begin))
			(end (plist-get (car (cdr paragraph)) :end)))
		    (buffer-substring begin end)))))))

  ;; Fetches all org-roam files and exports to hugo markdown
  ;; files. Adds in necessary hugo properties
  ;; e.g. HUGO_BASE_DIR. Only exports files marked as public.
  (setq org-roam-publish-path "~/dev/digital-garden")
  (defun file-path-to-slug (path)
    (let* ((file-name (car (last (split-string path "--"))))
	   (title (car (split-string file-name "\\."))))
      (replace-regexp-in-string (regexp-quote "_") "-" title nil 'literal)))
  (defun file-path-to-md-file-name (path)
    (let ((file-name (car (last (split-string path "/")))))
      (concat (car (split-string file-name "\\.")) ".md")))

  (defun org-roam-to-hugo-md ()
  (interactive)
  ;; Make sure the author is set
  (setq user-full-name "Clinton Boys")

  ;; Don't include any files tagged as private or
  ;; draft. The way we filter tags doesn't work nicely
  ;; with emacsql's DSL so ju
  ;; for clarity

  (let ((notes (org-roam-db-query "SELECT id, file FROM (SELECT nodes.id, nodes.file, group_concat(tags.tag) AS alltags FROM nodes LEFT OUTER JOIN tags ON nodes.id = tags.node_id GROUP BY nodes.file) WHERE alltags is null or (','||alltags||',' not like '%%,\"private\",%%' and ','||alltags||',' not like '%%,\"draft\",%%')")))
    (-map
     (-lambda ((id file))
       ;; Use temporary buffer to prevent a buffer being opened for
       ;; each note file.
       (with-temp-buffer
	 (message "Working on: %s" file)

	 (insert-file-contents file)

	 ;; Adding these tags must go after file content because it
	 ;; will include a :PROPERTIES: drawer as of org-roam v2
	 ;; which must be the first item on the page

	 ;; Add in hugo tags for export. This lets you write the
	 ;; notes without littering HUGO_* tags everywhere
	 ;; HACK:
	 ;; org-export-output-file-name doesn't play nicely with
	 ;; temp buffers since it attempts to get the file name from
	 ;; the buffer. Instead we explicitely add the name of the
	 ;; exported .md file otherwise you would get prompted for
	 ;; the output file name on every note.
	 (goto-char (point-min))
	 (re-search-forward ":END:")
	 (newline)
	 (insert
	  (format "#+HUGO_BASE_DIR: %s\n#+HUGO_SECTION: ./\n#+HUGO_SLUG: %s\n#+EXPORT_FILE_NAME: %s\n"
		  org-roam-publish-path
		  (file-path-to-slug file)
		  (file-path-to-md-file-name file)))

	 ;; If this is a placeholder note (no content in the
	 ;; body) then add default text. This makes it look ok when
	 ;; showing note previews in the index and avoids a headline
	 ;; followed by a headline in the note detail page.
	 (if (eq (clinton/org-roam--extract-note-body file) nil)
	     (progn
	       (goto-char (point-max))
	       (insert "\n/This note does not have a description yet./\n")))

	 ;; Add in backlinks (at the end of the file) because
	 ;; org-export-before-processing-hook won't be useful the
	 ;; way we are using a temp buffer
	 (let ((links (clinton/org-roam--backlinks-list id file)))
	   (if (not (string= links ""))
	       (progn
		 (goto-char (point-max))
		 (insert (concat "\n* Links to this note\n") links))))

	 (org-hugo-export-to-md)))
     notes)))


;;   (defun org-roam-to-hugo-md ()
;;     (interactive)
;;     ;; Make sure the author is set
;;     (setq user-full-name "Clinton Boys")

;;     (let ((files (mapcan
;;                   (lambda (x) x)
;;                   (org-roam-db-query
;;                    [:select :distinct [files:file]
;;                     :from files :left :outer :join nodes :on (= files:file nodes:file)
;;                     :left :outer :join tags :on (= nodes:id tags:node-id)
;;                     :where (like tags:tag '%public%)])))))

;; ;                  [:select :distinct [files:file]
;; ; v                 :from files
;; ; 1                 :left :outer :join tags :on (= files:file tags:file)
;; ;                   :where (like tags:tags '%public%)]))))

;;       (mapc
;;        (lambda (f)
;;          ;; Use temporary buffer to prevent a buffer being opened for
;;          ;; each note file.
;;          (with-temp-buffer
;;            (message "Working on: %s" f)
;;            (insert-file-contents f)

;;            (goto-char (point-min))
;;            ;; Add in hugo tags for export. This lets you write the
;;            ;; notes without littering HUGO_* tags everywhere
;;            ;; HACK:
;;            ;; org-export-output-file-name doesn't play nicely with
;;            ;; temp buffers since it attempts to get the file name from
;;            ;; the buffer. Instead we explicitely add the name of the
;;            ;; exported .md file otherwise you would get prompted for
;;            ;; the output file name on every note.
;;            (insert
;;             (format "#+HUGO_BASE_DIR: %s\n#+HUGO_SECTION: ./\n#+HUGO_SLUG: %s\n#+EXPORT_FILE_NAME: %s\n"
;;                     org-roam-publish-path
;;                     (file-path-to-slug (file-relative-name f "/Users/clinton/roam"))
;;                     (file-path-to-md-file-name f)))

;;            ;; If this is a placeholder note (no content in the
;;            ;; body) then add default text. This makes it look ok when
;;            ;; showing note previews in the index and avoids a headline
;;            ;; followed by a headline in the note detail page.
;;            (if (eq (clinton/org-roam--extract-note-body f) nil)
;;                (progn
;;                  (goto-char (point-max))
;;                  (insert "\n/This note does not have a description yet./\n")))
;;            (org-hugo-export-to-md)))
;;        files)
;;       ))

  (defun org-roam-to-hugo-md-private ()
     (interactive)
     ;; Make sure the author is set
     (setq
     user-full-name "Clinton Boys")

    (let ((files (mapcan
		  (lambda (x) x)
		  (org-roam-db-query
		  [:select :distinct [files:file]
		   :from files
		   :left :outer :join tags :on (= files:file tags:file)]))))
      (mapc
       (lambda (f)
	 ;; Use temporary buffer to prevent a buffer being opened for
	 ;; each note file.
	 (with-temp-buffer
	   (message "Working on: %s" f)
	   (insert-file-contents f)

	   (goto-char (point-min))
	   ;; Add in hugo tags for export. This lets you write the
	   ;; notes without littering HUGO_* tags everywhere
	   ;; HACK:
	   ;; org-export-output-file-name doesn't play nicely with
	   ;; temp buffers since it attempts to get the file name from
	   ;; the buffer. Instead we explicitely add the name of the
	   ;; exported .md file otherwise you would get prompted for
	   ;; the output file name on every note.
	   (insert
	    (format "#+HUGO_BASE_DIR: %s\n#+HUGO_SECTION: ./\n#+HUGO_SLUG: %s\n#+EXPORT_FILE_NAME: %s\n"
		    "~/dev/private-zettelkasten"
		    (file-path-to-slug (file-relative-name f "/Users/clinton/roam"))
		    (file-path-to-md-file-name f)))

	   ;; If this is a placeholder note (no content in the
	   ;; body) then add default text. This makes it look ok when
	   ;; showing note previews in the index and avoids a headline
	   ;; followed by a headline in the note detail page.
	   (if (eq (clinton/org-roam--extract-note-body f) nil)
	       (progn
		 (goto-char (point-max))
		 (insert "\n/This note does not have a description yet./\n")))
	   (org-hugo-export-to-md)))
       files)))

Finally, we need a global keybinding to run the Ninja build script.

(defvar script-name "~/.emacs.d/roam_build.sh")

(defun call-my-script-with-word ()
  (interactive)
  (shell-command
   (concat script-name
	   " "
	   (thing-at-point 'word))))

Programming #

  • Rust

    (use-package rust-mode
      :mode "\\.rs\\'"
      :custom
      (rust-format-on-save t)
      :bind (:map rust-mode-map ("C-c C-c" . rust-run))
      :config
      (use-package flycheck-rust
        :after flycheck
        :config
        (with-eval-after-load 'rust-mode
          (add-hook 'flycheck-mode-hook #'flycheck-rust-setup))))
    
  • Python

    (use-package python-mode
      :mode "\\.py\\'"
      :custom
      (rust-format-on-save t)
      :bind (:map python-mode-map ("C-c C-c" . python-run))
      :config
      (use-package flycheck-python
        :after flycheck
        :config
        (with-eval-after-load python-mode)))
    
  • Typescript

        (use-package tree-sitter
        :ensure t
        :config
        ;; activate tree-sitter on any buffer containing code for which it has a parser available
        (global-tree-sitter-mode)
        ;; you can easily see the difference tree-sitter-hl-mode makes for python, ts or tsx
        ;; by switching on and off
        (add-hook 'tree-sitter-after-on-hook #'tree-sitter-hl-mode))
    
      (use-package tree-sitter-langs
        :ensure t
        :after tree-sitter)
    
      (use-package typescript-mode
      :after tree-sitter
      :config
      ;; we choose this instead of tsx-mode so that eglot can automatically figure out language for server
      ;; see https://github.com/joaotavora/eglot/issues/624 and https://github.com/joaotavora/eglot#handling-quirky-servers
      (define-derived-mode typescriptreact-mode typescript-mode
        "TypeScript TSX")
    
      ;; use our derived mode for tsx files
      (add-to-list 'auto-mode-alist '("\\.tsx?\\'" . typescriptreact-mode))
      ;; by default, typescript-mode is mapped to the treesitter typescript parser
      ;; use our derived mode to map both .tsx AND .ts -> typescriptreact-mode -> treesitter tsx
      (add-to-list 'tree-sitter-major-mode-language-alist '(typescriptreact-mode . tsx)))
    
      ;; I'm not sure why this is needed, but it throws an error if I remove it
    (cl-defmethod project-root ((project (head eglot-project)))
      (cdr project))
    
    (defun my-project-try-tsconfig-json (dir)
      (when-let* ((found (locate-dominating-file dir "tsconfig.json")))
        (cons 'eglot-project found)))
    
    (add-hook 'project-find-functions
    	  'my-project-try-tsconfig-json nil nil)
    (require 'eglot)
    (add-to-list 'eglot-server-programs
    	     '((typescript-mode) "typescript-language-server" "--stdio"))
    (require 'prettier-js)
    

LSP mode #

(use-package eglot)
(setq lsp-rust-analyzer-server-command '("~/.cargo/bin/rust-analyzer"))
(add-to-list 'eglot-server-programs '(python-mode "/opt/homebrew/bin/pyls"))
(setq rustic-lsp-client 'eglot)
(setq python-shell-interpreter "/opt/homebrew/bin/python3")
  (use-package company
    :diminish company-mode
    :hook ((prog-mode LaTeX-mode latex-mode ess-r-mode) . company-mode)
    :bind
    (:map company-active-map
	  ([tab] . smarter-tab-to-complete)
	  ("TAB" . smarter-tab-to-complete))
    :custom
    (company-minimum-prefix-length 1)
    (company-tooltip-align-annotations t)
    (company-require-match 'never)
    ;; Don't use company in the following modes
    (company-global-modes '(not shell-mode eaf-mode))
    ;; Trigger completion immediately.
    (company-idle-delay 0.1)
    ;; Number the candidates (use M-1, M-2 etc to select completions).
    (company-show-numbers t)
    :config
    (global-company-mode 1)
    )
(setq company-global-modes '(not org-mode))
(electric-pair-mode 1)
(setq electric-pair-preserve-balance nil)
(load-file "~/.emacs.d/chatgpt-shell.el")
(setq chatgpt-shell-openai-key "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")

Exporting songs to Notes #


  (defun my-org-export-each-headline-to-markdown (&optional scope)
  "Export each headline to a markdown file with the title as filename.
If SCOPE is nil headlines in the current buffer are exported.
For other valid values for SCOPE see `org-map-entries'.
Already existing files are overwritten."
  (interactive)
  ;; Widen buffer temporarily as narrowing would affect the exporting.
  (org-with-wide-buffer
   (save-mark-and-excursion
     ;; Loop through each headline.
     (org-map-entries
      (lambda ()
	;; Get the plain headline text without statistics and make filename.
	(let* ((title (car (last (org-get-outline-path t))))
	       (dir (file-name-directory buffer-file-name))
	       (filename (concat "/Users/clinton/Documents/creative/songs/" title ".md")))
	  ;; Set the active region.
	  (set-mark (point))
	  (outline-next-preface)
	  (activate-mark)
	  ;; Export the region to a markdown file.
	  (with-current-buffer (org-md-export-as-markdown)
	    ;; Save the buffer to file and kill it.
	    (write-file filename)
	    (kill-current-buffer))))
      nil scope))))
  • rational-emacs, a nice package to start building one’s own configuration from, with sensible defaults for a lot of common usages.
  • Another nice repo with a well-organised configuration to steal from.