jishnusen / emacs-config

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Doom Emacs Configuration

This file is my so called “literate” Doom Emacs configuration. It is automatically tangled by doom into config.el when the file is edited in Doom or I run doom sync. I’m currently using this file to:

  1. Store modifications to functions/variables used by Doom itself, such as theme and UX.
  2. Environment-specific changes
  3. Emacs package (mostly Doom package) configurations
  4. Utility function definitions

Setup

Unfortunately the Doom HEAD is unstable, and there is no release tagging system. I by chance found commit 03d692f129633e3bf0bd100d91b3ebf3f77db6d1 to be stable, so remember to check it out, we do so as follows:

git clone --single-branch https://github.com/doomemacs/doomemacs ~/.config/emacs
pushd ~/.config/emacs
git checkout 03d692f129633e3bf0bd100d91b3ebf3f77db6d1
popd
~/.config/emacs/bin/doom install && ~/.config/emacs/bin/doom sync

Remember to install the Iosevka font! This will reveal itself as an issue when you run emacs --debug-init

Doom theme definitions

We start with setting doom-builtin theme modifiers. These propagate to Emacs variables elsewhere.

;; -*- lexical-binding: t -*-

(defun font-fallback (fonts)
  (cond
   ((not (car fonts)) nil)
   ((member (car fonts) (font-family-list)) (car fonts))
   (t (font-fallback (cdr fonts)))
   ))
