config/doom/config.org

26 KiB
Raw Blame History

Doom Emacs Configuration File

Installation

Execute doom sync after modifying this file to tangle it to config.el.

Dependencies:

  • Doom Emacs: git ripgrep, fd-find
  • lilypond-mode: LilyPond
  • :app everywhere: xwininfo, xdotool, xclip, xprop
  • Font: Source Code Pro

Individual packages may have other optional dependencies. Execute doom doctor to find out.

The following block of comments is from the default configuration.

;;; $DOOMDIR/config.el -*- lexical-binding: t; -*-

;; Place your private configuration here! Remember, you do not need to run 'doom
;; sync' after modifying this file!

;; Whenever you reconfigure a package, make sure to wrap your config in an
;; `after!' block, otherwise Doom's defaults may override your settings. E.g.
;;
;;   (after! PACKAGE
;;     (setq x y))
;;
;; The exceptions to this rule:
;;
;;   - Setting file/directory variables (like `org-directory')
;;   - Setting variables which explicitly tell you to set them before their
;;     package is loaded (see 'C-h v VARIABLE' to look up their documentation).
;;   - Setting doom variables (which start with 'doom-' or '+').
;;
;; Here are some additional functions/macros that will help you configure Doom.
;;
;; - `load!' for loading external *.el files relative to this one
;; - `use-package!' for configuring packages
;; - `after!' for running code after a package has loaded
;; - `add-load-path!' for adding directories to the `load-path', relative to
;;   this file. Emacs searches the `load-path' when you load packages with
;;   `require' or `use-package'.
;; - `map!' for binding new keys
;;
;; To get information about any of these functions/macros, move the cursor over
;; the highlighted symbol at press 'K' (non-evil users must press 'C-c c k').
;; This will open documentation for it, including demos of how they are used.
;; Alternatively, use `C-h o' to look up a symbol (functions, variables, faces,
;; etc).
;;
;; You can also try 'gd' (or 'C-c c d') to jump to their definition and see how
;; they are implemented.

These packages are loaded into packages.el. The following comments are provided by default.

;; -*- no-byte-compile: t; -*-
;;; $DOOMDIR/packages.el

;; To install a package with Doom you must declare them here and run 'doom sync'
;; on the command line, then restart Emacs for the changes to take effect -- or
;; use 'M-x doom/reload'.


;; To install SOME-PACKAGE from MELPA, ELPA or emacsmirror:
;(package! some-package)

;; To install a package directly from a remote git repo, you must specify a
;; `:recipe'. You'll find documentation on what `:recipe' accepts here:
;; https://github.com/radian-software/straight.el#the-recipe-format
;(package! another-package
;  :recipe (:host github :repo "username/repo"))

;; If the package you are trying to install does not contain a PACKAGENAME.el
;; file, or is located in a subdirectory of the repo, you'll need to specify
;; `:files' in the `:recipe':
;(package! this-package
;  :recipe (:host github :repo "username/repo"
;           :files ("some-file.el" "src/lisp/*.el")))

;; If you'd like to disable a package included with Doom, you can do so here
;; with the `:disable' property:
;(package! builtin-package :disable t)

;; You can override the recipe of a built in package without having to specify
;; all the properties for `:recipe'. These will inherit the rest of its recipe
;; from Doom or MELPA/ELPA/Emacsmirror:
;(package! builtin-package :recipe (:nonrecursive t))
;(package! builtin-package-2 :recipe (:repo "myfork/package"))

;; Specify a `:branch' to install a package from a particular branch or tag.
;; This is required for some packages whose default branch isn't 'master' (which
;; our package manager can't deal with; see radian-software/straight.el#279)
;(package! builtin-package :recipe (:branch "develop"))

;; Use `:pin' to specify a particular commit to install.
;(package! builtin-package :pin "1a2b3c4d5e")


