Skip to content

The modules of my Emacs configuration (`jw-emacs-modules/`)

;;; Theme setup and related
;;;; Load the desired theme module
;; These all reference my packages: `modus-themes', `ef-themes',
;; `standard-themes'.
(when jw-emacs-load-theme-family
(require
(pcase jw-emacs-load-theme-family
('ef 'jw-emacs-ef-themes)
('modus 'jw-emacs-modus-themes)
('standard 'jw-emacs-standard-themes))))
;;; The Modus themes
;; The themes are highly customisable. Read the manual:
;; <https://protesilaos.com/emacs/modus-themes>.
(use-package modus-themes
:straight t
:demand t
:bind (("<f5>" . modus-themes-toggle)
("C-<f5>" . modus-themes-select))
:config
(setq modus-themes-custom-auto-reload nil
modus-themes-to-toggle '(modus-operandi modus-vivendi)
;; modus-themes-to-toggle '(modus-operandi-tinted modus-vivendi-tinted)
;; modus-themes-to-toggle '(modus-operandi-deuteranopia modus-vivendi-deuteranopia)
;; modus-themes-to-toggle '(modus-operandi-tritanopia modus-vivendi-tritanopia)
modus-themes-mixed-fonts t
modus-themes-variable-pitch-ui t
modus-themes-italic-constructs t
modus-themes-bold-constructs nil
modus-themes-completions '((t . (extrabold)))
modus-themes-prompts '(extrabold)
modus-themes-headings
'((agenda-structure . (variable-pitch light 2.2))
(agenda-date . (variable-pitch regular 1.3))
(t . (regular 1.15))))
(setq modus-themes-common-palette-overrides nil))
(if (jw-emacs-theme-environment-dark-p)
(modus-themes-load-theme (cadr modus-themes-to-toggle))
(modus-themes-load-theme (car modus-themes-to-toggle)))
(provide 'jw-emacs-modus-themes)
;;; The Ef (εὖ) themes
;; The themes are customisable. Read the manual:
;; <https://protesilaos.com/emacs/ef-themes>.
(use-package ef-themes
:straight t
:demand t
:bind ("<f5>" . ef-themes-select)
:config
(setq ef-themes-variable-pitch-ui t
ef-themes-mixed-fonts t
ef-themes-headings ; read the manual's entry of the doc string
'((0 . (variable-pitch light 1.9))
(1 . (variable-pitch light 1.8))
(2 . (variable-pitch regular 1.7))
(3 . (variable-pitch regular 1.6))
(4 . (variable-pitch regular 1.5))
(5 . (variable-pitch 1.4)) ; absence of weight means `bold'
(6 . (variable-pitch 1.3))
(7 . (variable-pitch 1.2))
(agenda-date . (semilight 1.5))
(agenda-structure . (variable-pitch light 1.9))
(t . (variable-pitch 1.1))))
;; The `ef-themes' provide lots of themes. I want to pick one at
;; random when I start Emacs: the `ef-themes-load-random' does just
;; that (it can be called interactively as well). I just check with
;; my desktop environment to determine if the choice should be about
;; a light or a dark theme. Those functions are in my init.el.
(if (jw-emacs-theme-environment-dark-p)
(ef-themes-load-random 'dark)
(ef-themes-load-random 'light)))
(provide 'jw-emacs-ef-themes)
;;;; Pulsar
;; Read the pulsar manual: <https://protesilaos.com/emacs/pulsar>.
(use-package pulsar
:straight t
:config
(setopt pulsar-pulse t
pulsar-delay 0.055
pulsar-iterations 10
pulsar-face 'pulsar-magenta
pulsar-highlight-face 'pulsar-cyan)
(pulsar-global-mode 1)
:hook
;; There are convenience functions/commands which pulse the line using
;; a specific colour: `pulsar-pulse-line-red' is one of them.
((next-error . (pulsar-pulse-line-red pulsar-recenter-top pulsar-reveal-entry))
(minibuffer-setup . pulsar-pulse-line-red))
:bind
;; pulsar does not define any key bindings. This is just my personal
;; preference. Remember to read the manual on the matter. Evaluate:
;;
;; (info "(elisp) Key Binding Conventions")
(("C-x l" . pulsar-pulse-line) ; override `count-lines-page'
("C-x L" . pulsar-highlight-dwim))) ; or use `pulsar-highlight-line'
;;;; Lin
;; Read the lin manual: <https://protesilaos.com/emacs/lin>.
(use-package lin
:straight t
:hook (after-init . lin-global-mode) ; applies to all `lin-mode-hooks'
:config
;; You can use this to live update the face:
;;
;; (customize-set-variable 'lin-face 'lin-green)
;;
;; Or `setopt' on Emacs 29: (setopt lin-face 'lin-yellow)
;;
;; I still prefer `setq' for consistency.
(setq lin-face 'lin-magenta))
;;;; Increase padding of windows/frames
;; Yet another one of my packages:
;; <https://protesilaos.com/codelog/2023-06-03-emacs-spacious-padding/>.
(use-package spacious-padding
:straight t
:if (display-graphic-p)
:hook (after-init . spacious-padding-mode)
:bind ("<f8>" . spacious-padding-mode)
:init
;; These are the defaults, but I keep it here for visiibility.
(setq spacious-padding-widths
'( :internal-border-width 15
:header-line-width 4
:mode-line-width 6
:tab-width 4
:right-divider-width 1
:scroll-bar-width 8
:left-fringe-width 20
:right-fringe-width 20))
;; Read the doc string of `spacious-padding-subtle-mode-line' as
;; it is very flexible.
(setq spacious-padding-subtle-mode-line
`( :mode-line-active ,(if (or (eq jw-emacs-load-theme-family 'modus)
(eq jw-emacs-load-theme-family 'standard))
'default
'help-key-binding)
:mode-line-inactive window-divider)))
;;; Cursor appearance (cursory)
;; Read the manual: <https://protesilaos.com/emacs/cursory>.
(use-package cursory
:straight t
:demand t
:if (display-graphic-p)
:config
(setq cursory-presets
'((box
:blink-cursor-interval 1.2)
(box-no-blink
:blink-cursor-mode -1)
(bar
:cursor-type (bar . 2)
:blink-cursor-interval 0.8)
(bar-no-other-window
:inherit bar
:cursor-in-non-selected-windows nil)
(bar-no-blink
:cursor-type (bar . 2)
:blink-cursor-mode -1)
(underscore
:cursor-type (hbar . 3)
:blink-cursor-blinks 50)
(underscore-thin-other-window
:inherit underscore
:cursor-in-non-selected-windows (hbar . 1))
(underscore-thick
:cursor-type (hbar . 8)
:blink-cursor-interval 0.3
:blink-cursor-blinks 50
:cursor-in-non-selected-windows (hbar . 3))
(underscore-thick-no-blink
:blink-cursor-mode -1
:cursor-type (hbar . 8)
:cursor-in-non-selected-windows (hbar . 3))
(t ; the default values
:cursor-type box
:cursor-in-non-selected-windows hollow
:blink-cursor-mode 1
:blink-cursor-blinks 10
:blink-cursor-interval 0.2
:blink-cursor-delay 0.2)))
;; I am using the default values of `cursory-latest-state-file'.
;; Set last preset or fall back to desired style from `cursory-presets'.
(cursory-set-preset (or (cursory-restore-latest-preset) 'box))
:hook
;; The other side of `cursory-restore-latest-preset'.
(kill-emacs . cursory-store-latest-preset)
:bind
;; We have to use the "point" mnemonic, because C-c c is often the
;; suggested binding for `org-capture' and is the one I use as well.
("C-c p" . cursory-set-preset))
;;;; Theme buffet
(use-package theme-buffet
:straight t
:after (:any modus-themes ef-themes)
:defer 1
:config
(let ((modus-themes-p (featurep 'modus-themes))
(ef-themes-p (featurep 'ef-themes)))
(setq theme-buffet-menu 'end-user)
(setq theme-buffet-end-user
(cond
((and modus-themes-p ef-themes-p)
'( :night (modus-vivendi ef-dark ef-winter ef-autumn ef-night ef-duo-dark ef-symbiosis)
:morning (modus-operandi ef-light ef-cyprus ef-spring ef-frost ef-duo-light)
:afternoon (modus-operandi-tinted ef-arbutus ef-day ef-kassio ef-summer ef-elea-light ef-maris-light ef-melissa-light ef-trio-light ef-reverie)
:evening (modus-vivendi-tinted ef-rosa ef-elea-dark ef-maris-dark ef-melissa-dark ef-trio-dark ef-dream)))
(ef-themes-p
'( :night (ef-dark ef-winter ef-autumn ef-night ef-duo-dark ef-symbiosis)
:morning (ef-light ef-cyprus ef-spring ef-frost ef-duo-light)
:afternoon (ef-arbutus ef-day ef-kassio ef-summer ef-elea-light ef-maris-light ef-melissa-light ef-trio-light ef-reverie)
:evening (ef-rosa ef-elea-dark ef-maris-dark ef-melissa-dark ef-trio-dark ef-dream)))
(modus-themes-p
'( :night (modus-vivendi modus-vivendi-tinted modus-vivendi-tritanopia modus-vivendi-deuteranopia)
:morning (modus-operandi modus-operandi-tinted modus-operandi-tritanopia modus-operandi-deuteranopia)
:afternoon (modus-operandi modus-operandi-tinted modus-operandi-tritanopia modus-operandi-deuteranopia)
:evening (modus-vivendi modus-vivendi-tinted modus-vivendi-tritanopia modus-vivendi-deuteranopia)))))
(when (or modus-themes-p ef-themes-p)
(theme-buffet-timer-hours 1))))
;;;; Fontaine (font configurations)
;; Read the manual: <https://protesilaos.com/emacs/fontaine>
(use-package fontaine
:straight t
:if (display-graphic-p)
:hook
;; Persist the latest font preset when closing/starting Emacs.
((after-init . fontaine-mode)
(after-init . (lambda ()
;; Set last preset or fall back to desired style from `fontaine-presets'.
(fontaine-set-preset (or (fontaine-restore-latest-preset) 'regular)))))
:bind (("C-c f" . fontaine-set-preset)
("C-c F" . fontaine-toggle-preset))
:config
;; And this is for Emacs28.
(setq-default text-scale-remap-header-line t)
;; This is the default value. Just including it here for
;; completeness.
(setq fontaine-latest-state-file (locate-user-emacs-file "fontaine-latest-state.eld"))
;; The font family is my design: <https://github.com/protesilaos/aporetic>.
(setq fontaine-presets
'((small
:default-height 80)
(regular) ; like this it uses all the fallback values and is named `regular'
(medium
:default-family "Aporetic Serif Mono"
:default-height 115
:fixed-pitch-family "Aporetic Serif Mono"
:variable-pitch-family "Aporetic Sans")
(large
:default-height 150)
(presentation
:default-height 180)
(jumbo
:inherit medium
:default-height 260)
(t
;; I keep all properties for didactic purposes, but most can be
;; omitted. See the fontaine manual for the technicalities:
;; <https://protesilaos.com/emacs/fontaine>.
:default-family "Aporetic Sans Mono"
:default-weight regular
:default-slant normal
:default-width normal
:default-height 100
:fixed-pitch-family "Aporetic Sans Mono"
:fixed-pitch-weight nil
:fixed-pitch-slant nil
:fixed-pitch-width nil
:fixed-pitch-height 1.0
:fixed-pitch-serif-family nil
:fixed-pitch-serif-weight nil
:fixed-pitch-serif-slant nil
:fixed-pitch-serif-width nil
:fixed-pitch-serif-height 1.0
:variable-pitch-family "Aporetic Serif"
:variable-pitch-weight nil
:variable-pitch-slant nil
:variable-pitch-width nil
:variable-pitch-height 1.0
:mode-line-active-family nil
:mode-line-active-weight nil
:mode-line-active-slant nil
:mode-line-active-width nil
:mode-line-active-height 1.0
:mode-line-inactive-family nil
:mode-line-inactive-weight nil
:mode-line-inactive-slant nil
:mode-line-inactive-width nil
:mode-line-inactive-height 1.0
:header-line-family nil
:header-line-weight nil
:header-line-slant nil
:header-line-width nil
:header-line-height 1.0
:line-number-family nil
:line-number-weight nil
:line-number-slant nil
:line-number-width nil
:line-number-height 1.0
:tab-bar-family nil
:tab-bar-weight nil
:tab-bar-slant nil
:tab-bar-width nil
:tab-bar-height 1.0
:tab-line-family nil
:tab-line-weight nil
:tab-line-slant nil
:tab-line-width nil
:tab-line-height 1.0
:bold-family nil
:bold-slant nil
:bold-weight bold
:bold-width nil
:bold-height 1.0
:italic-family nil
:italic-weight nil
:italic-slant italic
:italic-width nil
:italic-height 1.0
:line-spacing nil)))
(with-eval-after-load 'pulsar
(add-hook 'fontaine-set-preset-hook #'pulsar-pulse-line)))
;;;;; `variable-pitch-mode' setup
(use-package face-remap
:straight nil
:functions jw/enable-variable-pitch
:bind ( :map ctl-x-x-map
("v" . variable-pitch-mode))
:hook ((text-mode notmuch-show-mode elfeed-show-mode) . jw/enable-variable-pitch)
:config
;; NOTE 2022-11-20: This may not cover every case, though it works
;; fine in my workflow. I am still undecided by EWW.
(defun jw/enable-variable-pitch ()
(unless (derived-mode-p 'mhtml-mode 'nxml-mode 'yaml-mode)
(variable-pitch-mode 1)))
;;;;; Resize keys with global effect
:bind
;; Emacs 29 introduces commands that resize the font across all
;; buffers (including the minibuffer), which is what I want, as
;; opposed to doing it only in the current buffer. The keys are the
;; same as the defaults.
(("C-x C-=" . global-text-scale-adjust)
("C-x C-+" . global-text-scale-adjust)
("C-x C-0" . global-text-scale-adjust)))
(provide 'jw-emacs-theme)

Enable line numbers globally, but not in the following modes: org, term, shell, and eshell.

In addition to line numbers, the column number will also be displayed.

;; Enable column numbers
(column-number-mode)
(global-display-line-numbers-mode t)
;; Disable line numbers for some modes
(dolist (mode '(org-mode-hook
markdown-mode-hook
term-mode-hook
shell-mode-hook
eshell-mode-hook))
(add-hook mode (lambda () (display-line-numbers-mode 0))))

Since fill-paragraph wraps fill-column, we adjust the size of the fill-column variable.

(setq-default fill-column 80)

Helpful adds a lot of very helpful (get it?) information to Emacs’ describe- command buffers. For example, if you use describe-function, you will not only get the documentation about the function, you will also see the source code of the function and where it gets used in other places in the Emacs configuration. It is very useful for figuring out how things work in Emacs.

(use-package helpful
:bind
([remap describe-command] . helpful-command)
([remap describe-key] . helpful-key))
(setq auth-sources '("~/.authinfo" "~/.netrc"))

The jw-emacs-essentials.el call to provide

Section titled “The jw-emacs-essentials.el call to provide”
(provide 'jw-emacs-essentials)

The mode-line was disabled earlier ([The init.el conditional to remove display of mode-line](*The init.el conditional to remove display of mode-line)) so that the startup UI would look smooth

(setq-default mode-line-format (default-value 'mode-line-format))
(setq display-time-format "%l:%M %p %b %y"
display-time-default-load-average nil)

doom-modeline is a very attractive and rich (yet still minimal) mode line configuration for Emacs. The default configuration is quite good but you can check out the configuration options for more things you can enable or disable.

If you are running in the macos terminal, then you have to make sure that you set the font to Droid Sans Mono Nerd Font Complete 18. You can do this by the following steps:

  • Navigate to Settings
  • Navigate to Profiles tab
  • Navigate to Text subtab
  • Under the Font menu click on Change
  • Select the appropriate font
(use-package doom-modeline
:straight t
:init (doom-modeline-mode 1)
:custom ((doom-modeline-height 15)))

doom-modeline icons rely on nerd-icons. Thus, you must install the nerd-icons if you want to use the icons on the modeline.

IMPORTANT: must run the following command — Mx - nerd-icons-install-fonts for the icons to populate. See the github issue here: Doom Emacs Issue #7368

(use-package nerd-icons
;; :custom
;; The Nerd Font you want to use in GUI
;; "Symbols Nerd Font Mono" is the default and is recommended
;; but you can use any other Nerd Font if you want
;; (nerd-icons-font-family "Symbols Nerd Font Mono")
)

To turn off icons uncomment the following:

;; (setq doom-modeline-icon nil)

The following contains configurations of the doom-modeline. All the configurations here use the setq.

;; If non-nil, a word count will be added to the selection-info modeline segment.
(setq doom-modeline-enable-word-count t)
;; Major modes in which to display word count continuously.
;; Also applies to any derived modes. Respects `doom-modeline-enable-word-count'.
;; If it brings the sluggish issue, disable `doom-modeline-enable-word-count' or
;; remove the modes from `doom-modeline-continuous-word-count-modes'.
(setq doom-modeline-continuous-word-count-modes '(markdown-mode gfm-mode org-mode))

Display the virtual environment version.

(setq doom-modeline-env-version t)
(provide 'jw-emacs-modeline)
;; for preserving minibuffer history
(use-package savehist
:straight t
:config
(setq history-length 25)
(savehist-mode 1))
;; Individual history elements can be configured separately
;;(put 'minibuffer-history 'history-length 25)
;;(put 'evil-ex-history 'history-length 50)
;;(put 'kill-ring 'history-length 25))

Completions with vertico.el

(defun jw/minibuffer-backward-kill (arg)
"When minibuffer is completing a file name delete up to parent
folder, otherwise delete a word"
(interactive "p")
(if minibuffer-completing-file-name
;; Borrowed from https://github.com/raxod502/selectrum/issues/498#issuecomment-803283608
(if (string-match-p "/." (minibuffer-contents))
(zap-up-to-char (- arg) ?/)
(delete-minibuffer-contents))
(delete-word (- arg))))
(use-package vertico
:straight t
:bind (:map vertico-map
("C-j" . vertico-next)
("C-k" . vertico-previous)
("C-f" . vertico-exit)
:map minibuffer-local-map
("M-h" . jw/minibuffer-backward-kill))
:custom
(vertico-cycle t)
:init
(vertico-mode))

Completions in region with corfu.el.

(use-package corfu
:straight t
;; Optional customizations
:custom
(corfu-cycle t) ;; Enable cycling for `corfu-next/previous'
(corfu-auto t) ;; Enable auto completion
(corfu-auto-prefix 2)
(corfu-auto-delay 0.8)
(corfu-popinfo-delay '(0.5 . 0.2))
(corfu-preview-current 'insert) ; insert previewed candidate
(corfu-preselect 'prompt)
;; (corfu-separator ?\s) ;; Orderless field separator
;; (corfu-quit-at-boundary nil) ;; Never quit at completion boundary
;; (corfu-quit-no-match nil) ;; Never quit, even if there is no match
;; (corfu-preview-current nil) ;; Disable current candidate preview
;; (corfu-preselect 'prompt) ;; Preselect the prompt
;; (corfu-on-exact-match nil) ;; Configure handling of exact matches
;; (corfu-scroll-margin 5) ;; Use scroll margin
:bind (:map corfu-map
("C-j" . corfu-next)
("C-k" . corfu-previous)
("C-f" . corfu-insert))
;; Enable Corfu only for certain modes.
;; :hook ((prog-mode . corfu-mode)
;; (shell-mode . corfu-mode)
;; (eshell-mode . corfu-mode))
;; Recommended: Enable Corfu globally. This is recommended since Dabbrev can
;; be used globally (M-/). See also the customization variable
;; `global-corfu-modes' to exclude certain modes.
:init
(global-corfu-mode))

Since corfu.el does not support running emacs in the terminal, I will just stick with company.el instead of corfu-terminal.

Company Mode provides a nicer in-buffer completion interface than completion-at-point which is more reminiscent of what you would expect from an IDE. We add a simple configuration to make the keybindings a little more useful (TAB now completes the selection and initiates completion at the current location if needed).

We also use company-box to further enhance the look of the completions with icons and better overall presentation.

(unless (display-graphic-p)
(progn
;; Configuration for GUI mode
(use-package company
:after eglot
:hook (eglot--managed-mode . company-mode)
:bind (:map company-active-map
("<tab>" . company-complete-selection))
(:map eglot-mode-map
("<tab>" . company-indent-or-complete-common))
:custom
(company-minimum-prefix-length 1)
(company-idle-delay 0.0))
(use-package company-box
:hook (company-mode . company-box-mode)))
;; Configuration for terminal mode (optional)
;; Add your terminal mode specific configuration here
)

Additional completions in region with cape.el.

(use-package cape
:straight t
:init
(add-to-list 'completion-at-point-functions #'cape-file)
(add-to-list 'completion-at-point-functions #'cape-dabbrev))

For candidate filtering.

(use-package orderless
:init
(setq completion-styles '(orderless)
completion-category-defaults nil
completion-category-overrides '((file (styles . (partial-completion))))))

For completion notations.

;;; Detailed completion annotations (marginalia.el)
(use-package marginalia
:straight t
:hook (after-init . marginalia-mode)
:config
(setq marginalia-max-relative-age 0)) ; absolute time

The jw-emacs-completion.el call to provide

Section titled “The jw-emacs-completion.el call to provide”
(provide 'jw-emacs-completion)

:PROPERTIES: :ID: 77434357-7761-4049-9F64-1808E10E549D :END:

Set up Org Mode with a baseline configuration. The following sections will add more things to it.

(defun jw/org-mode-setup ()
(org-indent-mode) ;; auto-indentation for headings
(variable-pitch-mode 1) ;; cause fonts to vary by proportionality
(visual-line-mode 1)) ;; wrap the text so that it does not go out of view
(use-package org
:hook (org-mode . jw/org-mode-setup)
:config
(setq org-ellipsis "") ;; when org headings closed down arrow instead of ellipsis
(setq org-M-RET-may-split-line '((default . nil))) ;; when auto generating subsequent headings, avoid splitting the line
(setq org-insert-heading-respect-content t) ;; when creating new heading respects the content of which heading it was originally
(setq org-log-done 'time)
(setq org-log-into-drawer t) ;; task change is in drawer instead of content
;; keywords for org task states
)

:PROPERTIES: :ID: 43652950-B9D2-4FAF-8F0C-75D1496E85FE :END:

;; setting dir of tasks
(setq org-agenda-files (directory-files-recursively "~/Otzar/Docs/agenda/" "\\.org$"))
(setq org-todo-keywords
'((sequence "TODO(t)" "WAIT(w!)" "|" "CANCEL(c!)" "DONE(d!)")))

Configure for macos to play sound:

;; on macos, fix "This Emacs binary lacks sound support"
;; - https://github.com/leoliu/play-sound-osx/blob/master/play-sound.el
;; - update according to https://github.com/leoliu/play-sound-osx/issues/2#issuecomment-1088360638
(when (eq system-type 'darwin)
(unless (and (fboundp 'play-sound-internal)
(subrp (symbol-function 'play-sound-internal)))
(defun play-sound-internal (sound)
"Internal function for `play-sound' (which see)."
(or (eq (car-safe sound) 'sound)
(signal 'wrong-type-argument (list sound)))
(cl-destructuring-bind (&key file data volume device)
(cdr sound)
(and (or data device)
(error "DATA and DEVICE arg not supported"))
(apply #'start-process "afplay" nil
"afplay" (append (and volume (list "-v" volume))
(list (expand-file-name file data-directory))))))))
(setq org-clock-sound "~/.dotfiles/.assets/sounds/mixkit-alert-quick-chime-766.wav")

The following is for sound support,

Usually this is a problem for macos and I found a snippet of code that enables sound support. The way to tell is by running M-x play-sound-file and navigating to the .wav file will ouput “This Emacs binary lacks sound support.”

;; on macos, fix "This Emacs binary lacks sound support"
;; - https://github.com/leoliu/play-sound-osx/blob/master/play-sound.el
;; - update according to https://github.com/leoliu/play-sound-osx/issues/2#issuecomment-1088360638
(when (eq system-type 'darwin)
(unless (and (fboundp 'play-sound-internal)
(subrp (symbol-function 'play-sound-internal)))
(defun play-sound-internal (sound)
"Internal function for `play-sound' (which see)."
(or (eq (car-safe sound) 'sound)
(signal 'wrong-type-argument (list sound)))
(cl-destructuring-bind (&key file data volume device)
(cdr sound)
(and (or data device)
(error "DATA and DEVICE arg not supported"))
(apply #'start-process "afplay" nil
"afplay" (append (and volume (list "-v" volume))
(list (expand-file-name file data-directory))))))))

As recommended by the official org manual to have these keys bound.

(global-set-key (kbd "C-c l") 'org-store-link)
(global-set-key (kbd "C-c C-l") 'org-insert-link)

Instead of relying on just the path/etc, allow orgmode to use unique IDs to create internal links that can point to specific headings in org files.

With the create-if-interactive setting, it only creates in interactive settings.

(setq org-id-link-to-org-use-id 'create-if-interactive)

Customize the heading bullets to make it consistent and nicer.

(use-package org-bullets
:after org
:hook (org-mode . org-bullets-mode)
:custom
(org-bullets-bullet-list '("" "" "" "" "" "" "")))
(setq org-image-actual-width nil)
(setq org-startup-with-inline-images t)
(add-hook 'org-mode-hook 'org-display-inline-images)

These structured templates are used to auto generate code blocks for org mode. In order to use the template simply type < followed by the abbreviation of the language and hit the TAB button. For example, the python snippit would be <py TAB.

;; This is needed as of Org 9.2
(require 'org-tempo)
(add-to-list 'org-structure-template-alist '("sh" . "src shell"))
(add-to-list 'org-structure-template-alist '("el" . "src emacs-lisp"))
(add-to-list 'org-structure-template-alist '("py" . "src python"))
(add-to-list 'org-structure-template-alist '("clang" . "src c"))
(add-to-list 'org-structure-template-alist '("cpp" . "src cpp"))

This snippet adds a hook to org-mode buffers so that jw/org-babel-tangle-config gets executed each time such a buffer gets saved. This function checks to see if the file being saved is the Emacs.org file you’re looking at right now, and if so, automatically exports the configuration here to the associated output files.

;; Automatically tangle our Emacs.org config file when we save it
(defun jw/org-babel-tangle-config ()
(when (string-equal (buffer-file-name)
(expand-file-name "~/.dotfiles/Emacs.org"))
;; Dynamic scoping to the rescue
(let ((org-confirm-babel-evaluate nil))
(org-babel-tangle))))
(add-hook 'org-mode-hook (lambda () (add-hook 'after-save-hook #'jw/org-babel-tangle-config)))

To execute or export code in org-mode code blocks, you’ll need to set up org-babel-load-languages for each language you’d like to use. This page documents all of the languages that you can use with org-babel.

(org-babel-do-load-languages
'org-babel-load-languages
'((emacs-lisp . t)
(python . t)))
(push '("conf-unix" . conf-unix) org-src-lang-modes)

visual-fill-column will create a document looking display with the extra padding on the left and on the right.

(defun jw/org-mode-visual-fill ()
(setq visual-fill-column-width 100
visual-fill-column-center-text t)
(visual-fill-column-mode 1))
(use-package visual-fill-column
:hook (org-mode . jw/org-mode-visual-fill)
(markdown-mode . jw/org-mode-visual-fill))
(with-eval-after-load 'ox-latex
(add-to-list 'org-latex-classes
'("org-plain-latex"
"\\documentclass{article}
[NO-DEFAULT-PACKAGES]
[PACKAGES]
[EXTRA]"
("\\section{%s}" . "\\section*{%s}")
("\\subsection{%s}" . "\\subsection*{%s}")
("\\subsubsection{%s}" . "\\subsubsection*{%s}")
("\\paragraph{%s}" . "\\paragraph*{%s}")
("\\subparagraph{%s}" . "\\subparagraph*{%s}")))
(add-to-list 'org-latex-classes
'("org-plain-no-section-numbering-latex"
"\\documentclass{article}
[NO-DEFAULT-PACKAGES]
[PACKAGES]
[EXTRA]"
("\\section*{%s}" . "\\section*{%s}")
("\\subsection*{%s}" . "\\subsection*{%s}")
("\\subsubsection*{%s}" . "\\subsubsection*{%s}")
("\\paragraph*{%s}" . "\\paragraph*{%s}")
("\\subparagraph*{%s}" . "\\subparagraph*{%s}"))))
(provide 'jw-emacs-org)
(require 'package)
(add-to-list 'package-archives
'("melpa" . "https://melpa.org/packages/") t)
(setq forge-add-default-bindings nil)
(use-package magit
:custom
(magit-display-buffer-function #'magit-display-buffer-same-window-except-diff-v1))
;; NOTE: Make sure to configure a GitHub token before using this package!
;; - https://magit.vc/manual/forge/Token-Creation.html#Token-Creation
;; - https://magit.vc/manual/ghub/Getting-Started.html#Getting-Started
(use-package forge
:after magit
)
(use-package evil-collection
:after (evil forge)
:config
(evil-collection-init)
(evil-collection-forge-setup))

When on the commit buffer, the argument for gpg-signing or -S may not be displayed. To resolve this issue manually, on the commit buffer menu, you must enter transient mode with C-x l and follow the prompting from there by typing the argument that you want to change the layering and then set the layering.

(provide 'jw-emacs-git)
(setq dired-listing-switches "-alD")

For macos, make sure to have coreutils installed. To install run, brew install coreutils

(setq insert-directory-program "gls"
dired-use-ls-dired t)
(provide 'jw-emacs-dired)

The jw-emacs-information-management.el module

Section titled “The jw-emacs-information-management.el module”
(use-package denote
:straight t)
(setq denote-directory (expand-file-name "~/Otzar/Gnosis/"))
(setq denote-save-buffer-after-creation nil)

Enable the denote dired mode for all files so that the components can easily be seen.

(add-hook 'dired-mode-hook #'denote-dired-mode)
(setq denote-known-keywords '("theology" "philosophy" "politics" "journal" "analysis" "linguistics"))
(setq denote-infer-keywords t)
(setq denote-sort-keywords t)
(setq denote-file-type nil) ; Org is the default, set others here
(setq denote-prompts '(subdirectory title keywords))
(setq denote-excluded-directories-regexp nil)
(setq denote-excluded-keywords-regexp nil)
(setq denote-rename-no-confirm nil) ; Set to t if you are familiar with `denote-rename-file'
;; Pick dates, where relevant, with Org's advanced interface:
(setq denote-date-prompt-use-org-read-date t)
;; Read this manual for how to specify `denote-templates'. We do not
;; include an example here to avoid potential confusion.
(setq denote-date-format nil) ; read doc string
;; By default, we do not show the context of links. We just display
;; file names. This provides a more informative view.
(setq denote-backlinks-show-context t)
;; Also see `denote-link-backlinks-display-buffer-action' which is a bit
;; advanced.
;; If you use Markdown or plain text files (Org renders links as buttons
;; right away)
(add-hook 'find-file-hook #'denote-fontify-links-mode-maybe)
(with-eval-after-load 'org-capture
(setq denote-org-capture-specifiers "%l\n%i\n%?")
(add-to-list 'org-capture-templates
'("n" "New note (with denote.el)" plain
(file denote-last-path)
#'denote-org-capture
:no-save t
:immediate-finish nil
:kill-buffer t
:jump-to-captured t)))
;; Also check the commands `denote-link-after-creating',
;; `denote-link-or-create'. You may want to bind them to keys as well.
;; If you want to have Denote commands available via a right click
;; context menu, use the following and then enable
;; `context-menu-mode'.
(add-hook 'context-menu-functions #'denote-context-menu)

Create the jw-emacs-org.el section for org-agenda —native org capture template leveraging the denote format.

;; Ensure denote.el is loaded
(require 'denote)
(defun jw-denote-weekly-tasks-filename ()
"Generate a Denote filename for a weekly tasks Org file in a custom directory and ensure the file exists.
The title is in the format 'YYYY: MONTH DD to DD', where DD to DD represents
the start and end days of the current week. The filename follows the Denote
convention with the '__tasks' tag."
(let* ((custom-directory "~/Otzar/Docs/agenda/") ; Specify your custom directory here
(today (current-time))
;; Calculate the start of the week (assuming Monday as the first day)
(start-of-week (time-subtract today (days-to-time (mod (nth 6 (decode-time today)) 7))))
;; Calculate the end of the week (Sunday)
(end-of-week (time-add start-of-week (days-to-time 6)))
;; Format the year and month from the start of the week
(year (format-time-string "%Y" start-of-week))
(month (format-time-string "%B" start-of-week))
(day-start (format-time-string "%d" start-of-week))
(day-end (format-time-string "%d" end-of-week))
;; Create the title in the format "YYYY MONTH DD to DD"
(title (format "%s: %s %s to %s" year month day-start day-end))
;; Generate the slug for the title
(slug (denote-sluggify-title title))
;; Generate the timestamp for the Denote filename
(timestamp (format-time-string "%Y%m%dT%H%M%S" start-of-week))
;; Construct the full filename with Denote convention
(filename (format "%s--%s__tasks.org" timestamp slug)))
;; Ensure the custom directory exists
(make-directory custom-directory t)
;; Generate the full file path
(let ((full-path (expand-file-name filename custom-directory)))
;; Create an empty file with Denote metadata if it doesn't exist
(unless (file-exists-p full-path)
(with-temp-buffer
(insert (format "#+title: %s\n#+date: %s\n#+filetags: :tasks:\n#+identifier: %s\n\n"
title
(format-time-string "[%Y-%m-%d %a %H:%M]" today)
timestamp))
(write-file full-path)))
full-path)))
;; Define the Org capture template
(setq org-capture-templates
'(("w" "Weekly Tasks" entry
(file jw-denote-weekly-tasks-filename)
""
:empty-lines 1
)))

Move the #<FILE># to a temporary directory instead of root directory.

(setq backup-directory-alist `(("." . ,(expand-file-name "tmp/backups/" user-emacs-directory))))
(setq lock-file-name-transforms
'(("\\`/.*/\\([^/]+\\)\\'" "/var/tmp/\\1" t)))

The jw-emacs-information-management.el call to provide

Section titled “The jw-emacs-information-management.el call to provide”
(provide 'jw-emacs-information-management)

Make sure to run M-x pdf-tools-install after installation.

(use-package pdf-tools
:straight t
:config
(pdf-tools-install)
:hook (pdf-view-mode . (lambda ()
(display-line-numbers-mode -1)
(message "PDF Tools activated for this buffer"))))

The issue with the 2025-01-14 is that if the installation works within the command line, when opening up a pdf file on Emacs would lead to the epdfserver crashing. This issue I found had to do with confict with macports being installed. If you uninstall macports, then the issue is resolved.

If you receive the option to rebuild the epdfserver and you agree to building on Emacs, there are instances where the build fails. When running M-x pdf-tools-install you will rebuild within Emacs and will obtain more information. If the error consists of not being able to find poppler, copy and paste the command used to run the installation and run it in the command line outside of emacs.

;; Ensure org-noter is installed
(use-package org-noter
:straight t
:after (org pdf-tools)
:config
(setq org-noter-always-create-frame nil))
;; Ensure org-pdftools is set up to work with org-mode
(use-package org-pdftools
:straight t
:hook (org-mode . org-pdftools-setup-link))
;; Configure org-noter-pdftools
(use-package org-noter-pdftools
:after (org-noter pdf-tools)
:config
;; Add a function to ensure precise note is inserted
(defun org-noter-pdftools-insert-precise-note (&optional toggle-no-questions)
(interactive "P")
(org-noter--with-valid-session
(let ((org-noter-insert-note-no-questions (if toggle-no-questions
(not org-noter-insert-note-no-questions)
org-noter-insert-note-no-questions))
(org-pdftools-use-isearch-link t)
(org-pdftools-use-freepointer-annot t))
(org-noter-insert-note (org-noter--get-precise-info)))))
;; Fix for the specific issue
(defun org-noter-set-start-location (&optional arg)
"When opening a session with this document, go to the current location.
With a prefix ARG, remove start location."
(interactive "P")
(org-noter--with-valid-session
(let ((inhibit-read-only t)
(ast (org-noter--parse-root))
(location (org-noter--doc-approx-location (when (called-interactively-p 'any) 'interactive))))
(with-current-buffer (org-noter--session-notes-buffer session)
(org-with-wide-buffer
(goto-char (org-element-property :begin ast))
(if arg
(org-entry-delete nil org-noter-property-note-location)
(org-entry-put nil org-noter-property-note-location
(org-noter--pretty-print-location location))))))))
;; Add a hook for pdf-annot
(with-eval-after-load 'pdf-annot
(add-hook 'pdf-annot-activate-handler-functions #'org-noter-pdftools-jump-to-note))
;; If you are working with EPUB files
(use-package nov
:straight t)
;; If you are working with DJVU files
(use-package djvu
:straight t))

The jw-emacs-productivity.el call to provide

Section titled “The jw-emacs-productivity.el call to provide”
(provide 'jw-emacs-productivity)
(use-package tramp
:straight t)
(setq tramp-default-method "ssh")
;; for debugging
(setq tramp-verbose 3)
;; some basic performance enhancements
(setq remote-file-name-inhibit-locks t
tramp-use-scp-direct-remote-copying t
remote-file-name-inhibit-auto-save-visited t)
;; disable backup files for tramp
(add-to-list 'backup-directory-alist
(cons tramp-file-name-regexp nil))
(setq tramp-connection-timeout 10) ;; 10 sec timeout

Guide by this blog post.

(connection-local-set-profile-variables
'remote-direct-async-process
'((tramp-direct-async-process . t)))
(connection-local-set-profiles
'(:application tramp :protocol "scp")
'remote-direct-async-process)
(setq magit-tramp-pipe-stty-settings 'pty)

compile command disables remote ssh connection sharing, which will require you to reenter your password each time you connect. Want to enable this for convienence.

(with-eval-after-load 'tramp
(with-eval-after-load 'compile
(remove-hook 'compilation-mode-hook #'tramp-compile-disable-ssh-controlmaster-options)))

Cache passwords until the end of the emacs session, which is default.

(setq password-cache-expiry nil)
;; Configure TRAMP to use ~/.emacs.d/tmp/ for caching
(let ((tramp-tmp-dir (expand-file-name "tmp/" user-emacs-directory)))
;; Ensure the directory exists
(unless (file-directory-p tramp-tmp-dir)
(make-directory tramp-tmp-dir t))
;; Set TRAMP cache directory
(setq tramp-persistency-file-name (expand-file-name "tramp-cache" tramp-tmp-dir))
;; Set auto-save directory for remote files
(setq tramp-auto-save-directory tramp-tmp-dir)
;; Optional: Set backup directory for remote files to tmp as well
(setq tramp-backup-directory-alist `(("." . ,tramp-tmp-dir))))
;; Enable persistent caching
(setq tramp-cache-read-persistent-data t)
(setq tramp-cache-compress t)
(setq remote-file-name-inhibit-cache nil)
(defun memoize-remote (key cache orig-fn &rest args)
"Memoize a value if the key is a remote path."
(if (and key
(file-remote-p key))
(if-let ((current (assoc key (symbol-value cache))))
(cdr current)
(let ((current (apply orig-fn args)))
(set cache (cons (cons key current) (symbol-value cache)))
current))
(apply orig-fn args)))
;; Memoize current project
(defvar project-current-cache nil)
(defun memoize-project-current (orig &optional prompt directory)
(memoize-remote (or directory
project-current-directory-override
default-directory)
'project-current-cache orig prompt directory))
(advice-add 'project-current :around #'memoize-project-current)
;; Memoize magit top level
(defvar magit-toplevel-cache nil)
(defun memoize-magit-toplevel (orig &optional directory)
(memoize-remote (or directory default-directory)
'magit-toplevel-cache orig directory))
(advice-add 'magit-toplevel :around #'memoize-magit-toplevel)
;; memoize vc-git-root
(defvar vc-git-root-cache nil)
(defun memoize-vc-git-root (orig file)
(let ((value (memoize-remote (file-name-directory file) 'vc-git-root-cache orig file)))
;; sometimes vc-git-root returns nil even when there is a root there
(when (null (cdr (car vc-git-root-cache)))
(setq vc-git-root-cache (cdr vc-git-root-cache)))
value))
(advice-add 'vc-git-root :around #'memoize-vc-git-root)
;; memoize all git candidates in the current project
(defvar $counsel-git-cands-cache nil)
(defun $memoize-counsel-git-cands (orig dir)
($memoize-remote (magit-toplevel dir) '$counsel-git-cands-cache orig dir))
(advice-add 'counsel-git-cands :around #'$memoize-counsel-git-cands)
;; Optional: Function to clear cache when needed
(defun jw/clear-tramp-cache ()
"Clear TRAMP cache files in ~/.emacs.d/tmp/"
(interactive)
(let ((cache-file tramp-persistency-file-name))
(when (file-exists-p cache-file)
(delete-file cache-file)
(message "TRAMP cache cleared"))))
(require 'project)

Set the project paths, but currently the code below only works for emacs 30+.

(defun jw/project-prompter ()
(read-file-name "Select a project folder:"
"~/Otzar/Projects/Code/"
nil
nil
nil
#'file-directory-p))
(setq project-prompter #'jw/project-prompter)

show-paren-mode allows one to see matching pairs of parentheses and other characters. When point is on the opening character of one of the paired characters, the other is highlighted. When the point is after the closing character of one of the paired characters, the other is highlighted.

(show-paren-mode 1)

To visualize all delimiters,

rainbow-delimiters is useful in programming modes because it colorizes nested parentheses and brackets according to their nesting depth. This makes it a lot easier to visually match parentheses in Emacs Lisp code without having to count them yourself.

(use-package rainbow-delimiters
:hook (prog-mode . rainbow-delimiters-mode))

electric-pair-mode will auto pair delimiters for you. One issue with the auto pairing is the < character in org-mode. The following hook to the enabling of electric-pair-mode aims to solve the issue when in org-mode.

(electric-pair-mode t)
(add-hook 'org-mode-hook (lambda ()
(setq-local electric-pair-inhibit-predicate
`(lambda (c)
(if (char-equal c ?<) t (,electric-pair-inhibit-predicate c))))))

evil-surround emulates surround.vim. For usage instructions visit evil-surround

(use-package evil-surround
:straight t
:config
(global-evil-surround-mode 1))

For logging keys,

command-log-mode is useful for displaying a panel showing each key binding you use in a panel on the right side of the frame. Great for live streams and screencasts!

(use-package command-log-mode)

To activate command-log-mode you must first run M-x global-command-log-mode to have command-log-mode in every buffer and then run M-x clm/toggle-command-log-buffer to have the buffer be displayed.

The jw-emacs-development.el call to provide

Section titled “The jw-emacs-development.el call to provide”
(provide 'jw-emacs-development)

There is a user option in my setup to load this module (The init.el option to enable which-key).

When the which-key-mode is enabled, any incomplete key sequence will produce a popup at the lower part of the Emacs frame showing keys that complete the current sequence together with the name of the command they are invoking.

(use-package which-key
:straight t
:hook (after-init . which-key-mode)
:config
(setq which-key-separator " ")
(setq which-key-prefix-prefix "... ")
(setq which-key-max-display-columns 3)
(setq which-key-idle-delay 1.5)
(setq which-key-idle-secondary-delay 0.25)
(setq which-key-add-column-padding 1)
(setq which-key-max-description-length 40))
(provide 'jw-emacs-which-key)

The purpose of this module is to have my integrations with llms or other ai models.

Incorporates the use of llms in the emacs client. For a great summary of the features please see Ben Simon’s video. For accessing the source code please see karthink’s repo.

(use-package gptel
:straight t
:after auth-source
:init
;; Ensure auth-source is configured to find ~/.authinfo or ~/.authinfo.gpg
(setq auth-sources '("~/.authinfo.gpg" "~/.authinfo" "~/.netrc"))
:config
;; Helper function to read file contents
(defun gptel-read-file (file-path)
"Read the contents of FILE-PATH into a string, trimming whitespace."
(if (and (file-exists-p file-path) (file-readable-p file-path))
(with-temp-buffer
(insert-file-contents file-path)
(string-trim (buffer-string)))
(progn
(message "Warning: File %s is not readable or does not exist" file-path)
"You are a polymath who is a helpful assistant. Respond concisely and accurately.")))
;; Define directives with file paths
(setq gptel-directives
(list
(cons 'default "You are a polymath who is a helpful assistant. Respond concisely and accurately.")
(cons 'coding (gptel-read-file "~/.dotfiles/.assets/gpt-prompts/coding.txt"))
(cons 'writing (gptel-read-file "~/.dotfiles/.assets/gpt-prompts/writing.txt"))
(cons 'research (gptel-read-file "~/.dotfiles/.assets/gpt-prompts/research.txt"))))
;; Set default directive
(setq gptel-default-directive 'coding)
;; Refresh directives dynamically
(defun gptel-refresh-directives ()
"Refresh gptel-directives by re-reading files."
(interactive)
(setq gptel-directives
(list
(cons 'default "You are a polymath who is a helpful assistant. Respond concisely and accurately.")
(cons 'coding (gptel-read-file "~/.dotfiles/.assets/gpt-prompts/coding.txt"))
(cons 'writing (gptel-read-file "~/.dotfiles/.assets/gpt-prompts/writing.txt"))
(cons 'research (gptel-read-file "~/.dotfiles/.assets/gpt-prompts/research.txt"))))
(message "Refreshed gptel-directives from files."))
;; Set OpenAI API key using gptel-api-key-from-auth-source
(setq gptel-api-key (lambda ()
(auth-source-forget-all-cached)
(gptel-api-key-from-auth-source)))
;; Configure Google (Gemini) backend
(gptel-make-gemini "Gemini" :stream t
:key (lambda ()
(auth-source-forget-all-cached)
(gptel-api-key-from-auth-source "generativelanguage.googleapis.com")))
;; Configure Anthropic (Claude) backend
(gptel-make-anthropic "Claude"
:stream t
:key (lambda ()
(auth-source-forget-all-cached)
(gptel-api-key-from-auth-source "console.anthropic.com")))
;; Optional: Enable debugging for auth-source issues
;; (setq auth-source-debug t)
)
(setq gptel-display-buffer-action
'(display-buffer-in-side-window
(side . right)
(window-width . 0.4)))
(defun gptel-save-response ()
"Save the entire gptel buffer to a file with a user-provided name."
(interactive)
(unless (bound-and-true-p gptel-mode)
(user-error "This command must be run in a gptel-mode buffer"))
(let* ((response (buffer-string))
(user-input (read-string "Enter a concise (2-5 words) filename description: ")))
(if (string-empty-p response)
(message "Error: Buffer is empty, cannot save file")
(let* ((clean-name (if (and user-input (stringp user-input) (not (string-empty-p user-input)))
(string-trim (replace-regexp-in-string "[^a-zA-Z0-9-]" "" (replace-regexp-in-string "\\s+" "-" user-input)))
"fallback-name"))
(timestamp (format-time-string "%Y%m%dT%H%M%S"))
(base-dir "~/Otzar/llm-outputs/")
(filename (concat (file-name-as-directory (expand-file-name base-dir)) timestamp "--" clean-name ".md")))
(condition-case err
(progn
(make-directory base-dir t)
(write-region (point-min) (point-max) filename nil 'silent)
(message "Saved buffer to %s" filename))
(error
(message "Error saving file: %s" err)))))))
(provide 'jw-emacs-ai)

Set language sources for treesit

(setq treesit-language-source-alist
'((typescript . ("https://github.com/tree-sitter/tree-sitter-typescript" "master" "typescript/src"))
(tsx . ("https://github.com/tree-sitter/tree-sitter-typescript" "master" "tsx/src"))
(python . ("https://github.com/tree-sitter/tree-sitter-python"))
(json "https://github.com/tree-sitter/tree-sitter-json")
(css "https://github.com/tree-sitter/tree-sitter-css")
(html "https://github.com/tree-sitter/tree-sitter-html")
(yaml "https://github.com/ikatyang/tree-sitter-yaml")
(toml "https://github.com/tree-sitter/tree-sitter-toml")
(make "https://github.com/alemuller/tree-sitter-make")
(markdown "https://github.com/ikatyang/tree-sitter-markdown")
(elisp "https://github.com/Wilfred/tree-sitter-elisp")
(cmake "https://github.com/uyha/tree-sitter-cmake")
(c "https://github.com/tree-sitter/tree-sitter-c")
(cpp "https://github.com/tree-sitter/tree-sitter-cpp")
(r "https://github.com/r-lib/tree-sitter-r")
(bash "https://github.com/tree-sitter/tree-sitter-bash")
))
(dolist (source treesit-language-source-alist)
(unless (treesit-ready-p (car source))
(treesit-install-language-grammar (car source))))
(add-to-list 'auto-mode-alist '("\\.ts\\'" . typescript-ts-mode))
(add-to-list 'auto-mode-alist '("\\.tsx\\'" . tsx-ts-mode))
(add-to-list 'auto-mode-alist '("\\.cpp\\'" . c++-ts-mode))
(add-to-list 'auto-mode-alist '("\\.c\\'" . c-ts-mode))
(add-to-list 'auto-mode-alist '("\\.mdx\\'" . markdown-mode))
(add-to-list 'major-mode-remap-alist '(
(python-mode . python-ts-mode)
(json-mode . json-ts-mode)
(css-mode . css-ts-mode)
(bash-mode . bash-ts-mode)
(yaml-mode . yaml-ts-mode)
(c++-mode . c++-ts-mode)
(c-mode . c-ts-mode)
))

The issue with the built in treesit.el is that it does not auto default to which language server. In addition if you need to install you will have to input the url yourself. This package is here to automate the process.

(use-package treesit-auto
:straight t
:custom
(treesit-auto-install 'prompt)
:config
(treesit-auto-add-to-auto-mode-alist 'all)
(global-treesit-auto-mode))

Auto install grammars when missing

(setq treesit-auto-install 'prompt)
(use-package auctex
:straight t
:defer t
:init
(setq TeX-auto-save t)
(setq TeX-parse-self t)
(setq TeX-PDF-mode t) ; Enable PDF output by default
:config
;; Set the default engine
(setq TeX-engine 'default)
;; Put auxiliary files in a tmp subdirectory
(setq TeX-output-dir "tmp/")
(setq LaTeX-output-directory "tmp/")
;; Simplified PDF viewer configuration for macOS
(when (eq system-type 'darwin) ; macOS only
(setq TeX-view-program-list '(("Preview.app" "open -a Preview.app %o")
("Skim" "open -a Skim.app %o")
("displayline" "displayline -g -b %n %o %b")
("open" "open %o")))
(setq TeX-view-program-selection '((output-pdf "Skim"))))
;; For non-macOS systems, use default viewer
(unless (eq system-type 'darwin)
(setq TeX-view-program-selection '((output-pdf "PDF Tools"))))
;; Ensure we have a default command
(setq TeX-command-default "LaTeX")
;; Auto-revert PDF files when they change
(add-hook 'TeX-after-compilation-finished-functions
#'TeX-revert-document-buffer)
;; LaTeX mode hooks
(add-hook 'LaTeX-mode-hook 'visual-line-mode) ; Enable word wrap
(add-hook 'LaTeX-mode-hook 'flyspell-mode) ; Enable spell checking
(add-hook 'LaTeX-mode-hook 'LaTeX-math-mode) ; Enable math mode
(add-hook 'LaTeX-mode-hook 'turn-on-reftex) ; Enable RefTeX
;; Ensure TeX-command-run-all works properly
(add-hook 'LaTeX-mode-hook
(lambda ()
;; Make sure the master file is set
(when (and (buffer-file-name)
(not TeX-master))
(setq-local TeX-master (file-name-sans-extension
(file-name-nondirectory (buffer-file-name)))))))
;; Auto-compile on save
(add-hook 'LaTeX-mode-hook
(lambda ()
(add-hook 'after-save-hook
(lambda () (TeX-command-run-all nil))
nil 'make-it-local)))
;; RefTeX configuration
(setq reftex-plug-into-AUCTeX t))

C-c C-a (TeX-command-run-all): compiles tex document and auto opens in the pdf viewer

Configuring pythone envs with conda.

(use-package conda
:straight t
:config
(setq conda-anaconda-home (expand-file-name "/opt/homebrew/Caskroom/miniconda/base/"))
(setq conda-env-home-directory (expand-file-name "/opt/homebrew/Caskroom/miniconda/base/envs/"))
(conda-env-autoactivate-mode t))

Python formatter configuration.

(use-package python-black
:demand t
:after python
:hook (python-ts-mode . python-black-on-save-mode))

I used the following guide from medium for this configuration.

;; WEB MODE
(use-package web-mode
:straight t)
;; astro
;; ASTRO
(define-derived-mode astro-mode web-mode "astro")
(setq auto-mode-alist
(append '((".*\\.astro\\'" . astro-mode))
auto-mode-alist))

Now set the config in eglot.

Download rust-mode.

(use-package rust-mode
:straight t
:mode "\\.rs\\'"
:config
(add-to-list 'auto-mode-alist '("\\.rs\\'" . rust-mode)))

Setting up rust-mode.

Custom function to find rust analyzer.

(defun jw/find-rust-analyzer ()
(or (executable-find "rust-analyzer")
(expand-file-name "~/.cargo/bin/rust-analyzer")))

Typescript support is done through treesit.el, which is now native to emacs as of v29.

Therefore all of the typescript will be using the tree-sitter equivalent.

;; (use-package typescript-mode
;; :ensure t
;; :mode "\\.ts\\'")

Install json-mode

;; (use-package json-mode
;; :ensure t
;; :mode "\\.json\\'")

Make sure you have the necessary packages installed.

(use-package apheleia
:straight t
:config
(setf (alist-get 'prettier-json apheleia-formatters)
'("prettier" "--stdin-filepath" filepath))
;; Map json-ts-mode to the prettier-json formatter
(setf (alist-get 'json-ts-mode apheleia-mode-alist)
'(prettier-json))
(add-to-list 'apheleia-mode-alist '(tsx-ts-mode . prettier))
(add-to-list 'apheleia-mode-alist '(typescript-ts-mode . prettier))
(add-to-list 'apheleia-mode-alist '(c++-ts-mode . clang-format))
(add-to-list 'apheleia-mode-alist '(c-ts-mode . clang-format))
(apheleia-global-mode +1))

:PROPERTIES: :ID: F9D087EE-895F-4DBC-BBCF-3056A2A5266E :END:

;; Dynamic server program functions
(defun jw/python-lsp-program (&optional interactive)
"Get Python LSP program."
(if (file-remote-p default-directory)
'("/home/jozhw/bin/pylsp-wrapper")
'("/opt/homebrew/Caskroom/miniconda/base/bin/pyright-langserver" "--stdio")))
(defun jw/rust-lsp-program (&optional interactive)
"Get Rust LSP program."
(list (jw/find-rust-analyzer)))
(defun jw/clangd-lsp-program (&optional interactive)
"Get clangd LSP program."
'("clangd"))
(defun jw/typescript-lsp-program (&optional interactive)
"Get TypeScript LSP program."
'("typescript-language-server" "--stdio"))
(defun jw/marksman-lsp-program (&optional interactive)
"Get Marksman LSP program."
'("marksman"))
(defun jw/astro-lsp-program (&optional interactive)
"Get Astro LSP program."
'("astro-ls" "--stdio" :initializationOptions (:typescript (:tsdk "./node_modules/typescript/lib"))))
(defun jw/tex-lsp-program (&optional interactive)
"Get latex lsp program"
'("texlab")
)

Add to eglot server list and setup hook after eglot is loaded.

;; Enhanced eglot configuration
(with-eval-after-load 'eglot
(setq eglot-prefer-local-server t)
;; undo elgot modifications of completion-category-defaults
(setq completion-category-defaults nil)
(setq eglot-connect-timeout 120)
;; Use function symbols - eglot will call these functions to get the command
(add-to-list 'eglot-server-programs
'(python-ts-mode . jw/python-lsp-program))
(add-to-list 'eglot-server-programs
'(rust-mode . jw/rust-lsp-program))
(add-to-list 'eglot-server-programs
'((c++-ts-mode c-ts-mode) . jw/clangd-lsp-program))
(add-to-list 'eglot-server-programs
'(typescript-ts-mode . jw/typescript-lsp-program))
(add-to-list 'eglot-server-programs
'(tsx-ts-mode . jw/typescript-lsp-program))
(add-to-list 'eglot-server-programs
'(markdown-mode . jw/marksman-lsp-program))
(add-to-list 'eglot-server-programs
'((latex-mode tex-mode LaTex-mode) . jw/tex-lsp-program))
(add-to-list 'eglot-server-programs
'(astro-mode . jw/astro-lsp-program)))

Function to start eglot.

;; Function to start eglot
(defun jw/maybe-start-eglot ()
"Start eglot if current mode is supported and file is not remote."
(when (and (not (file-remote-p default-directory))
(or (derived-mode-p 'python-mode)
(derived-mode-p 'python-ts-mode)
(derived-mode-p 'rust-mode)
(derived-mode-p 'tex-mode)
(derived-mode-p 'c-ts-mode)
(derived-mode-p 'c++-ts-mode)
(derived-mode-p 'typescript-ts-mode)
(derived-mode-p 'tsx-ts-mode)
(derived-mode-p 'markdown-mode)
(derived-mode-p 'astro-mode)))
(eglot-ensure)))
;; Helper function to restart eglot in current buffer
(defun jw/restart-eglot ()
"Restart eglot in current buffer."
(interactive)
(when (eglot-current-server)
(eglot-shutdown (eglot-current-server))
(eglot-ensure)))

Add the hook to auto start eglot depending on configured language.

(add-hook 'python-ts-mode-hook #'jw/maybe-start-eglot)
(add-hook 'rust-mode-hook #'jw/maybe-start-eglot)
(add-hook 'c-ts-mode-hook #'jw/maybe-start-eglot)
(add-hook 'c++-ts-mode-hook #'jw/maybe-start-eglot)
(add-hook 'typescript-ts-mode-hook #'jw/maybe-start-eglot)
(add-hook 'tsx-ts-mode-hook #'jw/maybe-start-eglot)
(add-hook 'markdown-mode-hook #'jw/maybe-start-eglot)
(add-hook 'astro-mode-hook #'jw/maybe-start-eglot)
(add-hook 'tex-mode-hook #'jw/maybe-start-eglot)

For debuggin,

(use-package dape
:straight t
;; :preface
;; By default dape shares the same keybinding prefix as `gud'
;; If you do not want to use any prefix, set it to nil.
;; (setq dape-key-prefix "\C-x\C-a")
:hook
;; Save breakpoints on quit
(kill-emacs . dape-breakpoint-save)
;; Load breakpoints on startup
;; (after-init . dape-breakpoint-load)
:config
;; Turn on global bindings for setting breakpoints with mouse
;; (dape-breakpoint-global-mode)
;; Info buffers to the right
(setq dape-buffer-window-arrangement 'right)
;; Info buffers like gud (gdb-mi)
(setq dape-buffer-window-arrangement 'gud)
(setq dape-info-hide-mode-line nil)
;; Pulse source line (performance hit)
(add-hook 'dape-display-source-hook 'pulse-momentary-highlight-one-line)
;; Showing inlay hints
(setq dape-inlay-hints t)
;; Save buffers on startup, useful for interpreted languages
(add-hook 'dape-start-hook (lambda () (save-some-buffers t t)))
;; Kill compile buffer on build success
(add-hook 'dape-compile-hook 'kill-buffer)
;; Projectile users
;; (setq dape-cwd-function 'projectile-project-root)
)
;; Enable repeat mode for more ergonomic `dape' use
(use-package repeat
:config
(repeat-mode))
;; for remote configs
(with-eval-after-load 'tramp
(require 'tramp-sh)
(setq tramp-own-remote-path '("/bin" "/usr/bin" "/usr/local/bin"))
(add-to-list 'tramp-remote-path 'tramp-own-remote-path))
(provide 'jw-emacs-langs)