(setq doom-theme 'modus-operandi
      doom-font (font-spec :family (font-fallback '("PragmataPro" "Iosevka"))
                           :size (if IS-MAC 14 18))
      doom-unicode-font (font-spec :family (font-fallback '("PragmataPro" "Iosevka")))
      doom-themes-enable-bold nil)
(defvar after-load-theme-hook nil
  "Hook run after a color theme is loaded using `load-theme'.")
(defadvice load-theme (after run-after-load-theme-hook activate)
  "Run `after-load-theme-hook'."
  (run-hooks 'after-load-theme-hook))
(add-hook 'after-load-theme-hook (lambda () (set-face-bold-p 'bold nil)))

Startup Options

And startup options/hacks. These are required and may be specific to my system.

(add-to-list 'initial-frame-alist '(fullscreen . maximized))
(setq shell-file-name (executable-find "bash")
      conda-env-home-directory (expand-file-name "~/miniconda3/")
      yas-triggers-in-field t
      )
(add-hook 'text-mode-hook #'auto-fill-mode)
(after! persp-mode
  (setq persp-emacsclient-init-frame-behaviour-override "main")
  )
(put 'narrow-to-region 'disabled nil)

Line numbers

I have line numbers disabled in the interest of performance. See https://discourse.doomemacs.org/t/why-is-emacs-doom-slow/83/3.

;; This determines the style of line numbers in effect. If set to `nil', line
;; numbers are disabled. For relative line numbers, set this to `relative'.
(setq display-line-numbers-type nil)

Org Mode

(setq org-directory "~/Documents/org/")

Keybinds

Vanilla Binds

These are “global” binds, so make sure to use keys that can be accessed from any Evil mode.

(map! :g "C-x C-k" #'kill-this-buffer)

Evil Mode Binds

These binds are specific to Evil modes (normal, insert, etc.)

;; disable recording macros (i'm too dumb for this feature i think)
(map! :n "q" nil)
(map! :n "J" "5j")
(map! :n "K" "5k")

Package Configurations

Magit

I try to use magit to interface with git. But, since my dotfiles are cloned as a bare repo, Magit is unable to see them. This function makes magit check if the CWD is tracked by my dotfiles clone. This has the clone location of my dotfiles hardcoded, maybe I will find a better way to implement this some other time.

(defun my/magit-process-environment (env)
  "Detect and set git -bare repo env vars when in tracked dotfile directories."
  (let* ((default (file-name-as-directory (expand-file-name default-directory)))
         (git-dir (expand-file-name "~/.dots/"))
         (work-tree (expand-file-name "~/"))
         (dotfile-dirs
          (-map (apply-partially 'concat work-tree)
                (-uniq (-keep #'file-name-directory
                              (split-string
                               (shell-command-to-string
                                (format "/usr/bin/git --git-dir=%s --work-tree=%s ls-tree --full-tree --name-only -r HEAD"
                                        git-dir work-tree))))))))
    (push work-tree dotfile-dirs)
    (when (member default dotfile-dirs)
      (push (format "GIT_WORK_TREE=%s" work-tree) env)
      (push (format "GIT_DIR=%s" git-dir) env)))
  env)

(advice-add 'magit-process-environment
            :filter-return #'my/magit-process-environment)

LaTeX

AUCTeX

(setq TeX-save-query nil
      TeX-command-extra-options "-shell-escape")
(after! latex
  (add-to-list 'TeX-command-list '("XeLaTeX" "%`xelatex%(mode)%' %t" TeX-run-TeX nil t))
  (advice-add 'TeX-pdf-tools-sync-view :around #'my-display-buffer-right)
  )
(setq +latex-viewers '(pdf-tools evince zathura okular skim sumatrapdf)
      font-latex-fontify-script nil
      font-latex-fontify-sectioning 1.0
      )
(defun prettify-setup ()
  ;; pretty unicodisms that arent default
  (push '("\\implies" . "") prettify-symbols-alist)
  (push '("\\impliedby" . "") prettify-symbols-alist)
  (push '("\\land" . "") prettify-symbols-alist)
  (push '("\\lor" . "") prettify-symbols-alist)
  (push '("\\dots" . 8230) prettify-symbols-alist)
  (push '("\\varnothing" . "") prettify-symbols-alist)
  (push '("\\upharpoonright" . "") prettify-symbols-alist)
  (push '("\\mhyphen" . "-") prettify-symbols-alist)
  (push (cons "\\textdegree{}" (cdr (assoc "\\textdegree" prettify-symbols-alist))) prettify-symbols-alist)
  (push (cons "\\Z" (cdr (assoc "\\mathbb{Z}" prettify-symbols-alist))) prettify-symbols-alist)
  (push (cons "\\N" (cdr (assoc "\\mathbb{N}" prettify-symbols-alist))) prettify-symbols-alist)
  (push (cons "\\R" (cdr (assoc "\\mathbb{R}" prettify-symbols-alist))) prettify-symbols-alist)
  (push (cons "\\Q" (cdr (assoc "\\mathbb{Q}" prettify-symbols-alist))) prettify-symbols-alist)
  (push (cons "\\C" "") prettify-symbols-alist)
  (when (not (string= (font-get doom-font :family) "PragmataPro"))
    (dotimes (l 26)
      ;; mathcal -> bold-italic math script
      (add-to-list 'prettify-symbols-alist (cons (concat "\\mathcal{" (byte-to-string (+ ?A l)) "}") (+ 120380 l))))
    )
  (dotimes (l 26)
    ;; mathscr -> mathcal
    (let* ((letter (byte-to-string (+ ?A l)))
           (cal (concat "\\mathcal{" letter "}"))
           (scr (concat "\\mathscr{" letter "}")))
      (add-to-list 'prettify-symbols-alist (cons scr (cdr (assoc cal prettify-symbols-alist))))
      ))
  (prettify-symbols-mode t)
  )

(add-hook 'LaTeX-mode-hook
          (lambda ()
            (make-local-variable 'line-move-visual)
            ;; stop autocomplete when i'm typing english
            (setq-local company-minimum-prefix-length 5)
            ;; reload file local from template
            (setq TeX-insert-macro-default-style 'mandatory-args-only)
            (prettify-setup)
            ))

LAAS

This is a package outside doom from tecosaur that sets up auto-inserting snippets for latex (and other languages). I need some extra snippets for environment insertion since I don’t use CDLatex. To get this to work, we first make a function to expand YAS snippets to specify them easily:

(defun insnip (str)
  (lambda () (interactive) (yas-expand-snippet str)))

Then, I enable LAAS-mode on LaTeX files, and configure my yas snippets that I want to be auto-inserted. TODO: I may want to save these in my yas snips directory.

(use-package! laas
  :hook (LaTeX-mode . laas-mode)
  :config
  (setq laas-enable-auto-space nil)
  (aas-set-snippets 'laas-mode
    :cond (lambda () (not (laas-mathp)))
    "dm" (insnip "\\[\n$0\n\\]")
    :cond (lambda () (laas-mathp))
    "'o" (lambda () (interactive) (laas-wrap-previous-object "mathbb"))
    ;; accent pairs
    :cond #'laas-object-on-left-condition
    "qq" (lambda () (interactive) (laas-wrap-previous-object "sqrt"))
    "'s" (lambda () (interactive) (laas-wrap-previous-object "mathscr"))
    )
  )

Spell Check

Uses spell-fu for spell check.

(after! spell-fu
  (setq ispell-personal-dictionary (concat doom-user-dir "misc/ispell_personal")
        )
  (cl-pushnew 'font-lock-constant-face (alist-get 'latex-mode +spell-excluded-faces-alist))
  (ispell-check-version) ;; hack, apparently this makes ispell set its vars correctly
  )

PDF Tools

For previews

(defun display-buffer-beside-selected (buffer alist)
  "Try displaying BUFFER in a window beside the selected window.
If there is a window below the selected one and that window
already displays BUFFER, use that window.
If that attempt fails and there is a non-dedicated window
beside the selected one, use that window.
The left or right hand side is chosen if ALIST contains
the cons (side . left) or (side . right), respectively."
  (let (window)
    (or (and (setq window (window-in-direction (cdr (assq 'side alist))))
         (eq buffer (window-buffer window))
         (window--display-buffer buffer window 'reuse alist))
    (and (setq window (window-in-direction (cdr (assq 'side alist))))
         (not (window-dedicated-p window))
         (window--display-buffer
          buffer window 'reuse alist)))))

(defun my-display-buffer-right (fun &rest args)
  "Use `display-buffer-in-side-window' as `display-buffer-overriding-action'.
Then run FUN with ARGS."
  (let ((display-buffer-overriding-action '(display-buffer-beside-selected (side . right))))
    (apply fun args)))

(defun my-display-buffer-left (fun &rest args)
  "Use `display-buffer-in-side-window' as `display-buffer-overriding-action'.
Then run FUN with ARGS."
  (let ((display-buffer-overriding-action '(display-buffer-beside-selected (side . left))))
    (apply fun args)))

(use-package! pdf-tools
  :defer t
  :config
  (setq pdf-sync-backward-display-action t)
  (setq pdf-sync-forward-display-action t)
  (setq-default pdf-view-display-size 'fit-page)
  (advice-add 'pdf-sync-backward-search-mouse :around #'my-display-buffer-left)
  )

Common Lisp

Set up SLY, defaults are sane but I want a fresh repl for every file.

(after! common-lisp
  (setq sly-command-switch-to-existing-lisp 'never)
  )

Org

inception :)

I have a lot of macros in my LaTeX preamble that are compatible with MathJax. To use them, I set up a babel language to read macros in the HTML header. See the Emacs stack exchange post.

(after! org
  (setq org-highlight-latex-and-related '(native script entities))
  (add-to-list 'org-src-lang-modes '("latex-macros" . latex))

  (defvar org-babel-default-header-args:latex-macros
    '((:results . "raw")
      (:exports . "results")))

  (defun prefix-all-lines (pre body)
    (with-temp-buffer
      (insert body)
      (string-insert-rectangle (point-min) (point-max) pre)
      (buffer-string)))

  (defun org-babel-execute:latex-macros (body _params)
    (concat
     (prefix-all-lines "#+LATEX_HEADER: " body)
     "\n#+HTML_HEAD_EXTRA: <div style=\"display: none\"> \\(\n"
     (prefix-all-lines "#+HTML_HEAD_EXTRA: " body)
     "\n#+HTML_HEAD_EXTRA: \\)</div>\n"))

  (org-eldoc-load)
  )

YAS

Not to be confused with LAAS, YAS is the snippet package I use for TAB-inserted snippets. It also supports the following macro for inserting a snippet (which I define in snippets/{ftype}/__) based on file type.

I also use YAS to insert a template for when I open a new text file. In the case of LaTeX, I have two templates; a light one for homework to compile quickly, and a heavy one with tikz, and a million other packages + macros for typesetting reports, etc. The light one is abbreviated to __light, so the following is just a function that rips off the y-n prompt to ask the user.

(defun insert-snippet-abbr (abbr)
  "Insert the snippet abbreviated to abbr"
  (progn
    (insert abbr)
    (call-interactively 'yas-expand)))

(defun ask-light ()
  "Use Preamble or preamble_light."
  (if (y-or-n-p "insert light preamble?")
      (insert-snippet-abbr "__light")
      (insert-snippet-abbr "__")
      )
  )

Next, we have to bind the templates to their filetypes and major modes :).

(set-file-template! "\\.tex$" :trigger #'ask-light :mode 'latex-mode)
(set-file-template! "\\.org$" :trigger "__" :mode 'org-mode)
(set-file-template! "/LICEN[CS]E$" :trigger '+file-templates/insert-license)

These are a set of functions taken from tecosaur’s config to make the src block insertion snippet work. They are used inside my snippet definitions.

(defun +yas/org-src-header-p ()
  "Determine whether `point' is within a src-block header or header-args."
  (pcase (org-element-type (org-element-context))
    ('src-block (< (point) ; before code part of the src-block
                   (save-excursion (goto-char (org-element-property :begin (org-element-context)))
                                   (forward-line 1)
                                   (point))))
    ('inline-src-block (< (point) ; before code part of the inline-src-block
                          (save-excursion (goto-char (org-element-property :begin (org-element-context)))
                                          (search-forward "]{")
                                          (point))))
    ('keyword (string-match-p "^header-args" (org-element-property :value (org-element-context))))))
(defun +yas/org-prompt-header-arg (arg question values)
  "Prompt the user to set ARG header property to one of VALUES with QUESTION.
The default value is identified and indicated. If either default is selected,
or no selection is made: nil is returned."
  (let* ((src-block-p (not (looking-back "^#\\+property:[ \t]+header-args:.*" (line-beginning-position))))
         (default
           (or
            (cdr (assoc arg
                        (if src-block-p
                            (nth 2 (org-babel-get-src-block-info t))
                          (org-babel-merge-params
                           org-babel-default-header-args
                           (let ((lang-headers
                                  (intern (concat "org-babel-default-header-args:"
                                                  (+yas/org-src-lang)))))
                             (when (boundp lang-headers) (eval lang-headers t)))))))
            ""))
         default-value)
    (setq values (mapcar
                  (lambda (value)
                    (if (string-match-p (regexp-quote value) default)
                        (setq default-value
                              (concat value " "
                                      (propertize "(default)" 'face 'font-lock-doc-face)))
                      value))
                  values))
    (let ((selection (consult--read values :prompt question :default default-value)))
      (unless (or (string-match-p "(default)$" selection)
                  (string= "" selection))
        selection))))
(defun +yas/org-src-lang ()
  "Try to find the current language of the src/header at `point'.
Return nil otherwise."
  (let ((context (org-element-context)))
    (pcase (org-element-type context)
      ('src-block (org-element-property :language context))
      ('inline-src-block (org-element-property :language context))
      ('keyword (when (string-match "^header-args:\\([^ ]+\\)" (org-element-property :value context))
                  (match-string 1 (org-element-property :value context)))))))

(defun +yas/org-last-src-lang ()
  "Return the language of the last src-block, if it exists."
  (save-excursion
    (beginning-of-line)
    (when (re-search-backward "^[ \t]*#\\+begin_src" nil t)
      (org-element-property :language (org-element-context)))))

(defun +yas/org-most-common-no-property-lang ()
  "Find the lang with the most source blocks that has no global header-args, else nil."
  (let (src-langs header-langs)
    (save-excursion
      (goto-char (point-min))
      (while (re-search-forward "^[ \t]*#\\+begin_src" nil t)
        (push (+yas/org-src-lang) src-langs))
      (goto-char (point-min))
      (while (re-search-forward "^[ \t]*#\\+property: +header-args" nil t)
        (push (+yas/org-src-lang) header-langs)))

    (setq src-langs
          (mapcar #'car
                  ;; sort alist by frequency (desc.)
                  (sort
                   ;; generate alist with form (value . frequency)
                   (cl-loop for (n . m) in (seq-group-by #'identity src-langs)
                            collect (cons n (length m)))
                   (lambda (a b) (> (cdr a) (cdr b))))))

    (car (cl-set-difference src-langs header-langs :test #'string=))))

Vterm

The shell, so I never leave emacs. To get other plugins to work properly, my SHELL envvar is set to bash, but I prefer to use fish interactively:

(cl-loop for file in '("/usr/local/bin/fish" "/usr/bin/fish")
         when (file-exists-p file)
         do (progn
              (setq vterm-shell file)
              (cl-return)))

Useless

Elcord

Everyone must know, of course.

(use-package! elcord
  :commands elcord-mode
  :config
  (setq elcord-use-major-mode-as-main-icon t)
  (setq elcord-icon-base "https://raw.githubusercontent.com/jishnusen/emacs-config/main/misc/elcord-icons/"))

perfect-margin

;; (use-package! perfect-margin
;;   :config
;;   (after! doom-modeline
;;     (setq mode-line-right-align-edge 'right-fringe))
;;   ;; (setq perfect-margin-only-set-left-margin t)
;;   (perfect-margin-mode t))

About


Languages

Language:Emacs Lisp 67.2%Language:YASnippet 32.8%