;; Doom's packages are pinned to a specific commit and updated from release to
;; release. The `unpin!' macro allows you to unpin single packages...
;(unpin! pinned-package)
;; ...or multiple packages
;(unpin! pinned-package another-pinned-package)
;; ...Or *all* packages (NOT RECOMMENDED; will likely break things)
;(unpin! t)

User specific settings

Some functionality uses this to identify you, e.g. GPG configuration, email clients, file templates and snippets. It is optional. This could be changed to rely on system environment variables.

(setq
	user-full-name "Leni Aniva"
	user-mail-address "v@leni.sh")

If you use org and don't want your org files in the default location below, change org-directory. It must be set before org loads!

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

Interface

Key Bindings

Modifications to the default key bindings to mimic spacemacs

  • SPC SPC is mapped to M-x (#'execute-extended-command)
  • SPC ! is mapped to M-! (#'shell-command)
  • SPC : is mapped to M-: (#'eval-expression)
  • , is mapped to the local (major mode) leader ,
  • SPC b 0 deletes the current buffer and window like Spacemacs SPC b x
(setq doom-localleader-key ",")
(map! :leader
	:desc "Execute shell command"
	"!" #'shell-command)
(map! :leader
	:desc "Execute extended command (emacs M-x)"
	"SPC" #'execute-extended-command)
(map! :leader
	:desc "Evaluate expression (emacs M-:)"
	":" #'eval-expression)
(map! :leader
	:desc "Kill buffer and window"
	"b 0" #'kill-buffer-and-window)
(map! :leader
	(:prefix ("S" . "Set")
	 :desc "Input method" "i" #'set-input-method
	))
(map! :nvi "C" nil) ; remove this one weird binding

(map! "<f12>" #'toggle-input-method)

; Remap JIS yen key to backslash
(global-set-key (kbd "M-¥") (kbd "\\"))
(global-set-key (kbd "M-|") (kbd "|"))
(global-set-key (kbd "C-M-¥") (kbd "C-\\"))

Font settings

Doom exposes five (optional) variables for controlling fonts in Doom:

  • doom-font the primary font to use
  • doom-variable-pitch-font a non-monospace font (where applicable)
  • doom-big-font used for doom-big-font-mode; use this for presentations or streaming.
  • doom-unicode-font for unicode glyphs
  • doom-serif-font for the fixed-pitch-serif face

See C-h v doom-font for documentation and more examples of what they accept.

If you or Emacs can't find your font, use M-x describe-font to look them up, M-x eval-region to execute ELisp code, and M-x doom/reload-font to refresh your font settings. If Emacs still can't find your font, it likely wasn't installed correctly. Font issues are rarely Doom issues!

There are two ways to load a theme. Both assume the theme is installed and available. You can either set doom-theme or manually load a theme with the load-theme function.

(setq
	default-font "Source Code Pro"
	default-font-size 14.0
	doom-font (font-spec
		:family default-font
		:size default-font-size
		:weight 'semi-light)
	doom-variable-pitch-font (font-spec
		:family default-font
		:size (+ default-font-size 1.0))
	doom-unicode-font (font-spec
		:family default-font
		:size default-font-size)
	doom-theme 'doom-lantern)
;(add-to-list 'default-frame-alist '(alpha . 90))

Splash Screen

(defun custom/doom-banner ()
	(let* (
			(banner (with-temp-buffer
				(insert-file-contents "~/.config/doom/banner.txt")
				(split-string (buffer-string) "\n" t)))
			(longest-line (apply #'max (mapcar #'length banner)))
		)
		(put-text-property
			(point)
			(dolist (line banner (point))
				(insert
					(+doom-dashboard--center
					 +doom-dashboard--width
					 (concat line (make-string (max 0 (- longest-line (length line))) 32)))
					"\n"))
		 'face 'doom-dashboard-banner)))
(setq +doom-dashboard-ascii-banner-fn #'custom/doom-banner)
(set-face-font 'doom-dashboard-banner (font-spec :family "Source Code Pro" :foundry "ADBO"))

Line numbers

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 'relative)

Indentation Style Hooks

(defun custom/common-program-hook ()
	(setq
		whitespace-style '(face spaces tabs newline trailing space-mark tab-mark newline-mark)
		whitespace-line-column 80)
	(display-line-numbers-mode)
	(display-fill-column-indicator-mode)
	(setq
		indent-tabs-mode t
		indent-line-function 'tab-to-tab-stop
		tab-width 3
		standard-indent 3)
	(indent-tabs-mode)
	(smart-tabs-mode-enable)
)
(defun custom/common-richtext-hook ()
	(setq whitespace-style '(face spaces tabs newline trailing space-mark tab-mark newline-mark))
	(setq whitespace-line-column nil)
	(display-line-numbers-mode)
)
(add-hook 'text-mode-hook #'custom/common-richtext-hook)
(setq
	typescript-indent-level 3
	css-indent-offset 3
	sh-indentation 3
	nxml-indent-offset 3
)

Shells

Fish (and possibly other non-POSIX shells) is known to inject garbage output into some of the child processes that Emacs spawns. Many Emacs packages/utilities will choke on this output, causing unpredictable issues.

Here we set the default shell to bash but the vterm shell to the system $SHELL variable.

(setq shell-file-name (executable-find "bash"))
(setq vterm-shell (getenv "SHELL"))

Package-specific Settings

Editor

Smart Tabs

(package! smart-tabs-mode)
(use-package! smart-tabs-mode
	:config
	(print! "Configuring smart-tabs-mode")
	(smart-tabs-insinuate
		'c 'c++
		'java
		'javascript))
(setq-default
	indent-tabs-mode t
	indent-line-function 'tab-to-tab-stop
	tab-width 3
	standard-indent 3)

Whitespace mode

(package! whitespace)
(use-package! whitespace
	:config
	(setq
		whitespace-style '(face tabs tab-mark spaces space-mark trailing newline newline-mark)
		whitespace-line-column 80
		whitespace-display-mappings
		'(
			(space-mark 32 [183] [46]) ; 32 SPACE 「 」, 183 MIDDLE DOT 「·」, 46 FULL STOP 「.」
			(newline-mark 10 [172 10]) ; 10 LINE FEED, 172 Not Sign「¬」
			(tab-mark 9 [10141 9] [92 9]) ; 9 TAB, 10141 Triangle-headed rightwards arrow 「➝」
		))
	(global-whitespace-mode +1)
	)

Format

Note that onsave was intentionally turned off because it messes with version control systems.

(after! format
	(print! "Configuring (after! format ...)")
	'(not
		emacs-lisp-mode
		sql-mode ; Broken
		;; tex related modes are broken
		tex-mode
		latex-mode
		bibtex-mode
	))

Spell

(after! flyspell
	(print! "Configuring (after! flyspell ...)")
	(setq flyspell-default-dictionary "english"))

Languages

Capnproto

(package! capnp-mode :recipe (
		:host github
		:repo "capnproto/capnproto"
		:files ("highlighting/emacs/*.el")))

Cov

(package! cov)

Emacs Lisp

(defun custom/lisp-family-hook ()
	(setq
		indent-line-function 'tab-to-tab-stop
		tab-width 3
		standard-indent 3)
	(indent-tabs-mode)
	(display-line-numbers-mode)
	)
(add-hook 'lisp-mode-hook       #'custom/lisp-family-hook)
(add-hook 'lisp-data-mode-hook  #'custom/lisp-family-hook)
(add-hook 'emacs-lisp-mode-hook #'custom/lisp-family-hook)

Epub

(package! nov)
(add-hook 'nov-mode-hook (lambda ()
	(setq nov-text-width t)
	(visual-line-mode)))

Org

Rather than using a common prefix , i like Spacemacs to consolidate all insertion operations, we put them in different categories. Some keybindings are removed.

(map! :after org
	:localleader
	:map org-mode-map
	:desc "LaTeX Preview"             "X" #'org-latex-preview
	;; Remove some clutter
	"h" nil
	"n" nil
	"i" nil
	"@" nil
	"q" nil
	"o" nil
	"I" nil ; 'org-id-get-create'
	"A" nil ; 'org-archive-subtree'
	(:prefix ("b". "table")
	 :desc "Edit formulae"           "F" #'org-table-edit-formulas
	 :desc "Eval formula"            "f" #'org-table-eval-formula
	 (:prefix ("m" . "move")
	  :desc "Move row up"            "K" #'org-table-move-row-up
	  :desc "Move row down"          "J" #'org-table-move-row-down
	  :desc "Move column left"       "H" #'org-table-move-column-left
	  :desc "Move column right"      "L" #'org-table-move-column-right
	  :desc "Move cell up"           "k" #'org-table-move-cell-up
	  :desc "Move cell down"         "j" #'org-table-move-cell-down
	  :desc "Move cell left"         "h" #'org-table-move-cell-left
	  :desc "Move cell right"        "l" #'org-table-move-cell-right
	  )
	 )
	(:prefix ("h" . "headings")
	 :desc "Consult heading"          "." #'consult-org-heading
	 :desc "Insert heading"           "h" #'org-insert-heading
	 :desc "Insert todo heading"      "t" #'org-insert-todo-heading
	 :desc "Insert subheading"        "i" #'org-insert-subheading
	 :desc "Insert todo subheading"   "t" #'org-insert-todo-subheading
	 :desc "Previous visible heading" "[" #'org-previous-visible-heading
	 :desc "Next visible heading"     "]" #'org-next-visible-heading
	 )
	(:prefix ("o" . "tags/properties")
	 :desc "Set tags command"         "t" #'org-set-tags-command
	 :desc "Set property"             "s" #'org-set-property
	 :desc "Delete property"          "d" #'org-delete-property
	 :desc "Property action"          "a" #'org-property-action
	 )
	(:prefix ("i" . "insert")
	 :desc "Item"                     "i" #'org-toggle-item
	 :desc "Citation"                 "c" #'org-cite-insert
	 :desc "Footnote"                 "f" #'org-footnote-new
	 :desc "Id"                       "I" #'org-id-get-create
	 )
	(:prefix ("l" . "links")
	 ;; Description update
	 :desc "Insert/Modify Link"       "l" #'org-insert-link
	 )
	)

LaTeX

The target indentation style of LaTeX is as follows:

\section{A section}
% No indent here

\begin{enumerate}[1.]
\item Some item 1. Note that item is not indented.

	Item 1 continued. 1 level of indentation.
\item
	\begin{tikzpicture}[
			xscale=2,yscale=3
		]
		\node[dot] (x) at (0,0);
		\node[dot] (y) at (0,1);
		\draw (x) -- (y)
			\node[midway,fill=white] {$f$};
	\end{tikzpicture}
\end{enumerate}

The SPC m c command which by default binds to TeX-command-run-all is modified so it runs in the directory of the nearest .latexmkrc file or the projectile root, whichever is closer. If a .latexmkrc file is not found, we fall back to the current directory. We improve this below by decreeing that all compilation has to happen with the .latexmkrc file's directory as PWD.

(defun custom/TeX-root-directory (start)
	(let* (
		(dir-latexmkrc (projectile-locate-dominating-file start ".latexmkrc"))
		(dir-projectile (projectile-project-root))
		(is-latexmk-child-of-projectile (and dir-projectile dir-latexmkrc
			(string-match-p (regexp-quote dir-projectile) dir-latexmkrc))))
		(cond
			((and dir-latexmkrc is-latexmk-child-of-projectile) dir-latexmkrc)
			(dir-projectile dir-projectile)
			(t start))))
(defun custom/TeX-master-directory-filter (dir-master)
	(custom/TeX-root-directory dir-master))
(defun custom/TeX-master-file-filter (file-master)
	(concat
		(string-remove-prefix (custom/TeX-root-directory default-directory) default-directory)
		file-master
	))

(advice-add 'TeX-master-directory :filter-return #'custom/TeX-master-directory-filter)
(advice-add 'TeX-master-file      :filter-return #'custom/TeX-master-file-filter)

For some reason the keymap binding has to use LaTeX-mode-map or TeX-mode-map instead of latex-mode-map. Also some original keybindings are removed so they can be displayed correctly in prompts. See e.g. tex-buf.el for an example.

When unbinding these keys, use :g flag to ensure no extraneous keys are introduced.

(after! latex
	(print! "Configuring (after! latex ...)")
	(add-hook 'tex-mode-hook       #'custom/common-program-hook)
	(add-hook 'LaTeX-mode-hook     #'custom/common-program-hook)
	(setq-default TeX-electric-sub-and-superscript nil)
	;(local-unset-key "C-c C-l")
	;(local-unset-key "C-c `")
	)
(map! :after latex
	:localleader
	:map TeX-mode-map
	:desc "View Compilation Log" "l" #'TeX-recenter-output-buffer
	:desc "Next Error"           "e" #'TeX-next-error
	:desc "Close Environment"    "]" #'LaTeX-close-environment
	(:prefix ("f" . "Fold")
	 :desc "Fold Mode"           "f" #'TeX-fold-mode
	 :desc "Environment"         "e" #'TeX-fold-env
	 :desc "Buffer"              "b" #'TeX-fold-buffer
	 :desc "Region"              "r" #'TeX-fold-region
	 :desc "Paragraph"           "p" #'TeX-fold-paragraph
	 :desc "Dwim"                "d" #'TeX-fold-dwim)
	)
(map! :after latex
	:map TeX-mode-map
	:g "C-c C-l" nil
	:g "C-c `" nil
	)

Lean 3

(add-hook! lean-mode
	(activate-input-method 'Lean)
	(if (not indent-tabs-mode)
		(indent-tabs-mode))
	)
(map! :after lean-mode
	:localleader
	:map lean-mode-map
	:desc "Show symbol keystrokes" "k" #'quail-show-key
	)
(map! :after lean-mode
	:map TeX-mode-map
	:g "C-c C-k" nil
	:g "C-c C-r" nil
	:g "C-c C-s" nil
	:g "C-c C-g" nil
	:g "C-c SPC" nil
	)

Lean 4

Lean 4 is still volatile and under construction.

(package! lean4-mode :recipe (
		:host github
		:repo "leanprover/lean4-mode"
		:files ("*.el" "data")))
(add-hook 'lean4-mode-hook (lambda ()
		(setq tab-width 2)))
(map! :after lean4-mode
	:localleader
	:map lean4-mode-map
	:desc "Execute"               "R" #'lean4-execute
	:desc "Execute in standalone" "r" #'lean4-std-exe
	:desc "Toggle info buffer"    "t" #'lean4-toggle-info
	(:prefix ("e" . "Error")
	 :desc "Previous error"       "p" #'flycheck-previous-error
	 :desc "Next error"           "n" #'flycheck-next-error
	 :desc "List error"           "l" #'flycheck-list-errors
	 )
	:desc "Lake build"            "b" #'lean4-lake-build
	(:prefix ("p" . "leanpkg")
	 :desc "Test"                 "t" #'lean4-leanpkg-test
	 :desc "Build"                "b" #'lean4-leanpkg-build
	 :desc "Configure"            "c" #'lean4-leanpkg-configure
	 )
	)

LilyPond

Add the necessary hooks for LilyPond mode.

(use-package! lilypond-mode
	:mode ("\\.ly\\'" . LilyPond-mode)
	:after-call LilyPond-mode
	:config
		(add-hook 'LilyPond-mode-hook  #'custom/common-program-hook))
(map! :after lilypond-mode
	:localleader
	:map LilyPond-mode-map
	:desc "Info"               "I" #'LilyPond-info
	:desc "Info Index Search"  "i" #'LilyPond-info-index-search
	:desc "Comment/Uncomment"  ";" #'LilyPond-comment-region
	:desc "What beat"          "b" #'LilyPond-what-beat
	:desc "Play midi"          "m" #'LilyPond-command-current-midi
	:desc "Quick insert mode"  "q" #'LilyPond-quick-insert-mode
	:desc "Insert tag current" "t" #'LilyPond-insert-tag-current
	(:prefix ("c" . "command")
	 :desc "Buffer"            "b" #'LilyPond-command-buffer
	 :desc "Master"            "r" #'LilyPond-command-master
	 :desc "LilyPond"          "l" #'LilyPond-command-lilypond
	 :desc "Format ps"         "p" #'LilyPond-command-formatps
	 :desc "Format midi"       "m" #'LilyPond-command-formatmidi
	 :desc "Kill jobs"         "k" #'LilyPond-kill-jobs
	 )
	)

Rust

(after! rustic
	(setq rustic-indent-offset standard-indent))

Tools

Language Server Protocol (LSP)

File path is removed from breadcrumb since it is provided by doom-modeline and since it clutters the header-line.

(setq-hook! 'lsp-mode-hook
	lsp-headerline-breadcrumb-enable t
	lsp-headerline-breadcrumb-enable-symbol-numbers nil
	lsp-headerline-breadcrumb-segments '(symbols)
)

Magit

(map! :after magit
	:localleader
	:map git-commit-mode-map
	:desc "Finish" "c" #'with-editor-finish
	:desc "Cancel" "q" #'with-editor-cancel
	)

Emacs IPython Notebook (ein)

Adapted from https://gist.github.com/millejoh/8632402a11384a3a0949fa350a1a271b

Usage: Since ein:notebooklist-mode, ein:markdown-mode, and ein:traceback-mode are major modes, their hotkeys are triggered by the local leader , / SPC m. However the ein:notebook-mode and ein:notebook-multilang-mode are minor modes and they are triggered by C-c instead of ,.

FIXME: Cleanup ein:markdown-mode-map.

(defun custom/ein:worksheet-merge-cell-next ()
	(interactive)
	(ein:worksheet-merge-cell-km (ein:worksheet--get-ws-or-error) (ein:worksheet-get-current-cell) t t))
(map! :after ein
	;:localleader
	:map ein:notebooklist-mode-map ; Major mode
	"" nil
	:desc "Describe mode"          "h" #'describe-mode
	:desc "Reload"                 "r" #'ein:notebooklist-reload
	:desc "File open"              "o" #'ein:file-open
	:desc "Open"                   "O" #'ein:notebook-open
	:desc "Prev item"              "[" #'ein:notebooklist-prev-item
	:desc "Next item"              "]" #'ein:notebooklist-next-item
	:desc "New notebook"           "n" #'ein:notebooklist-new-notebook
	:desc "New notebook with name" "N" #'ein:notebooklist-new-notebook-with-name
)
(map! :after ein
	:localleader
	:map ein:markdown-mode-map ; Major mode
	:desc "Up"      "K" #'ein:markdown-move-up
	:desc "Down"    "J" #'ein:markdown-move-down
	:desc "Promote" "H" #'ein:markdown-move-up
	:desc "Demote"  "L" #'ein:markdown-move-down
	:desc "Do"      "e" #'ein:markdown-do
	:desc "Cycle"   "c" #'ein:markdown-cycle
)
(map! :after ein
	:map ein:notebook-mode-map ; Minor mode
	:prefix "C-c"
	"" nil ; Remove all keys
	;; Python
	:desc "Jump back"          "," #'ein-pytools-jump-back-command
	:desc "Jump to source"     "." #'ein-pytools-jump-to-source-command
	;; Cell
	:desc "Copy cell"          "y" #'ein:worksheet-copy-cell-km
	:desc "Yank cell"          "p" #'ein:worksheet-yank-cell-km
	:desc "kill cell"          "d" #'ein:worksheet-kill-cell-km
	:desc "Insert below"       "i" #'ein:worksheet-insert-cell-below-km
	:desc "Insert above"       "I" #'ein:worksheet-insert-cell-above-km
	:desc "Goto next input"    "j" #'ein:worksheet-goto-next-input-km
	:desc "Goto prev input"    "k" #'ein:worksheet-goto-prev-input-km
	:desc "Move cell down"     "J" #'ein:worksheet-move-cell-down-km
	:desc "Move cell up"       "K" #'ein:worksheet-move-cell-up-km
	:desc "Change cell type"   "u" #'ein:worksheet-change-cell-type-km
	:desc "Toggle cell type"   "t" #'ein:worksheet-toggle-cell-type-km
	:desc "Merge cell"         "m" #'ein:worksheet-merge-cell-km
	:desc "Merge cell next"    "M" #'custom/ein:worksheet-merge-cell-next
	:desc "Split cell"         "s" #'ein:worksheet-split-cell-at-point-km
	:desc "Execute cell and goto next" "RET" #'ein:worksheet-execute-cell-and-goto-next-km
	;; Notebook
	:desc "Move prev"          "H" #'ein:notebook-worksheet-move-prev
	:desc "Move next"          "L" #'ein:notebook-worksheet-move-next
	(:prefix ("o" . "Output")
	 :desc "Toggle output"         "t" #'ein:worksheet-toggle-output-km
	 :desc "Show traceback"        "T" #'ein:tb-show-km
	 :desc "Clear output"          "c" #'ein:worksheet-clear-output
	 :desc "Clear all output"      "C" #'ein:worksheet-clear-all-output
	 :desc "Set output visibility" "v" #'ein:worksheet-set-output-visibility-all-km
	 :desc "Show code cell"        "s" #'ein:shared-output-show-code-cell-at-point-km
	)
	(:prefix ("e" . "Execute")
	 :desc "All cells"             "a" #'ein:worksheet-execute-all-cells
	 :desc "All cells above"       "K" #'ein:worksheet-execute-all-cells-above
	 :desc "All cells below"       "J" #'ein:worksheet-execute-all-cells-below
	 :desc "Cell and go to next"   "RET" #'ein:worksheet-execute-cell-and-goto-next-km
	 :desc "Cell and insert below" "j" #'ein:worksheet-execute-cell-and-insert-below-km
	)
	(:prefix ("f" . "File")
	 :desc "Open console"       "c"   #'ein:console-open
	 :desc "Open prev or last"  "h" #'ein:notebook-worksheet-open-prev-or-last
	 :desc "Open next or first" "l" #'ein:notebook-worksheet-open-next-or-first
	 :desc "Insert next"        "n" #'ein:notebook-worksheet-insert-next
	 :desc "Open 1"             "1" #'ein:notebook-worksheet-open-1th
	 :desc "Open 2"             "2" #'ein:notebook-worksheet-open-2th
	 :desc "Open 3"             "3" #'ein:notebook-worksheet-open-3th
	 :desc "Open 4"             "4" #'ein:notebook-worksheet-open-4th
	 :desc "Open 5"             "5" #'ein:notebook-worksheet-open-5th
	 :desc "Open 6"             "6" #'ein:notebook-worksheet-open-5th
	 :desc "Open 7"             "7" #'ein:notebook-worksheet-open-7th
	 :desc "Open 8"             "8" #'ein:notebook-worksheet-open-8th
	 :desc "Open last"          "9" #'ein:notebook-worksheet-open-last
	 :desc "Close notebook"     "x" #'ein:notebook-close
	 :desc "Save notebook"      "s" #'ein:notebook-save-notebook-command
	 :desc "Rename sheet"       "R" #'ein:worksheet-rename-sheet
	 :desc "Rename notebook"    "C-r" #'ein:notebook-rename-command
	 :desc "Worksheet Delete"   "d" #'ein:notebook-worksheet-delete
	)
)
(map! :after ein-multilang
	:map ein:notebook-multilang-mode-map ; Minor mode
	:prefix "C-c"
	:desc "Execute cell"               "RET" #'ein:worksheet-execute-cell
	:desc "Execute cell and goto next" "<C-return>" #'ein:worksheet-execute-cell-and-goto-next
	:desc "Goto next input" "gj" 'ein:worksheet-goto-next-input
	:desc "Goto prev input" "gk" 'ein:worksheet-goto-prev-input
	:desc "Move cell down"  "M-j" 'ein:worksheet-move-cell-down
	:desc "Move cell up"    "M-k" 'ein:worksheet-move-cell-up
)

Telegram

(package! telega)

On Arch Linux, telegram-tdlib installs to

telegram-tdlib /usr/
telegram-tdlib /usr/include/
telegram-tdlib /usr/include/td/...
telegram-tdlib /usr/lib/
telegram-tdlib /usr/lib/cmake/
telegram-tdlib /usr/lib/cmake/...
telegram-tdlib /usr/lib/libtdactor.a
telegram-tdlib /usr/lib/...
telegram-tdlib /usr/lib/pkgconfig/...
(setq telega-server-libs-prefix "/usr")