dotfiles/Emacs.org

116 KiB

One day we won't hate one another, no young boy will march to war and I will clean up my Emacs config. But that day isn't today.

My Emacs configuration.

As with other files in the repo, parts prefixed with (OFF) are not used but kept for historic purposes.

Contents

Primary setup

Measure startup speed

A small function to print out the loading time and number of GCs during the loading. Can be useful as a point of data for optimizing Emacs startup time.

(add-hook 'emacs-startup-hook
          (lambda ()
            (message "*** Emacs loaded in %s with %d garbage collections."
                     (format "%.2f seconds"
                             (float-time
                              (time-subtract after-init-time before-init-time)))
                     gcs-done)))

;; (setq use-package-verbose t)

straight.el

Straight.el is my Emacs package manager of choice. Its advantages & disadvantages over other options are listed pretty thoroughly in the README file in the repo.

The following is a straight.el bootstrap script.

References:

(defvar bootstrap-version)
(let ((bootstrap-file
       (expand-file-name "straight/repos/straight.el/bootstrap.el" user-emacs-directory))
      (bootstrap-version 5))
  (unless (file-exists-p bootstrap-file)
    (with-current-buffer
        (url-retrieve-synchronously
         "https://raw.githubusercontent.com/raxod502/straight.el/develop/install.el"
         'silent 'inhibit-cookies)
      (goto-char (point-max))
      (eval-print-last-sexp)))
      (load bootstrap-file nil 'nomessage))

use-package

A macro to simplify package specification & configuration. Integrates with straight.el.

Set use-package-verbose to t to print out individual package loading time.

References:

(straight-use-package 'use-package)
(eval-when-compile (require 'use-package))
 ;; (setq use-package-verbose t)

Performance

Garbage collection

Just setting gc-cons-treshold to a larger value.

Note Type
CHECK The value may be too large for an interactive use
(setq gc-cons-threshold 80000000)
(setq read-process-output-max (* 1024 1024))

Run garbage collection when Emacs is unfocused

Run GC when Emacs loses focus. Time will tell if that's a good idea.

(add-hook 'emacs-startup-hook
          (lambda ()
            (if (boundp 'after-focus-change-function)
                (add-function :after after-focus-change-function
                              (lambda ()
                                (unless (frame-focus-state)
                                  (garbage-collect))))
              (add-hook 'after-focus-change-function 'garbage-collect))))

Misc

The following variable is true when my machine is not powerful enough for some resource-heavy packages.

(setq my/lowpower (string= (system-name) "pntk"))

And the following is true if Emacs is meant to be used with TRAMP over slow ssh

(setq my/slow-ssh (string= (getenv "IS_TRAMP") "true"))

Native compilation

Set number of jobs to 1 on low-power machines

(when my/lowpower
  (setq comp-async-jobs-number 1))

Anaconda & environment

Anaconda is a free package and environment manager. I currently use it to manage multiple versions of Python and Node.js

The following code uses the conda package to activate the base environment on startup if Emacs is launched outside the environment.

References:

(use-package conda
  :straight t
  :if (executable-find "conda")
  :config
  (setq conda-anaconda-home (expand-file-name "~/Programs/miniconda3/"))
  (setq conda-env-home-directory (expand-file-name "~/Programs/miniconda3/"))
  (setq conda-env-subdirectory "envs")
  (unless (getenv "CONDA_DEFAULT_ENV")
    (conda-env-activate "base")))

Also, I sometimes need to know if a program is running inside Emacs (say, inside a terminal emulator). To do that, I set the following environment variable:

(setenv "IS_EMACS" "true")

Custom file location

By default, custom writes stuff to init.el, which is somewhat annoying. The following makes a separate file custom.el

(setq custom-file (concat user-emacs-directory "custom.el"))
(load custom-file 'noerror)

Global editing configuration

General keybindings stuff

general.el

general.el provides a convenient interface to manage Emacs keybindings.

References:

(use-package general
  :straight t
  :config
  (general-evil-setup))

which-key

A package that displays the available keybindings in a popup.

Pretty useful, as Emacs seems to have more keybindings than I can remember at any given point.

References:

(use-package which-key
  :config
  (setq which-key-idle-delay (if my/lowpower 1 0.3))
  (setq which-key-popup-type 'frame)
  (which-key-mode)
  (which-key-setup-side-window-bottom)
  (set-face-attribute 'which-key-local-map-description-face nil
                      :weight 'bold)
  :straight t)

Evil mode

A whole ecosystem of packages that emulates the main features of Vim. Probably the best vim emulator out there.

The only problem is that the package name makes it hard to google anything by just typing "evil".

References:

evil

Basic evil configuration.

(use-package evil
  :straight t
  :init
  (setq evil-want-integration t)
  (setq evil-want-C-u-scroll t)
  (setq evil-want-keybinding nil)
  :config
  (evil-mode 1)
  (setq evil-search-module 'evil-search)
  (setq evil-split-window-below t)
  (setq evil-vsplit-window-right t)
  ;; (setq evil-respect-visual-line-mode t)
  (evil-set-undo-system 'undo-tree)
  ;; (add-to-list 'evil-emacs-state-modes 'dired-mode)
  )

Addons

evil-surround emulates one of my favorite vim plugins, surround.vim. Adds a lot of parentheses management options.

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

evil-commentary emulates commentary.vim.

(use-package evil-commentary
  :straight t
  :after evil
  :config
  (evil-commentary-mode))

evil-quickscope emulates quickscope.vim. It highlights the important target characters for f, F, t, T keys.

(use-package evil-quickscope
  :straight t
  :after evil
  :config
  :hook ((prog-mode . turn-on-evil-quickscope-mode)
         (LaTeX-mode . turn-on-evil-quickscope-mode)
         (org-mode . turn-on-evil-quickscope-mode)))

evil-numbers allows incrementing and decrementing numbers at point.

(use-package evil-numbers
  :straight t
  :commands (evil-numbers/inc-at-pt evil-numbers/dec-at-pt)
  :init
  (general-nmap
    "g+" 'evil-numbers/inc-at-pt
    "g-" 'evil-numbers/dec-at-pt))

evil-collection

evil-collection is a package that provides evil bindings for a lot of different packages. One can see the whole list in the modes folder.

I don't enable the entire package, just the modes I need.

(use-package evil-collection
  :straight t
  :after evil
  :config
  (evil-collection-init
   '(eww
     dired
     debug
     guix
     docker
     geiser
     pdf
     info
     elfeed
     edebug
     bookmark
     company
     vterm
     flycheck
     profiler
     cider
     explain-pause-mode
     notmuch
     custom
     xref
     eshell
     helpful
     compile
     comint
     magit)))

More keybindigs

The main keybindigs setup is positioned after evil mode to take the latter into account.

Escape key

Use escape key instead of C-g whenever possible.

I must have copied it from somewhere, but as I googled to find out the original source, I discovered quite a number of variations of the following code over time.

I wonder if Richard Dawkins was inspired by something like this a few decades ago.

(defun minibuffer-keyboard-quit ()
  "Abort recursive edit.
In Delete Selection mode, if the mark is active, just deactivate it;
then it takes a second \\[keyboard-quit] to abort the minibuffer."
  (interactive)
  (if (and delete-selection-mode transient-mark-mode mark-active)
      (setq deactivate-mark  t)
    (when (get-buffer "*Completions*") (delete-windows-on "*Completions*"))
    (abort-recursive-edit)))

(general-define-key
 :keymaps '(normal visual global)
 [escape] 'keyboard-quit)

(general-define-key
 :keymaps '(minibuffer-local-map
            minibuffer-local-ns-map
            minibuffer-local-completion-map
            minibuffer-local-must-match-map
            minibuffer-local-isearch-map)
 [escape] 'minibuffer-keyboard-quit)

Home & end

(general-def :states '(normal insert visual)
  "<home>" 'beginning-of-line
  "<end>" 'end-of-line)

My leader

Using the SPC key as a sort of a leader key.

(general-create-definer my-leader-def
  :keymaps 'override
  :prefix "SPC"
  :states '(normal motion emacs))


(general-def :states '(normal motion emacs) "SPC" nil)

(my-leader-def "?" 'which-key-show-top-level)
(my-leader-def "E" 'eval-expression)

Universal argument

Change the universal argument to M-u

(general-def
  :keymaps 'universal-argument-map
  "M-u" 'universal-argument-more)
(general-def
  :keymaps 'override
  :states '(normal motion emacs insert visual)
  "M-u" 'universal-argument)

Profiler

The built-in profiler is a magnificent tool to troubleshoot performance issues.

(my-leader-def "Ps" 'profiler-start)
(my-leader-def "Pe" 'profiler-stop)
(my-leader-def "Pp" 'profiler-report)

Buffer switching

Some keybindings I used in vim to switch buffer and can't let go of.

(general-define-key
  :keymaps 'override
  "C-<right>" 'evil-window-right
  "C-<left>" 'evil-window-left
  "C-<up>" 'evil-window-up
  "C-<down>" 'evil-window-down
  "C-h" 'evil-window-left
  "C-l" 'evil-window-right
  "C-k" 'evil-window-up
  "C-j" 'evil-window-down
  "C-x h" 'previous-buffer
  "C-x l" 'next-buffer)

And winner-mode to keep a history of window states.

(winner-mode 1)
(define-key evil-window-map (kbd "u") 'winner-undo)
(define-key evil-window-map (kbd "U") 'winner-redo)

xref

Some keybindings for xref, Emacs' built-in systems for managing identifiers.

(general-nmap
  "gD" 'xref-find-definitions-other-window
  "gr" 'xref-find-references)

(my-leader-def
  "fx" 'xref-find-apropos)

Folding

(general-nmap :keymaps '(hs-minor-mode-map outline-minor-mode-map)
  "ze" 'hs-hide-level
  "TAB" 'evil-toggle-fold)

Zoom

(defun my/zoom-in ()
  "Increase font size by 10 points"
  (interactive)
  (set-face-attribute 'default nil
                      :height
                      (+ (face-attribute 'default :height)
                         10)))

(defun my/zoom-out ()
  "Decrease font size by 10 points"
  (interactive)
  (set-face-attribute 'default nil
                      :height
                      (- (face-attribute 'default :height)
                         10)))

;; change font size, interactively
(global-set-key (kbd "C-+") 'my/zoom-in)
(global-set-key (kbd "C-=") 'my/zoom-out)

Editing helpers

Visual fill column mode

(use-package visual-fill-column
  :straight t
  :config
  (add-hook 'visual-fill-column-mode-hook
            (lambda () (setq visual-fill-column-center-text t))))

smartparens

A minor mode to deal with pairs. Its functionality overlaps with evil-surround, but smartparens provides the most comfortable way to do stuff like automatically insert pairs.

References:

(use-package smartparens
  :straight t)

Aggressive Indent

A package to keep the code intended.

Doesn't work too well with js ecosystem, because the lsp-based indentation is rather slow, but nice for Lisps.

References:

(use-package aggressive-indent
  :commands (aggressive-indent-mode)
  :straight t)

Delete trailing whitespace

Delete trailing whitespace on save, unless in particular modes where trailing whitespace is important, like Markdown.

(setq my/trailing-whitespace-modes '(markdown-mode))

(require 'cl-extra)

(add-hook 'before-save-hook
          (lambda ()
            (unless (cl-some #'derived-mode-p my/trailing-whitespace-modes)
              (delete-trailing-whitespace))))

Expand region

(use-package expand-region
  :straight t
  :commands (er/expand-region)
  :init
  (general-nmap "+" 'er/expand-region))

Various settings

Tabs

Some default settings to manage tabs.

(setq tab-always-indent nil)

(setq-default default-tab-width 4)
(setq-default tab-width 4)
(setq-default evil-indent-convert-tabs nil)
(setq-default indent-tabs-mode nil)
(setq-default tab-width 4)
(setq-default evil-shift-round nil)

Scrolling config

(setq scroll-conservatively scroll-margin)
(setq scroll-step 1)
(setq scroll-preserve-screen-position t)
(setq scroll-error-top-bottom t)
(setq mouse-wheel-progressive-speed nil)
(setq mouse-wheel-inhibit-click-time nil)

Clipboard

(setq select-enable-clipboard t)
(setq mouse-yank-at-point t)

Backups

(setq backup-inhibited t)
(setq auto-save-default nil)

Undo Tree

Replaces Emacs build-in sequential undo system with a tree-based one. Probably one of the greatest features of Emacs as a text editor.

References:

(use-package undo-tree
  :straight t
  :config
  (global-undo-tree-mode)
  (setq undo-tree-visualizer-diff t)
  (setq undo-tree-visualizer-timestamps t)

  (my-leader-def "u" 'undo-tree-visualize)
  (fset 'undo-auto-amalgamate 'ignore)
  (setq undo-limit 6710886400)
  (setq undo-strong-limit 100663296)
  (setq undo-outer-limit 1006632960))

Help

helpful package improves the *help* buffer.

(use-package helpful
  :straight t
  :commands (helpful-callable
             helpful-variable
             helpful-key
             helpful-macro
             helpful-function
             helpful-command))

As I use C-h to switch buffers, I moved the help to SPC-h with the code below. Of course, I didn't type it all by hand.

(my-leader-def
  :infix "h"
  "RET" 'view-order-manuals
  "." 'display-local-help
  "?" 'help-for-help
  "C" 'describe-coding-system
  "F" 'Info-goto-emacs-command-node
  "I" 'describe-input-method
  "K" 'Info-goto-emacs-key-command-node
  "L" 'describe-language-environment
  "P" 'describe-package
  "S" 'info-lookup-symbol
  "a" 'helm-apropos
  "b" 'describe-bindings
  "c" 'describe-key-briefly
  "d" 'apropos-documentation
  "e" 'view-echo-area-messages
  "f" 'helpful-function
  "g" 'describe-gnu-project
  "h" 'view-hello-file
  "i" 'info
  "k" 'helpful-key
  "l" 'view-lossage
  "m" 'describe-mode
  "n" 'view-emacs-news
  "o" 'describe-symbol
  "p" 'finder-by-keyword
  "q" 'help-quit
  "r" 'info-emacs-manual
  "s" 'describe-syntax
  "t" 'help-with-tutorial
  "v" 'helpful-variable
  "w" 'where-is
  "<f1>" 'help-for-help
  "C-\\" 'describe-input-method
  "C-a" 'about-emacs
  "C-c" 'describe-copying
  "C-d" 'view-emacs-debugging
  "C-e" 'view-external-packages
  "C-f" 'view-emacs-FAQ
  "C-h" 'help-for-help
  "C-n" 'view-emacs-news
  "C-o" 'describe-distribution
  "C-p" 'view-emacs-problems
  "C-s" 'search-forward-help-for-help
  "C-t" 'view-emacs-todo
  "C-w" 'describe-no-warranty)

Ivy, counsel, swiper

Minibuffer completion tools for Emacs.

References:

(use-package ivy
  :straight t
  :config
  (setq ivy-use-virtual-buffers t)
  (ivy-mode))

(use-package counsel
  :straight t
  :after ivy
  :config
  (counsel-mode))

(use-package swiper
  :defer t
  :straight t)

ivy-rich

ivy-rich provides more informative interface for ivy.

(use-package ivy-rich
  :straight t
  :after ivy
  :config
  (ivy-rich-mode 1)
  (setcdr (assq t ivy-format-functions-alist) #'ivy-format-function-line))

prescient

A package which enhances sorting & filtering of candidates. ivy-prescient adds integration with Ivy.

References:

(use-package ivy-prescient
  :straight t
  :after counsel
  :config
  (ivy-prescient-mode +1)
  (setq ivy-prescient-retain-classic-highlighting t)
  (prescient-persist-mode 1)
  (setq ivy-prescient-sort-commands
        '(:not swiper
               swiper-isearch
               ivy-switch-buffer
               ;; ivy-resume
               ;; ivy--restore-session
               lsp-ivy-workspace-symbol
               counsel-grep
               ;; counsel-find-file
               counsel-git-grep
               counsel-rg
               counsel-ag
               counsel-ack
               counsel-fzf
               counsel-pt
               counsel-imenu
               counsel-yank-pop
               counsel-recentf
               counsel-buffer-or-recentf))
  ;; Do not use prescient in find-file
  (ivy--alist-set 'ivy-sort-functions-alist #'read-file-name-internal #'ivy-sort-file-function-default))

Keybindings

(my-leader-def
  :infix "f"
  "b" 'counsel-switch-buffer
  "e" 'conda-env-activate
  "f" 'project-find-file
  "c" 'counsel-yank-pop
  "a" 'counsel-rg
  "A" 'counsel-ag)

(general-imap
  "C-y" 'counsel-yank-pop)

(my-leader-def "SPC" 'ivy-resume)
(my-leader-def "s" 'swiper-isearch
  "S" 'swiper-all)

(general-define-key
 :keymaps '(ivy-minibuffer-map swiper-map)
 "M-j" 'ivy-next-line
 "M-k" 'ivy-previous-line
 "<C-return>" 'ivy-call
 "M-RET" 'ivy-immediate-done
 [escape] 'minibuffer-keyboard-quit)

OFF (OFF) Helm

Config for the Helm incremental completion framework. I switched to Ivy some time ago, but keep the configuration just in case.

(use-package helm
  :init
  (require 'helm-config)
  (setq helm-split-window-in-side-p t)
  (setq helm-move-to-line-cycle-in-source t)
  :straight t
  :config
  (helm-mode 1)
  (helm-autoresize-mode 1))

(use-package helm-ag
  :straight t)

(use-package helm-rg
  :straight t)

(general-nmap
  :keymaps 'helm-ag-mode-map
  "RET" 'helm-ag-mode-jump
  "M-RET" 'helm-ag-mode-jump-other-window)

(general-nmap
  :keymaps 'helm-occur-mode-map
  "RET" 'helm-occur-mode-goto-line
  "M-RET" 'helm-occur-mode-goto-line-ow)

(general-define-key "M-x" 'helm-M-x)
(my-leader-def
  "fb" 'helm-buffers-list
  "fs" 'helm-lsp-workspace-symbol
  "fw" 'helm-lsp-global-workspace-symbol
  "fc" 'helm-show-kill-ring
  ;; "fa" 'helm-do-ag-project-root
  "fm" 'helm-bookmarks
  "ff" 'project-find-file
  "fe" 'conda-env-activate)

(my-leader-def "s" 'helm-occur)
(my-leader-def "SPC" 'helm-resume)

(general-define-key
  :keymaps 'helm-map
  "C-j" 'helm-next-line
  "C-k" 'helm-previous-line)

(general-define-key
  :keymaps '(helm-find-files-map helm-locate-map)
  "C-h" 'helm-find-files-up-one-level
  "C-l" 'helm-execute-persistent-action)

(general-imap
  "C-y" 'helm-show-kill-ring)
;; (general-nmap "C-p" 'project-find-file)

Treemacs

Treemacs calls itself a tree layout file explorer, but looks more like a project and workspace management system.

Integrates with evil, magit and projectile.

(use-package treemacs
  :straight t
  :commands (treemacs treemacs-switch-workspace treemacs-edit-workspace)
  :config
  (setq treemacs-follow-mode nil)
  (setq treemacs-follow-after-init nil)
  (setq treemacs-space-between-root-nodes nil)
  (treemacs-git-mode 'extended)
  (with-eval-after-load 'treemacs
    (add-to-list 'treemacs-pre-file-insert-predicates #'treemacs-is-file-git-ignored?)))

(use-package treemacs-evil
  :after (treemacs evil)
  :straight t)

(use-package treemacs-magit
  :after (treemacs magit)
  :straight t)

(general-define-key
 :keymaps '(normal override global)
 "C-n" 'treemacs)

(general-define-key
 :keymaps '(treemacs-mode-map) [mouse-1] #'treemacs-single-click-expand-action)

(my-leader-def
  "tw" 'treemacs-switch-workspace
  "te" 'treemacs-edit-workspaces)

Projectile

Projectile gives a bunch of useful functions for managing projects, like finding files within a project, fuzzy-find, replace, etc.

defadvice is meant to speed projectile up with TRAMP a bit.

(use-package projectile
  :straight t
  :config
  (projectile-mode +1)
  (setq projectile-project-search-path '("~/Code" "~/Documents"))
  (defadvice projectile-project-root (around ignore-remote first activate)
    (unless (file-remote-p default-directory) ad-do-it)))

(use-package counsel-projectile
  :after (counsel projectile)
  :straight t)

(use-package treemacs-projectile
  :after (treemacs projectile)
  :straight t)

(my-leader-def
  "p" 'projectile-command-map)

(general-nmap "C-p" 'counsel-projectile-find-file)

Company

A completion framework for Emacs.

References:

(use-package company
  :straight t
  :config
  (global-company-mode)
  (setq company-idle-delay (if my/lowpower 0.5 0.125))
  (setq company-dabbrev-downcase nil)
  (setq company-show-numbers t))

(general-imap "C-SPC" 'company-complete)

A company frontend with nice icons.

(use-package company-box
  :straight t
  :if (not my/lowpower)
  :after (company)
  :hook (company-mode . company-box-mode))

Git & Magit

Magic is a git interface for Emacs. The closest non-Emacs alternative (sans actual clones) I know is lazygit, which I used before Emacs.

Also, git-gutter is plugin which shows git changes for each line (added/changed/deleted lines).

(use-package magit
  :straight t
  :commands (magit-status magit-file-dispatch)
  :config
  (setq magit-blame-styles
        '((margin
           (margin-format    . ("%a %A %s"))
           (margin-width     . 42)
           (margin-face      . magit-blame-margin)
           (margin-body-face . (magit-blame-dimmed)))
          (headings
           (heading-format   . "%-20a %C %s\n"))
          (highlight
           (highlight-face   . magit-blame-highlight))
          (lines
           (show-lines       . t)
           (show-message     . t)))
        ))

(use-package git-gutter
  :straight t
  :if (not my/slow-ssh)
  :config
  (global-git-gutter-mode +1))

(my-leader-def
  "m" 'magit
  "M" 'magit-file-dispatch)

Editorconfig

Editorconfig support for Emacs.

References:

(use-package editorconfig
  :straight t
  :config
  (unless my/slow-ssh (editorconfig-mode 1))
  (add-to-list 'editorconfig-indentation-alist
               '(emmet-mode emmet-indentation)))

OFF (OFF) Avy

(use-package avy
  :straight t)

(general-nmap "\\w" 'avy-goto-word-0-below)
(general-nmap "\\b" 'avy-goto-word-0-above)

Snippets

A snippet system for Emacs and a collection of pre-built snippets.

yasnippet-snippets has to be loaded before yasnippet for user snippets to override the pre-built ones.

References:

(use-package yasnippet-snippets
  :straight t)

(use-package yasnippet
  :straight t
  :config
  (setq yas-triggers-in-field t)
  (yas-global-mode 1))

(general-imap "M-TAB" 'company-yasnippet)

Time trackers

A bunch of timetrackers I use.

References:

WakaTime

Before I figure out how to package this for Guix:

  • Clone the repo
  • Run go build
  • Copy the binary to the ~/bin folder
(use-package wakatime-mode
  :straight t
  :config
  (advice-add 'wakatime-init :after (lambda () (setq wakatime-cli-path "/home/pavel/bin/wakatime-cli")))
  (global-wakatime-mode))

ActivityWatch

(use-package request
  :straight t)

(use-package activity-watch-mode
  :straight t
  :disabled
  :config
  (global-activity-watch-mode))

UI

General UI & GUI Settings

Disable GUI elements

(tool-bar-mode -1)
(menu-bar-mode -1)
(scroll-bar-mode -1)

Transparency

;; (set-frame-parameter (selected-frame) 'alpha '(90 . 90))
;; (add-to-list 'default-frame-alist '(alpha . (90 . 90)))

Prettify symbols

;; (global-prettify-symbols-mode)

No start screen

(setq inhibit-startup-screen t)

Visual bell

(setq visible-bell 0)

y or n instead of yes or no

(defalias 'yes-or-no-p 'y-or-n-p)

Hide mouse cursor while typing

(setq make-pointer-invisible t)

Line numbers. There seems to be a catch with the relative number setting:

  • visual doesn't take folding into account, but also doesn't take wrapped lines into account (makes multiple numbers for a single wrapped line)
  • relative makes a single number for a wrapped line, but counts folded lines.

visual option seems to be less of a problem in most cases.

(global-display-line-numbers-mode 1)
(line-number-mode nil)
(setq display-line-numbers-type 'visual)
(column-number-mode)

Show pairs

(show-paren-mode 1)

Word wrap

(setq word-wrap 1)
(global-visual-line-mode t)

Hightlight line

(global-hl-line-mode 1)

Theme & global stuff

Dim inactive buffers.

(use-package auto-dim-other-buffers
  :straight t
  :if (display-graphic-p)
  :config
  (set-face-attribute 'auto-dim-other-buffers-face nil
                      :background "#212533")
  (auto-dim-other-buffers-mode t))

My colorscheme of choice.

(use-package doom-themes
  :straight t
  :config
  (setq doom-themes-enable-bold t
        doom-themes-enable-italic t)
  (load-theme 'doom-palenight t)
  (doom-themes-visual-bell-config)
  (setq doom-themes-treemacs-theme "doom-colors")
  (doom-themes-treemacs-config))

Font

To install a font, download the font and unpack it into the .local/share/fonts directory. Create one if it doesn't exist.

As I use nerd fonts elsewhere, I use one in Emacs as well.

References:

(set-frame-font "JetBrainsMono Nerd Font 10" nil t)

To make the icons work (e.g. in the Doom Modeline), run M-x all-the-icons-install-fonts. The package definition is somewhere later in the config.

Custom frame title

(setq-default frame-title-format
              '(""
                "emacs"
                (:eval
                 (let ((project-name (projectile-project-name)))
                   (if (not (string= "-" project-name))
                       (format ":%s@%s" project-name (system-name))
                     (format "@%s" (system-name)))))))

Tab bar

I rely rather heavily on tab-bar in my workflow. I have a suspicion I'm not using it the intended way, but that works for me.

Setup

(general-define-key
 :keymaps 'override
 :states '(normal emacs)
 "gt" 'tab-bar-switch-to-next-tab
 "gT" 'tab-bar-switch-to-prev-tab
 "gn" 'tab-bar-new-tab)

(setq tab-bar-show 1)
(setq tab-bar-tab-hints t)
(setq tab-bar-tab-name-function 'tab-bar-tab-name-current-with-count)

;; Tabs
(general-nmap "gn" 'tab-new)
(general-nmap "gN" 'tab-close)

;; Colors
(custom-set-faces
 ;; `(tab-bar ((t (:background ,(doom-color 'bg) :foreground ,(doom-color 'bg)))))
 `(tab-bar-tab ((t (
                    :background ,(doom-color 'bg)
                    :foreground ,(doom-color 'yellow)
                    :underline ,(doom-color 'yellow))))))

My title

Prepend tab name with the shortened projectile project title

(setq my/project-title-separators "[-_ ]")

(defun my/shorten-project-name-elem (elem crop)
  (if (string-match "^\\[.*\\]$" elem)
      (concat "["
              (my/shorten-project-name-elem (substring elem 1 (- (length elem) 1)) crop)
              "]")
    (let* ((prefix (car (s-match my/project-title-separators elem)))
           (rest
            (substring
             (if prefix
                 (substring elem (length prefix))
               elem)
             0 (if crop 1 nil))))
      (concat prefix rest))))

(defun my/shorten-project-name (project-name)
  (let ((elems (s-slice-at my/project-title-separators project-name)))
    (concat
     (apply
      #'concat
      (cl-mapcar (lambda (elem) (my/shorten-project-name-elem elem t)) (butlast elems)))
     (my/shorten-project-name-elem (car (last elems)) nil))))

(defun my/tab-bar-name-function ()
  (let ((project-name (projectile-project-name)))
    (if (string= "-" project-name)
        (tab-bar-tab-name-current-with-count)
      (concat "[" (my/shorten-project-name project-name) "] "
              (replace-regexp-in-string "<.*>" "" (tab-bar-tab-name-current-with-count))))))

(setq tab-bar-tab-name-function #'my/tab-bar-name-function)

Modeline

A modeline from Doom Emacs.

References:

(use-package doom-modeline
  :straight t
  :init
  (setq doom-modeline-env-enable-python nil)
  (setq doom-modeline-env-enable-go nil)
  :config
  (doom-modeline-mode 1)
  (setq doom-modeline-minor-modes nil)
  (setq doom-modeline-buffer-state-icon nil))

Font stuff

Emojis

Note Type
TODO Figure out how to display emojis without prettify symbols
(use-package emojify
  :straight t
  :if (not my/lowpower)
  :hook (after-init . global-emojify-mode))

Ligatures

Ligature setup for the JetBrainsMono font.

(use-package ligature
  :straight (:host github :repo "mickeynp/ligature.el")
  :config
  (ligature-set-ligatures
   '(
     typescript-mode
     js2-mode
     vue-mode
     svelte-mode
     scss-mode
     php-mode
     python-mode
     js-mode
     markdown-mode
     clojure-mode
     go-mode
     sh-mode
     haskell-mode)
   '("--" "---" "==" "===" "!=" "!==" "=!=" "=:=" "=/=" "<="
     ">=" "&&" "&&&" "&=" "++" "+++" "***" ";;" "!!" "??"
     "?:" "?." "?=" "<:" ":<" ":>" ">:" "<>" "<<<" ">>>"
     "<<" ">>" "||" "-|" "_|_" "|-" "||-" "|=" "||=" "##"
     "###" "####" "#{" "#[" "]#" "#(" "#?" "#_" "#_(" "#:"
     "#!" "#=" "^=" "<$>" "<$" "$>" "<+>" "<+" "+>" "<*>"
     "<*" "*>" "</" "</>" "/>" "<!--" "<#--" "-->" "->" "->>"
     "<<-" "<-" "<=<" "=<<" "<<=" "<==" "<=>" "<==>" "==>" "=>"
     "=>>" ">=>" ">>=" ">>-" ">-" ">--" "-<" "-<<" ">->" "<-<"
     "<-|" "<=|" "|=>" "|->" "<->" "<~~" "<~" "<~>" "~~" "~~>"
     "~>" "~-" "-~" "~@" "[||]" "|]" "[|" "|}" "{|" "[<"
     ">]" "|>" "<|" "||>" "<||" "|||>" "<|||" "<|>" "..." ".."
     ".=" ".-" "..<" ".?" "::" ":::" ":=" "::=" ":?" ":?>"
     "//" "///" "/*" "*/" "/=" "//=" "/==" "@_" "__"))
  (global-ligature-mode t))

Icons

(use-package all-the-icons
  :straight t)

Highlight todo

(use-package hl-todo
  :hook (prog-mode . hl-todo-mode)
  :straight t)

Text highlight improvements

Hightlight indent guides.

(use-package highlight-indent-guides
  :straight t
  :if (not my/lowpower)
  :hook (
         (prog-mode . highlight-indent-guides-mode)
         (vue-mode . highlight-indent-guides-mode)
         (LaTeX-mode . highlight-indent-guides-mode))
  :config
  (setq highlight-indent-guides-method 'bitmap)
  (setq highlight-indent-guides-bitmap-function 'highlight-indent-guides--bitmap-line))

Rainbow parentheses.

(use-package rainbow-delimiters
  :straight t
  :if (not my/lowpower)
  :hook ((prog-mode . rainbow-delimiters-mode))
  ;; :commands (rainbow-delimiters-mode)
  ;; :init
  ;; (add-hook 'prog-mode-hook
  ;;           (lambda ()
  ;;             (unless (org-in-src-block-p)
  ;;               (rainbow-delimiters-mode))))
  )

Highlight colors

(use-package rainbow-mode
  :commands (rainbow-mode)
  :straight t)

Dired

Dired is a built-in file manager. I use it as my primary file manager, hence the top level of config.

Type Note
TODO Make subdirs mode work

Basic config & keybindings

My config mostly follows ranger's and vifm's keybindings which I'm used to.

(use-package dired
  :ensure nil
  :custom ((dired-listing-switches "-alh --group-directories-first"))
  :commands (dired)
  :config
  (setq dired-dwim-target t)
  (setq wdired-allow-to-change-permissions t)
  (setq wdired-create-parent-directories t)
  (setq dired-recursive-copies 'always)
  (setq dired-recursive-deletes 'always)
  (add-hook 'dired-mode-hook
            (lambda ()
              (setq truncate-lines t)
              (visual-line-mode nil)))
  (evil-collection-define-key 'normal 'dired-mode-map
    "h" 'dired-single-up-directory
    "l" 'dired-single-buffer
    "h" 'dired-single-up-directory
    "l" 'dired-single-buffer
    "=" 'dired-narrow
    "-" 'dired-create-empty-file
    (kbd "<left>") 'dired-single-up-directory
    (kbd "<right>") 'dired-single-buffer)
  (general-define-key
   :keymaps 'dired-mode-map
   [remap dired-find-file] 'dired-single-buffer
   [remap dired-mouse-find-file-other-window] 'dired-single-buffer-mouse
   [remap dired-up-directory] 'dired-single-up-directory
   "M-<return>" 'dired-open-xdg))

(defun my/dired-home ()
  "Open dired at $HOME"
  (interactive)
  (dired (expand-file-name "~")))

(my-leader-def
  "ad" #'dired
  "aD" #'my/dired-home)

Addons

Dired+ provides a lot of extensions for dired functionality.

(use-package dired+
  :straight t
  :after dired
  :init
  (setq diredp-hide-details-initially-flag nil))

Reuse the current dired buffer instead of spamming new ones.

(use-package dired-single
  :after dired
  :straight t)

Display icons for files.

Note Type
ACHTUNG This plugin is slow as hell with TRAMP
(use-package all-the-icons-dired
  :straight t
  :if (not (or my/lowpower my/slow-ssh))
  :hook (dired-mode . all-the-icons-dired-mode)
  :config
  (advice-add 'dired-add-entry :around #'all-the-icons-dired--refresh-advice)
  (advice-add 'dired-remove-entry :around #'all-the-icons-dired--refresh-advice))

Provides stuff like dired-open-xdg

(use-package dired-open
  :straight t
  :commands (dired-open-xdg))

vifm-like filter

(use-package dired-narrow
  :straight t
  :commands (dired-narrow)
  :config
  (general-define-key
   :keymaps 'dired-narrow-map
   [escape] 'keyboard-quit))

TRAMP

TRAMP is a package which provides remote editing capacities. It is particularly useful for remote server management.

One of the reasons why TRAMP may be slow is that some plugins do too much requests to the filesystem. To debug these issues, set the following variable to 6:

(setq tramp-verbose 1)

To check if a file is remote, you can use file-remote-p. E.g. (file-remote-p default-directory) for a current buffer. The problem with this approach is that it's rather awkward to add these checks in every hook, especially for global modes, so for now I just set environment variable for Emacs which disables these modes.

So far I found the following problematic plugins:

Plugin Note Solution
editorconfig looks for .editorconfig in the file tree do not enable globally
all-the-icons-dired runs test on every file in the directory disable
projectile looks for .git, .svn, etc advice projectile-file-name
lsp does a whole lot of stuff disable
git-gutter runs git disable
vterm no proper TRAMP integration use eshell or shell

At any rate, it's usable, although not perfect.

Some other optimization settings:

(setq remote-file-name-inhibit-cache nil)
(setq vc-ignore-dir-regexp
      (format "\\(%s\\)\\|\\(%s\\)"
              vc-ignore-dir-regexp
              tramp-file-name-regexp))

Bookmarks

A simple bookmark list for Dired, mainly to use with TRAMP. I may look into a proper bookmarking system later.

Bookmarks are listed in the dired-bookmarks.el file, which looks like this:

(setq my/dired-bookmarks
      '(("sudo" . "/sudo::/")))

The file itself is encrypted with yadm.

(defun my/dired-bookmark-open ()
  (interactive)
  (unless (boundp 'my/dired-bookmarks)
    (load (concat user-emacs-directory "dired-bookmarks")))
  (let ((bookmarks
         (mapcar
          (lambda (el) (cons (format "%-30s %s" (car el) (cdr el)) (cdr el)))
          my/dired-bookmarks)))
    (dired
     (cdr
      (assoc
       (completing-read "Dired: " bookmarks nil nil "^")
       bookmarks)))))

Shells

vterm

My terminal emulator of choice.

References:

Configuration

I use the package from the Guix repository to avoid building libvterm.

(use-package vterm
  ;; :straight t
  :commands (vterm vterm-other-window)
  :config
  (setq vterm-kill-buffer-on-exit t)

  (add-hook 'vterm-mode-hook
            (lambda ()
              (setq-local global-display-line-numbers-mode nil)
              (display-line-numbers-mode 0)))

  (general-define-key
   :keymaps 'vterm-mode-map
   "M-q" 'vterm-send-escape

   "C-h" 'evil-window-left
   "C-l" 'evil-window-right
   "C-k" 'evil-window-up
   "C-j" 'evil-window-down

   "C-<right>" 'evil-window-right
   "C-<left>" 'evil-window-left
   "C-<up>" 'evil-window-up
   "C-<down>" 'evil-window-down

   "M-<left>" 'vterm-send-left
   "M-<right>" 'vterm-send-right
   "M-<up>" 'vterm-send-up
   "M-<down>" 'vterm-send-down)

  (general-imap
    :keymaps 'vterm-mode-map
    "C-r" 'vterm-send-C-r
    "C-k" 'vterm-send-C-k
    "C-j" 'vterm-send-C-j
    "M-l" 'vterm-send-right
    "M-h" 'vterm-send-left))

Subterminal

Open a terminal in the lower third of the frame with the ` key. That's mostly how I use vterm.

(add-to-list 'display-buffer-alist
             `(,"vterm-subterminal.*"
               (display-buffer-reuse-window
                display-buffer-in-side-window)
               (side . bottom)
               (reusable-frames . visible)
               (window-height . 0.33)))

(defun my/toggle-vterm-subteminal ()
  "Toogle subteminal."
  (interactive)
  (let
      ((vterm-window
        (seq-find
         (lambda (window)
           (string-match
            "vterm-subterminal.*"
            (buffer-name (window-buffer window))))
         (window-list))))
    (if vterm-window
        (if (eq (get-buffer-window (current-buffer)) vterm-window)
            (kill-buffer (current-buffer))
          (select-window vterm-window))
      (vterm-other-window "vterm-subterminal"))))
(unless my/slow-ssh
  (general-nmap "`" 'my/toggle-vterm-subteminal)
  (general-nmap "~" 'vterm))

Eshell

A shell written in Emacs lisp. I don't use it as of now, but keep the config just in case.

(defun my/configure-eshell ()
  (add-hook 'eshell-pre-command-hook 'eshell-save-some-history)
  (add-to-list 'eshell-output-filter-functions 'eshell-truncate-buffer)
  (setq eshell-history-size 10000)
  (setq eshell-hist-ingnoredups t)
  (setq eshell-buffer-maximum-lines 10000)

  (evil-define-key '(normal insert visual) eshell-mode-map (kbd "<home>") 'eshell-bol)
  (evil-define-key '(normal insert visual) eshell-mode-map (kbd "C-r") 'counsel-esh-history)
  (evil-collection-define-key 'normal 'eshell-mode-map
    (kbd "C-h") 'evil-window-left
    (kbd "C-l") 'evil-window-right
    (kbd "C-k") 'evil-window-up
    (kbd "C-j") 'evil-window-down))

(use-package eshell
  :ensure nil
  :after evil-collection
  :commands (eshell)
  :config
  (add-hook 'eshell-first-time-mode-hook 'my/configure-eshell 90)
  (setq eshell-banner-message ""))

(use-package aweshell
  :straight (:repo "manateelazycat/aweshell" :host github)
  :after eshell
  :config
  (custom-set-faces
   `(aweshell-alert-buffer-face ((t (:foreground ,(doom-color 'red) :weight bold))))
   `(aweshell-alert-command-face ((t (:foreground ,(doom-color 'yellow) :weight bold))))
   `(epe-pipeline-delimiter-face ((t (:foreground ,(doom-color 'green)))))
   `(epe-pipeline-host-face ((t (:foreground ,(doom-color 'blue)))))
   `(epe-pipeline-time-face ((t (:foreground ,(doom-color 'yellow)))))
   `(epe-pipeline-user-face ((t (:foreground ,(doom-color 'red))))))
  (setq eshell-highlight-prompt nil)
  (setq eshell-prompt-function 'epe-theme-pipeline))

(use-package eshell-info-banner
  :defer t
  :if (not my/slow-ssh)
  :straight (eshell-info-banner :type git
                                :host github
                                :repo "phundrak/eshell-info-banner.el")
  :hook (eshell-banner-load . eshell-info-banner-update-banner))

(when my/slow-ssh
  (general-nmap "`" 'aweshell-dedicated-toggle)
  (general-nmap "~" 'eshell))

Org Mode

The best feature of Emacs. Just after every other best feature of Emacs, probably.

References:

Installation & basic settings

Use the built-in org mode.

(use-package org
  :straight t
  :defer t
  :config
  (setq org-directory (expand-file-name "~/Documents/org-mode"))
  (setq org-default-notes-file (concat org-directory "/notes.org"))

  (setq org-startup-indented t)
  (setq org-return-follows-link t)
  (setq org-src-tab-acts-natively nil)
  (add-hook 'org-mode-hook 'smartparens-mode)
  (add-hook 'org-agenda-mode-hook
            (lambda ()
              (visual-line-mode -1)
              (toggle-truncate-lines 1)
              (display-line-numbers-mode 0)))
  (add-hook 'org-mode-hook
            (lambda ()
              (rainbow-delimiters-mode -1)))
  <<org-crypt-setup>>
  <<org-lang-setup>>
  <<org-ui-setup>>
  <<org-keys-setup>>)

Encryption

(require 'org-crypt)
(org-crypt-use-before-save-magic)
(setq org-tags-exclude-from-inheritance (quote ("crypt")))
(setq org-crypt-key nil)

Integration with evil

(use-package evil-org
  :straight t
  :hook (org-mode . evil-org-mode)
  :config
  (add-hook 'evil-org-mode-hook
            (lambda ()
              (evil-org-set-key-theme '(navigation insert textobjects additional calendar todo))))
  (add-to-list 'evil-emacs-state-modes 'org-agenda-mode)
  (require 'evil-org-agenda)
  (evil-org-agenda-set-keys))

Literate programing

Python & Jupyter

Use jupyter kernels for Org Mode.

References:

(use-package jupyter
  :straight t
  :init
  (my-leader-def "ar" 'jupyter-run-repl))

Refresh kernelspecs.

Kernelspecs by default are hashed, so even switching Anaconda environments doesn't change kernel (i.e. kernel from the first environment is ran after the switch to the second one).

(defun my/jupyter-refresh-kernelspecs ()
  "Refresh Jupyter kernelspecs"
  (interactive)
  (jupyter-available-kernelspecs t))

Also, if some kernel wasn't present an the moment of load of emacs-jupyter, it won't be added to the org-src-lang-modes list. E.g. I have Hy kernel installed in a separate Anaconda environment, so if Emacs hasn't been launched in this environment, I wouldn't be able to use hy in org-src blocks.

Fortunately, emacs-jupyter provides a function for that problem as well.

(defun my/jupyter-refesh-langs ()
  "Refresh Jupyter languages"
  (interactive)
  (org-babel-jupyter-aliases-from-kernelspecs t))

Hy

Note Description
(use-package ob-hy
  :straight t)

View HTML in browser

Open HTML in the begin_export block with xdg-open.

(setq my/org-view-html-tmp-dir "/tmp/org-html-preview/")

(use-package f
  :straight t)

(defun my/org-view-html ()
  (interactive)
  (let ((elem (org-element-at-point))
        (temp-file-path (concat my/org-view-html-tmp-dir (number-to-string (random (expt 2 32))) ".html")))
    (cond
     ((not (eq 'export-block (car elem)))
      (message "Not in an export block!"))
     ((not (string-equal (plist-get (car (cdr elem)) :type) "HTML"))
      (message "Export block is not HTML!"))
     (t (progn
          (f-mkdir my/org-view-html-tmp-dir)
          (f-write (plist-get (car (cdr elem)) :value) 'utf-8 temp-file-path)
          (start-process "org-html-preview" nil "xdg-open" temp-file-path))))))

Setup

Enable languages

(org-babel-do-load-languages
 'org-babel-load-languages
 '((emacs-lisp . t)
   (python . t)
   ;; (typescript .t)
   (hy . t)
   (shell . t)
   (octave . t)
   (jupyter . t)))

(add-hook 'org-babel-after-execute-hook 'org-redisplay-inline-images)

Use Jupyter block instead of built-in Python.

(org-babel-jupyter-override-src-block "python")
(org-babel-jupyter-override-src-block "hy")

Turn of some minor modes in source blocks.

(add-hook 'org-src-mode-hook
          (lambda ()
            ;; (hs-minor-mode -1)
            ;; (electric-indent-local-mode -1)
            ;; (rainbow-delimiters-mode -1)
            (highlight-indent-guides-mode -1)))

Async code blocks evaluations. Jupyter blocks have a built-in async, so they are set as ignored.

(use-package ob-async
  :straight t
  :after (org)
  :config
  (setq ob-async-no-async-languages-alist '("python" "hy" "jupyter-python" "jupyter-octave")))

Managing Jupyter kernels

Functions for managing local Jupyter kernels.

my/insert-jupyter-kernel inserts a path to an active Jupyter kernel to the buffer. Useful to quickly write a header like:

#+PROPERTY: header-args:python :session <path-to-kernel>

my/jupyter-connect-repl opens a emacs-jupyter REPL, connected to an active kernel. my/jupyter-qtconsole runs a standalone Jupyter QtConsole.

Requirements: ss

(setq my/jupyter-runtime-folder (expand-file-name "~/.local/share/jupyter/runtime"))

(defun my/get-open-ports ()
  (mapcar
   #'string-to-number
   (split-string (shell-command-to-string "ss -tulpnH | awk '{print $5}' | sed -e 's/.*://'") "\n")))

(defun my/list-jupyter-kernel-files ()
  (mapcar
   (lambda (file) (cons (car file) (cdr (assq 'shell_port (json-read-file (car file))))))
   (sort
    (directory-files-and-attributes my/jupyter-runtime-folder t ".*kernel.*json$")
    (lambda (x y) (not (time-less-p (nth 6 x) (nth 6 y)))))))

(defun my/select-jupyter-kernel ()
  (let ((ports (my/get-open-ports))
        (files (my/list-jupyter-kernel-files)))
    (completing-read
     "Jupyter kernels: "
     (seq-filter
      (lambda (file)
        (member (cdr file) ports))
      files))))

(defun my/insert-jupyter-kernel ()
  "Insert a path to an active Jupyter kernel into the buffer"
  (interactive)
  (insert (my/select-jupyter-kernel)))

(defun my/jupyter-connect-repl ()
  "Open an emacs-jupyter REPL, connected to a Jupyter kernel"
  (interactive)
  (jupyter-connect-repl (my/select-jupyter-kernel) nil nil nil t))

(defun my/jupyter-qtconsole ()
  "Open Jupyter QtConsole, connected to a Jupyter kernel"
  (interactive)
  (start-process "jupyter-qtconsole" nil "setsid" "jupyter" "qtconsole" "--existing"
                 (file-name-nondirectory (my/select-jupyter-kernel))))

I've also noticed that there are JSON files left in the runtime folder whenever kernel isn't stopped correctly. So here is a cleanup function.

(defun my/jupyter-cleanup-kernels ()
  (interactive)
  (let* ((ports (my/get-open-ports))
         (files (my/list-jupyter-kernel-files))
         (to-delete (seq-filter
                     (lambda (file)
                       (not (member (cdr file) ports)))
                     files)))
    (when (and (length> to-delete 0)
               (y-or-n-p (format "Delete %d files?" (length to-delete))))
      (dolist (file to-delete)
        (delete-file (car file))))))

Do not wrap output in emacs-jupyter

Emacs-jupyter has its own insertion mechanisms, which always prepents output statements with :. That is not desirable in cases where a kernel supports only plain output, e.g. calysto_hy kernel.

So there we have a minor mode which overrides this behavior.

(defun my/jupyter-org-scalar (value)
  (cond
   ((stringp value) value)
   (t (jupyter-org-scalar value))))

(define-minor-mode my/emacs-jupyter-raw-output
  "Make emacs-jupyter do raw output")

(defun my/jupyter-org-scalar-around (fun value)
  (if my/emacs-jupyter-raw-output
      (my/jupyter-org-scalar value)
    (funcall fun value)))

(advice-add 'jupyter-org-scalar :around #'my/jupyter-org-scalar-around)

Wrap source code output

A function to remove :RESULTS: drawer from the results. Once again, necessary because emacs-jupyter doesn't seem to respect :results raw.

(defun my/org-strip-results (data)
  (replace-regexp-in-string ":\\(RESULTS\\|END\\):\n" "" data))

And an all-in-one function to:

  • prepend #+NAME: and #+CAPTION: to the source block output. Useful if the output is image.
  • strip :RESULTS: drawer from the output, if necessary
  • wrap results in the src block

As for now, looks sufficient to format source code outputs to get a tolerable LaTeX.

(defun my/org-caption-wrap (data &optional name caption attrs strip-drawer src-wrap)
  (let* ((data-s (if (and strip-drawer (not (string-empty-p strip-drawer)))
                     (my/org-strip-results data)
                   data))
         (drawer-start (if (string-match-p "^:RESULTS:.*" data-s) 10 0)))
    (concat
     (substring data-s 0 drawer-start)
     (and name (not (string-empty-p name)) (concat "#+NAME:" name "\n"))
     (and caption (not (string-empty-p caption)) (concat "#+CAPTION:" caption "\n"))
     (and attrs (not (string-empty-p attrs)) (concat "#+ATTR_LATEX:" attrs "\n"))
     (if (and src-wrap (not (string-empty-p src-wrap)))
         (concat "#+begin_src " src-wrap "\n"
                 (substring data-s drawer-start)
                 (when (not (string-match-p ".*\n" data-s)) "\n")
                 "#+end_src")
       (substring data-s drawer-start)))))

To use, add the following snippet to the org file:

#+NAME: out_wrap
#+begin_src emacs-lisp :var data="" caption="" name="" attrs="" strip-drawer="" src-wrap="" :tangle no :exports none
(my/org-caption-wrap data name caption attrs strip-drawer src-wrap)
#+end_src

Example usage:

:post out_wrap(name="fig:chart", caption="График", data=*this*)

UI

Instant equations preview

Instant math previews for org mode.

References:

(use-package org-latex-impatient
  :straight (:repo "yangsheng6810/org-latex-impatient"
                   :branch "master"
                   :host github)
  :hook (org-mode . org-latex-impatient-mode)
  :init
  (setq org-latex-impatient-tex2svg-bin
        "/home/pavel/Programs/miniconda3/lib/node_modules/mathjax-node-cli/bin/tex2svg")
  (setq org-latex-impatient-scale 1.75)
  (setq org-latex-impatient-delay 1)
  (setq org-latex-impatient-border-color "#ffffff"))

LaTeX fragments

A function to enable LaTeX native highlighting. Not setting as default, because it loads LaTeX stuff.

(defun my/enable-org-latex ()
  (interactive)
  (customize-set-variable 'org-highlight-latex-and-related '(native))
  (add-hook 'org-mode-hook (lambda () (yas-activate-extra-mode 'LaTeX-mode))))

Call the function before opening an org file or reopen a buffer after calling the function.

Scale latex fragments preview.

(setq my/org-latex-scale 1.75)
(setq org-format-latex-options (plist-put org-format-latex-options :scale my/org-latex-scale))

Also, LaTeX fragments preview tends to break whenever the are custom #+LATEX_HEADER entries. To circuvment this, I add a custom header and modify the org-preview-latex-process-alist variable

(setq my/latex-preview-header "\\documentclass{article}
\\usepackage[usenames]{color}
\\usepackage{graphicx}
\\usepackage{grffile}
\\usepackage{longtable}
\\usepackage{wrapfig}
\\usepackage{rotating}
\\usepackage[normalem]{ulem}
\\usepackage{amsmath}
\\usepackage{textcomp}
\\usepackage{amssymb}
\\usepackage{capt-of}
\\usepackage{hyperref}
\\pagestyle{empty}")

(setq org-preview-latex-process-alist
      (mapcar
       (lambda (item)
         (cons
          (car item)
          (plist-put (cdr item) :latex-header my/latex-preview-header)))
       org-preview-latex-process-alist))

Better headers

(use-package org-superstar
  :straight t
  :hook (org-mode . org-superstar-mode))

Org Agenda Icons

(if (not my/lowpower)
    (setq org-agenda-category-icon-alist
          `(
            ("work" ,(list (all-the-icons-faicon "cog")) nil nil :ascent center)
            ("lesson" ,(list (all-the-icons-faicon "book")) nil nil :ascent center)
            ("education" ,(list (all-the-icons-material "build")) nil nil :ascent center)
            ("meeting" ,(list (all-the-icons-material "chat")) nil nil :ascent center)
            ("music" ,(list (all-the-icons-faicon "music")) nil nil :ascent center)
            ("misc" ,(list (all-the-icons-material "archive")) nil nil :ascent center)
            ("event" ,(list (all-the-icons-octicon "clock")) nil nil :ascent center))))

Export

Ignore headlines

A function to exclude headlines with :ignore: tag from the export.

The source is ox-extras.el from the org-plus-contrib package, but I can neither install it nor find the original source for some reason.

(defun org-export-ignore-headlines (data backend info)
  "Remove headlines tagged \"ignore\" retaining contents and promoting children.
Each headline tagged \"ignore\" will be removed retaining its
contents and promoting any children headlines to the level of the
parent."
  (org-element-map data 'headline
    (lambda (object)
      (when (member "ignore" (org-element-property :tags object))
        (let ((level-top (org-element-property :level object))
              level-diff)
          (mapc (lambda (el)
                  ;; recursively promote all nested headlines
                  (org-element-map el 'headline
                    (lambda (el)
                      (when (equal 'headline (org-element-type el))
                        (unless level-diff
                          (setq level-diff (- (org-element-property :level el)
                                              level-top)))
                        (org-element-put-property el
                                                  :level (- (org-element-property :level el)
                                                            level-diff)))))
                  ;; insert back into parse tree
                  (org-element-insert-before el object))
                (org-element-contents object)))
        (org-element-extract-element object)))
    info nil)
  data)

(with-eval-after-load 'ox
  (add-hook 'org-export-filter-parse-tree-functions #'org-export-ignore-headlines))

Hugo

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

Jupyter Notebook

(use-package ox-ipynb
  :straight (:host github :repo "jkitchin/ox-ipynb")
  :after ox)

Html export

(use-package htmlize
  :straight t
  :after ox
  :config
  (setq org-html-htmlize-output-type 'css))

LaTeX

Add a custom LaTeX template without default packages. Packages are indented to be imported with function from /sqrtminusone/dotfiles/src/commit/349e9c66aa7066a85c23914657473b3ce577df3b/Import%20%2A.sty.

(defun my/setup-org-latex ()
  (setq org-latex-compiler "xelatex") ;; Probably not necessary
  (setq org-latex-pdf-process '("latexmk -outdir=%o %f")) ;; Use latexmk
  (setq org-latex-listings 'minted) ;; Use minted to highlight source code
  (setq org-latex-minted-options    ;; Some minted options I like
        '(("breaklines" "true")
          ("tabsize" "4")
          ("autogobble")
          ("linenos")
          ("numbersep" "0.5cm")
          ("xleftmargin" "1cm")
          ("frame" "single")))
  ;; Use extarticle without the default packages
  (add-to-list 'org-latex-classes
               '("org-plain-extarticle"
                 "\\documentclass{extarticle}
[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}")))
  ;; Use beamer without the default packages
  (add-to-list 'org-latex-classes
               '("org-latex-beamer"
                 "\\documentclass{beamer}
[NO-DEFAULT-PACKAGES]
[PACKAGES]
[EXTRA]"
                 ("beamer" "\\documentclass[presentation]{beamer}"
                  ("\\section{%s}" . "\\section*{%s}")
                  ("\\subsection{%s}" . "\\subsection*{%s}")
                  ("\\subsubsection{%s}" . "\\subsubsection*{%s}")))))

;; Make sure to eval the function when org-latex-classes list already exists
(with-eval-after-load 'ox-latex
  (my/setup-org-latex))

Keybindings & stuff

(general-define-key
 :keymaps 'org-mode-map
 "C-c d" 'org-decrypt-entry
 "C-c e" 'org-encrypt-entry
 "M-p" 'org-latex-preview
 "M-o" 'org-redisplay-inline-images)

(general-define-key
 :keymaps 'org-mode-map
 :states '(normal emacs)
 "L" 'org-shiftright
 "H" 'org-shiftleft
 "S-<next>" 'org-next-visible-heading
 "S-<prior>" 'org-previous-visible-heading
 "M-0" 'org-next-visible-heading
 "M-9" 'org-previous-visible-heading
 "M-]" 'org-babel-next-src-block
 "M-[" 'org-babel-previous-src-block)

(general-define-key
 :keymaps 'org-agenda-mode-map
 "M-]" 'org-agenda-later
 "M-[" 'org-agenda-earlier)

;; (general-imap :keymaps 'org-mode-map "RET" 'evil-org-return)
(general-nmap :keymaps 'org-mode-map "RET" 'org-ctrl-c-ctrl-c)

(my-leader-def "aa" 'org-agenda)

Copy a link

(defun my/org-link-copy (&optional arg)
  "Extract URL from org-mode link and add it to kill ring."
  (interactive "P")
  (let* ((link (org-element-lineage (org-element-context) '(link) t))
          (type (org-element-property :type link))
          (url (org-element-property :path link))
          (url (concat type ":" url)))
    (kill-new url)
    (message (concat "Copied URL: " url))))

(general-nmap :keymaps 'org-mode-map
    "C-x C-l" 'my/org-link-copy)

Presentations

Doing presentations with org-present.

(use-package hide-mode-line
  :straight t
  :after (org-present))

(defun my/present-next-with-latex ()
  (interactive)
  (org-present-next)
  (org-latex-preview '(16)))

(defun my/present-prev-with-latex ()
  (interactive)
  (org-present-prev)
  (org-latex-preview '(16)))

(use-package org-present
  :straight (:host github :repo "rlister/org-present")
  :commands (org-present)
  :config
  (general-define-key
   :keymaps 'org-present-mode-keymap
   "<next>" 'my/present-next-with-latex
   "<prior>" 'my/present-prev-with-latex)
  (add-hook 'org-present-mode-hook
            (lambda ()
              (blink-cursor-mode 0)
              (org-present-big)
              ;; (org-display-inline-images)
              (org-present-hide-cursor)
              (org-present-read-only)
              (display-line-numbers-mode 0)
              (hide-mode-line-mode +1)
              (setq-local org-format-latex-options
                          (plist-put org-format-latex-options
                                     :scale (* org-present-text-scale my/org-latex-scale 0.5)))
              (org-latex-preview '(16))
              (tab-bar-mode 0)))
  (add-hook 'org-present-mode-quit-hook
            (lambda ()
              (blink-cursor-mode 1)
              (org-present-small)
              ;; (org-remove-inline-images)
              (org-present-show-cursor)
              (org-present-read-write)
              (display-line-numbers-mode 1)
              (hide-mode-line-mode 0)
              (setq-local org-format-latex-options (plist-put org-format-latex-options :scale my/org-latex-scale))
              (org-latex-preview '(64))
              (tab-bar-mode 1))))

TOC

Make a TOC inside the org file.

References:

(use-package org-make-toc
  :after (org)
  :commands
  (org-make-toc
   org-make-toc-insert
   org-make-toc-set
   org-make-toc-at-point)
  :straight t)

org-ref

Type Description
TODO Figure out how not to load Helm

org-ref is a package which provides support for various citations & reference in Org mode.

Useful to use BibTeX citations in LaTeX export.

As of now, this package loads Helm on start. To avoid this, I have to exclude Helm from the Package-requires in the org-ref.el file. I haven't found a way to do this without modifying the package source yet.

(use-package org-ref
  :straight (:files (:defaults (:exclude "*helm*")))
  :init
  (setq org-ref-completion-library 'org-ref-ivy-cite)
  (setq bibtex-dialect 'biblatex)
  :after (org)
  :config
  (general-define-key
   :keymaps 'org-mode-map
   "C-c l l" 'org-ref-ivy-insert-cite-link
   "C-c l r" 'org-ref-ivy-insert-ref-link)
  (add-to-list 'orhc-candidate-formats
               '("online" . "  |${=key=}| ${title} ${url}")))

System configuration

Functions used across my literate config files.

Tables for Guix Dependencies

A function to extract Guix dependencies from the org file. If column name matches [G|g]uix.*dep, its contents will be added to the result.

That seems pretty nice as I'm planning to move to Guix unless I encounter some unmovable obstacles.

(defun my/extract-guix-dependencies (&optional category)
  (let ((dependencies '()))
    (org-table-map-tables
     (lambda ()
       (let* ((table
               (seq-filter
                (lambda (q) (not (eq q 'hline)))
                (org-table-to-lisp)))
              (dep-name-index
               (cl-position
                nil
                (mapcar #'substring-no-properties (nth 0 table))
                :test (lambda (_ elem)
                        (string-match-p "[G|g]uix.*dep" elem))))
              (category-name-index
               (cl-position
                nil
                (mapcar #'substring-no-properties (nth 0 table))
                :test (lambda (_ elem)
                        (string-match-p ".*[C|c]ategory.*" elem)))))
         (when dep-name-index
           (dolist (elem (cdr table))
             (when
                 (or
                  (not category)
                  (not category-name-index)
                  (string-match-p category (nth category-name-index elem)))
               (add-to-list
                'dependencies
                (substring-no-properties (nth dep-name-index elem)))))))))
    dependencies))

Now, join dependencies list to make it compatible with Scheme:

(defun my/format-guix-dependencies (&optional category)
  (mapconcat
   (lambda (e) (concat "\"" e "\""))
   (my/extract-guix-dependencies category)
   "\n"))

Noweb evaluations

Turn off eval confirmations for configuration files.

(setq my/org-config-files
      '("/home/pavel/Emacs.org"
        "/home/pavel/Desktop.org"
        "/home/pavel/Console.org"
        "/home/pavel/Guix.org"))

(add-hook 'org-mode-hook
          (lambda ()
            (when (member (buffer-file-name) my/org-config-files)
              (setq-local org-confirm-babel-evaluate nil))))

OFF (OFF) EAF

Emacs Application Framework provides a way to integrate PyQt applications with Emacs.

I've made it work, but don't find any uses cases for me at the moment

Installation

Requirements: Node >= 14

pip install qtconsole markdown qrcode[pil] PyQt5 PyQtWebEngine

Config

(use-package eaf
  :straight (:host github :repo "manateelazycat/emacs-application-framework" :files ("*"))
  :init
  (use-package epc :defer t :straight t)
  (use-package ctable :defer t :straight t)
  (use-package deferred :defer t :straight t)
  :config
  (require 'eaf-evil)
  (setq eaf-evil-leader-key "SPC"))

Programming

General setup

LSP

LSP-mode provides an IDE-like experience for Emacs - real-time diagnostic, code actions, intelligent autocompletion, etc.

References:

Setup
(use-package lsp-mode
  :straight t
  :if (not my/slow-ssh)
  :hook (
         (typescript-mode . lsp)
         (vue-mode . lsp)
         (go-mode . lsp)
         (svelte-mode . lsp)
         ;; (python-mode . lsp)
         (json-mode . lsp)
         (haskell-mode . lsp)
         (haskell-literate-mode . lsp)
         (java-mode . lsp)
         (csharp-mode . lsp))
  :commands lsp
  :config
  (setq lsp-idle-delay 1)
  (setq lsp-eslint-server-command '("node" "/home/pavel/.emacs.d/.cache/lsp/eslint/unzipped/extension/server/out/eslintServer.js" "--stdio"))
  (setq lsp-eslint-run "onSave")
  (setq lsp-signature-render-documentation nil)
  ;; (lsp-headerline-breadcrumb-mode nil)
  (setq lsp-headerline-breadcrumb-enable nil)
  (add-to-list 'lsp-language-id-configuration '(svelte-mode . "svelte")))

(use-package lsp-ui
  :straight t
  :commands lsp-ui-mode
  :config
  (setq lsp-ui-doc-delay 2)
  (setq lsp-ui-sideline-show-hover nil))
Integrations

The only integration left now is treemacs.

Origami should've leveraged LSP folding, but it was too unstable at the moment I tried it.

;; (use-package helm-lsp
;;   :straight t
;;   :commands helm-lsp-workspace-symbol)

;; (use-package origami
;;   :straight t
;;   :hook (prog-mode . origami-mode))

;; (use-package lsp-origami
;;   :straight t
;;   :config
;;   (add-hook 'lsp-after-open-hook #'lsp-origami-try-enable))

(use-package lsp-treemacs
  :straight t
  :commands lsp-treemacs-errors-list)
Keybindings
(my-leader-def
  "ld" 'lsp-ui-peek-find-definitions
  "lr" 'lsp-rename
  "lu" 'lsp-ui-peek-find-references
  "ls" 'lsp-ui-find-workspace-symbol
  ;; "la" 'helm-lsp-code-actions
  "le" 'list-flycheck-errors)

Flycheck

A syntax checking extension for Emacs. Integrates with LSP-mode, but can also use various standalone checkers.

References:

(use-package flycheck
  :straight t
  :config
  (global-flycheck-mode)
  (setq flycheck-check-syntax-automatically '(save idle-buffer-switch mode-enabled))
  (add-hook 'evil-insert-state-exit-hook
            (lambda ()
              (if flycheck-checker
                  (flycheck-buffer))
              ))
  (advice-add 'flycheck-eslint-config-exists-p :override (lambda() t))
  (add-to-list 'display-buffer-alist
               `(,(rx bos "*Flycheck errors*" eos)
                 (display-buffer-reuse-window
                  display-buffer-in-side-window)
                 (side            . bottom)
                 (reusable-frames . visible)
                 (window-height   . 0.33))))

Tree Sitter

An incremental code parsing system, constructing a syntax tree at runtime.

Right now it doesn't do much expect providing a better syntax highlighting than regexes, but this integration is a rather recent development. There are already some major modes built on top of this thing.

Also, it seems to break if ran from mmm-mode, so there is a small workaround.

References:

(defun my/tree-sitter-if-not-mmm ()
  (when (not (and (boundp 'mmm-temp-buffer-name)
                  (string-equal mmm-temp-buffer-name (buffer-name))))
    (tree-sitter-mode)
    (tree-sitter-hl-mode)))

(use-package tree-sitter
  :straight t
  :hook ((typescript-mode . my/tree-sitter-if-not-mmm)
         (js-mode . my/tree-sitter-if-not-mmm)
         (python-mode . tree-sitter-mode)
         (python-mode . tree-sitter-hl-mode)
         (csharp-mode . tree-sitter-mode)))

(use-package tree-sitter-langs
  :straight t
  :after tree-sitter)

OFF (OFF) DAP

An Emacs client for Debugger Adapter Protocol.

I don't use it now, because there are debuggers I like more for the technologies I'm currently using.

References:

(use-package dap-mode
  :straight t
  :defer t
  :init
  (setq lsp-enable-dap-auto-configure nil)
  :config

  (setq dap-ui-variable-length 100)
  (require 'dap-node)
  (dap-node-setup)

  (require 'dap-chrome)
  (dap-chrome-setup)

  (require 'dap-python)

  (dap-mode 1)
  (dap-ui-mode 1)
  (dap-tooltip-mode 1)
  (tooltip-mode 1)
  (dap-ui-controls-mode 1))

(my-leader-def
  :infix "d"
  "d" 'dap-debug
  "b" 'dap-breakpoint-toggle
  "c" 'dap-breakpoint-condition
  "wl" 'dap-ui-locals
  "wb" 'dap-ui-breakpoints
  "wr" 'dap-ui-repl
  "ws" 'dap-ui-sessions
  "we" 'dap-ui-expressions)

(my-leader-def
  :infix "d"
  :keymaps 'dap-mode-map
  "h" 'dap-hydra)

(defun my/dap-yank-value-at-point (node)
  (interactive (list (treemacs-node-at-point)))
  (kill-new (message (plist-get (button-get node :item) :value))))

OFF (OFF) TabNine

A ML-based autocompletion system.

More often than not gives really good results, but slow as hell & consumes a lot of RAM. Also, LSP-provided completions were more useful in my experience.

References:

(use-package company-tabnine
  :straight t
  :if (not my/lowpower)
  :after company
  :config
  (add-to-list 'company-backends #'company-tabnine))

OFF (OFF) Code Compass

A set of code analysing tools.

References:

Dependencies
(use-package async
  :straight t)
(use-package dash
  :straight t)
(use-package f
  :straight t)
(use-package s
  :straight t)
(use-package simple-httpd
  :straight t)
Plugin
(use-package code-compass
  :straight (
  :repo "ag91/code-compass"
  :files ("code-compass.el")
  :branch "main"
  ))

CHECK (OFF) Format-all

(use-package format-all
  :straight t)

General additional config

Make smartparens behave the way I like for C-like languages.

(defun my/set-smartparens-indent (mode)
  (sp-local-pair mode "{" nil :post-handlers '(("|| " "SPC") ("||\n[i]" "RET")))
  (sp-local-pair mode "[" nil :post-handlers '(("|| " "SPC") ("||\n[i]" "RET")))
  (sp-local-pair mode "(" nil :post-handlers '(("|| " "SPC") ("||\n[i]" "RET"))))

Override flycheck checker with eslint.

(defun set-flycheck-eslint()
  "Override flycheck checker with eslint."
  (setq-local lsp-diagnostic-package :none)
  (setq-local flycheck-checker 'javascript-eslint))

Web development

Configs for various web development technologies I'm using.

Emmet

Emmet is a toolkit which greatly speeds up typing HTML & CSS.

Type Note
TODO Do not enable for every Svelte mode
TODO make expand div[disabled] as <div disabled></div>

My bit of config here:

  • makes Emmet activate only in certain mmm-mode submodes.
  • makes TAB the only key I have to use
(use-package emmet-mode
  :straight t
  :hook ((vue-html-mode . emmet-mode)
         (svelte-mode . emmet-mode)
         (html-mode . emmet-mode)
         (css-mode . emmet-mode)
         (scss-mode . emmet-mode))
  :config
  ;; (setq emmet-indent-after-insert nil)
  (setq my/emmet-mmm-submodes '(vue-html-mode css-mode))
  (defun my/emmet-or-tab (&optional arg)
    (interactive)
    (if (and
         (boundp 'mmm-current-submode)
         mmm-current-submode
         (not (member mmm-current-submode my/emmet-mmm-submodes)))
        (indent-for-tab-command arg)
      (or (emmet-expand-line arg)
          (emmet-go-to-edit-point 1)
          (indent-for-tab-command arg))))
  (general-imap :keymaps 'emmet-mode-keymap
    "TAB" 'my/emmet-or-tab
    "<backtab>" 'emmet-prev-edit-point))

Prettier

(use-package prettier
  :commands (prettier-prettify)
  :straight t
  :init
  (my-leader-def
    :keymaps '(js-mode-map typescript-mode-map vue-mode-map svelte-mode-map)
    "rr" #'prettier-prettify))

TypeScript

(use-package typescript-mode
  :straight t
  :mode "\\.ts\\'"
  :config
  (add-hook 'typescript-mode-hook #'smartparens-mode)
  (add-hook 'typescript-mode-hook #'rainbow-delimiters-mode)
  (add-hook 'typescript-mode-hook #'hs-minor-mode)
  (my/set-smartparens-indent 'typescript-mode))

JavaScript

(add-hook 'js-mode-hook #'smartparens-mode)
(add-hook 'js-mode-hook #'hs-minor-mode)
(my/set-smartparens-indent 'js-mode)

Jest

(use-package jest-test-mode
  :straight t
  :hook ((typescript-mode . jest-test-mode)
         (js-mode . jest-test-mode))
  :config
  (my-leader-def
    :keymaps 'jest-test-mode-map
    :infix "t"
    "t" 'jest-test-run-at-point
    "r" 'jest-test-run
    "a" 'jest-test-run-all-tests))

Vue.js

(use-package vue-mode
  :straight t
  :mode "\\.vue\\'"
  :config
  (add-hook 'vue-mode-hook #'hs-minor-mode)
  (add-hook 'vue-mode-hook #'smartparens-mode)
  (my/set-smartparens-indent 'vue-mode)
  (add-hook 'vue-mode-hook (lambda () (set-face-background 'mmm-default-submode-face nil))))

(with-eval-after-load 'editorconfig
  (add-to-list 'editorconfig-indentation-alist
               '(vue-mode css-indent-offset
                          js-indent-level
                          sgml-basic-offset
                          ssass-tab-width
                          typescript-indent-level
                          emmet-indentation
                          vue-html-extra-indent)))
mmm-mode fix

References:

--- a/mmm-region.el
+++ b/mmm-region.el
@@ -868,9 +868,10 @@ calls each respective submode's `syntax-propertize-function'."
                     (mmm-set-current-pair mode ovl)
                     (mmm-set-local-variables mode mmm-current-overlay)
                     (save-restriction
-                      (when mmm-current-overlay
+                      (if mmm-current-overlay
                         (narrow-to-region (overlay-start mmm-current-overlay)
-                                          (overlay-end mmm-current-overlay)))
+                                          (overlay-end mmm-current-overlay))
+                        (narrow-to-region beg end))
                       (cond
                        (func
                         (funcall func beg end))

Svelte

(use-package svelte-mode
  :straight t
  :mode "\\.svelte\\'"
  :config
  (add-hook 'svelte-mode-hook 'set-flycheck-eslint)
  (add-hook 'svelte-mode-hook #'smartparens-mode)
  (my/set-smartparens-indent 'svelte-mode)
  ;; I have my own Emmet
  (setq lsp-svelte-plugin-css-completions-emmet nil)
  (setq lsp-svelte-plugin-html-completions-emmet nil))

SCSS

(add-hook 'scss-mode-hook #'smartparens-mode)
(add-hook 'scss-mode-hook #'hs-minor-mode)
(my/set-smartparens-indent 'scss-mode)

PHP

(use-package php-mode
  :straight t
  :mode "\\.php\\'")

LaTeX

AUCTeX

The best LaTeX editing environment I've found so far.

References:

(use-package tex
  :straight auctex
  :defer t
  :config
  (setq-default TeX-auto-save t)
  (setq-default TeX-parse-self t)
  (TeX-PDF-mode)
  ;; Use XeLaTeX & stuff
  (setq-default TeX-engine 'xetex)
  (setq-default TeX-command-extra-options "-shell-escape")
  (setq-default TeX-source-correlate-method 'synctex)
  (TeX-source-correlate-mode)
  (setq-default TeX-source-correlate-start-server t)
  (setq-default LaTeX-math-menu-unicode t)

  (setq-default font-latex-fontify-sectioning 1.3)

  ;; Scale preview for my DPI
  (setq-default preview-scale-function 1.4)
  (assoc-delete-all "--" tex--prettify-symbols-alist)
  (assoc-delete-all "---" tex--prettify-symbols-alist)

  (add-hook 'LaTeX-mode-hook
            (lambda ()
              (TeX-fold-mode 1)
              (outline-minor-mode)))

  (add-to-list 'TeX-view-program-selection
               '(output-pdf "Zathura"))

  ;; Do not run lsp within templated TeX files
  (add-hook 'LaTeX-mode-hook
            (lambda ()
               (unless (string-match "\.hogan\.tex$" (buffer-name))
                 (lsp))
               (setq-local lsp-diagnostic-package :none)
               (setq-local flycheck-checker 'tex-chktex)))

  (add-hook 'LaTeX-mode-hook #'rainbow-delimiters-mode)
  (add-hook 'LaTeX-mode-hook #'smartparens-mode)
  (add-hook 'LaTeX-mode-hook #'prettify-symbols-mode)

  (my/set-smartparens-indent 'LaTeX-mode)
  (require 'smartparens-latex)

  (general-nmap
    :keymaps '(LaTeX-mode-map latex-mode-map)
    "RET" 'TeX-command-run-all
    "C-c t" 'orgtbl-mode)

  <<init-greek-latex-snippets>>
  <<init-english-latex-snippets>>
  <<init-math-latex-snippets>>
  <<init-section-latex-snippets>>)

BibTeX

(use-package ivy-bibtex
  :commands (ivy-bibtex)
  :straight t
  :init
  (my-leader-def "fB" 'ivy-bibtex))

(add-hook 'bibtex-mode 'smartparens-mode)

Import *.sty

A function to import .sty files to the LaTeX document.

(defun my/list-sty ()
  (reverse
   (sort
    (seq-filter
     (lambda (file) (if (string-match ".*\.sty$" file) 1 nil))
     (directory-files
      (seq-some
       (lambda (dir)
         (if (and
              (f-directory-p dir)
              (seq-some
               (lambda (file) (string-match ".*\.sty$" file))
               (directory-files dir))
              ) dir nil))
       (list "./styles" "../styles/" "." "..")) :full))
    (lambda (f1 f2)
      (let ((f1b (file-name-base f1))
            (f1b (file-name-base f2)))
        (cond
         ((string-match-p ".*BibTex" f1) t)
         ((and (string-match-p ".*Locale" f1) (not (string-match-p ".*BibTex" f2))) t)
         ((string-match-p ".*Preamble" f2) t)
         (t (string-lessp f1 f2))))))))

(defun my/import-sty ()
  (interactive)
  (insert
   (apply #'concat
          (cl-mapcar
           (lambda (file) (concat "\\usepackage{" (file-name-sans-extension (file-relative-name file default-directory)) "}\n"))
           (my/list-sty)))))

(defun my/import-sty-org ()
  (interactive)
  (insert
   (apply #'concat
          (cl-mapcar
           (lambda (file) (concat "#+LATEX_HEADER: \\usepackage{" (file-name-sans-extension (file-relative-name file default-directory)) "}\n"))
           (my/list-sty)))))

Snippets

Note Type
TODO Move yasnippet snippets here? Maybe extract to a separate file?
Greek letters

Autogenerate snippets for greek letters. I have a few blocks like this because it's faster & more flexible than usual yasnippet snippets.

Noweb points to the AUCTeX config block.

(setq my/greek-alphabet
      '(("a" . "\\alpha")
        ("b" . "\\beta" )
        ("g" . "\\gamma")
        ("d" . "\\delta")
        ("e" . "\\epsilon")
        ("z" . "\\zeta")
        ("h" . "\\eta")
        ("t" . "\\theta")
        ("i" . "\\iota")
        ("k" . "\\kappa")
        ("l" . "\\lambda")
        ("m" . "\\mu")
        ("n" . "\\nu")
        ("x" . "\\xi")
        ("p" . "\\pi")
        ("r" . "\\rho")
        ("s" . "\\sigma")
        ("t" . "\\tau")
        ("u" . "\\upsilon")
        ("f" . "\\phi")
        ("c" . "\\chi")
        ("v" . "\\psi")
        ("g" . "\\omega")))

(setq my/latex-greek-prefix "'")

;; The same for capitalized letters
(dolist (elem my/greek-alphabet)
  (let ((key (car elem))
        (value (cdr elem)))
    (when (string-equal key (downcase key))
      (add-to-list 'my/greek-alphabet
                   (cons
                    (capitalize (car elem))
                    (concat
                     (substring value 0 1)
                     (capitalize (substring value 1 2))
                     (substring value 2)))))))

(yas-define-snippets
 'latex-mode
 (mapcar
  (lambda (elem)
    (list (concat my/latex-greek-prefix (car elem)) (cdr elem) (concat "Greek letter " (car elem))))
  my/greek-alphabet))
English letters
(setq my/english-alphabet
      '("a" "b" "c" "d" "e" "f" "g" "h" "i" "j" "k" "l" "m" "n" "o" "p" "q" "r" "s" "t" "u" "v" "w" "x" "y" "z"))

(dolist (elem my/english-alphabet)
  (when (string-equal elem (downcase elem))
    (add-to-list 'my/english-alphabet (upcase elem))))

(setq my/latex-mathbb-prefix "`")

(yas-define-snippets
 'latex-mode
 (mapcar
  (lambda (elem)
    (list (concat my/latex-mathbb-prefix elem) (concat "\\mathbb{" elem "}") (concat "Mathbb letter " elem)))
  my/english-alphabet))
Math symbols
(setq my/latex-math-symbols
      '(("x" . "\\times")
        ("." . "\\cdot")
        ("v" . "\\forall")
        ("s" . "\\sum_{$1}^{$2}$0")
        ("p" . "\\prod_{$1}^{$2}$0")
        ("e" . "\\exists")
        ("i" . "\\int_{$1}^{$2}$0")
        ("c" . "\\cap")
        ("u" . "\\cup")
        ("0" . "\\emptyset")))

(setq my/latex-math-prefix "''")

(yas-define-snippets
 'latex-mode
 (mapcar
  (lambda (elem)
    (let ((key (car elem))
          (value (cdr elem)))
      (list (concat my/latex-math-prefix key) value (concat "Math symbol " value))))
  my/latex-math-symbols))
Section snippets

Section snippets. The code turned out to be more complicated than just writing the snippets by hand.

(setq my/latex-section-snippets
      '(("ch" . "\\chapter{$1}")
        ("sec" . "\\section{$1}")
        ("ssec" . "\\subsection{$1}")
        ("sssec" . "\\subsubsection{$1}")
        ("par" . "\\paragraph{$1}}")))

(setq my/latex-section-snippets
      (mapcar
       (lambda (elem)
         `(,(car elem)
           ,(cdr elem)
           ,(progn
              (string-match "[a-z]+" (cdr elem))
              (match-string 0 (cdr elem)))))
       my/latex-section-snippets))

(dolist (elem my/latex-section-snippets)
  (let* ((key (nth 0 elem))
         (value (nth 1 elem))
         (desc (nth 2 elem))
         (star-index (string-match "\{\$1\}" value)))
    (add-to-list 'my/latex-section-snippets
                 `(,(concat key "*")
                   ,(concat
                     (substring value 0 star-index)
                     "*"
                     (substring value star-index))
                   ,(concat desc " with *")))
    (add-to-list 'my/latex-section-snippets
                 `(,(concat key "l")
                   ,(concat value "%\n\\label{sec:$2}")
                   ,(concat desc " with label")))))

(dolist (elem my/latex-section-snippets)
  (setf (nth 1 elem) (concat (nth 1 elem) "\n$0")))

(yas-define-snippets
 'latex-mode
 my/latex-section-snippets)

Other markup languages

Markdown

(use-package markdown-mode
  :straight t
  :mode "\\.md\\'"
  :config
  (setq markdown-command
        (concat
         "pandoc"
         " --from=markdown --to=html"
         " --standalone --mathjax --highlight-style=pygments"
         " --css=pandoc.css"
         " --quiet"
         ))
  (setq markdown-live-preview-delete-export 'delete-on-export)
  (setq markdown-asymmetric-header t)
  (setq markdown-open-command "/home/pavel/bin/scripts/chromium-sep")
  (add-hook 'markdown-mode-hook #'smartparens-mode)
  (general-define-key
   :keymaps 'markdown-mode-map
   "M-<left>" 'markdown-promote
   "M-<right>" 'markdown-demote))

;; (use-package livedown
;;   :straight (:host github :repo "shime/emacs-livedown")
;;   :commands livedown-preview
;;   :config
;;   (setq livedown-browser "qutebrowser"))

PlantUML

(use-package plantuml-mode
  :straight t
  :mode "(\\.\\(plantuml?\\|uml\\|puml\\)\\'"
  :config
  (setq plantuml-executable-path "/usr/bin/plantuml")
  (setq plantuml-default-exec-mode 'executable)
  (add-to-list 'auto-mode-alist '("\\.plantuml\\'" . plantuml-mode))
  (add-to-list 'auto-mode-alist '("\\.uml\\'" . plantuml-mode))
  (add-hook 'plantuml-mode-hook #'smartparens-mode))

(general-nmap
  :keymaps 'plantuml-mode-map
  "RET" 'plantuml-preview)

LanguageTool

LanguageTool is a great offline spell checker. For some reason the download link is nowhere to be found on the home page, so it is listed in the references as well.

References:

(use-package langtool
  :straight t
  :commands (langtool-check)
  :config
  (setq langtool-language-tool-server-jar "/home/pavel/Programs/LanguageTool-5.1/languagetool-server.jar")
  (setq langtool-mother-tongue "ru")
  (setq langtool-default-language "en-US"))

(my-leader-def
  :infix "L"
  "c" 'langtool-check
  "s" 'langtool-server-stop
  "d" 'langtool-check-done
  "n" 'langtool-goto-next-error
  "p" 'langtool-goto-previous-error
  "l" 'langtool-correct-buffer)

Lisp

These are your father's parentheses. Elegant weapons for a more… civilized age.

Meta Lisp

Some packages for editing various Lisps.

(use-package lispy
  :commands (lispy-mode)
  :straight t)

(use-package lispyville
  :hook (lispy-mode . lispyville-mode)
  :straight t)

(sp-with-modes sp-lisp-modes
  (sp-local-pair "'" nil :actions nil))

Emacs Lisp

(add-hook 'emacs-lisp-mode-hook #'aggressive-indent-mode)
;; (add-hook 'emacs-lisp-mode-hook #'smartparens-strict-mode)
(add-hook 'emacs-lisp-mode-hook #'lispy-mode)

Clojure

(use-package clojure-mode
  :straight t
  :mode "\\.clj[sc]?\\'"
  :config
  ;; (add-hook 'clojure-mode-hook #'smartparens-strict-mode)
  (add-hook 'clojure-mode-hook #'lispy-mode)
  (add-hook 'clojure-mode-hook #'aggressive-indent-mode))

(use-package cider
  :mode "\\.clj[sc]?\\'"
  :straight t)

Hy

Python requirements:

  • hy
  • jedhy
(use-package hy-mode
  :straight t
  :mode "\\.hy\\'"
  :config
  (add-hook 'hy-mode-hook #'lispy-mode)
  (add-hook 'hy-mode-hook #'aggressive-indent-mode))

Scheme

(use-package geiser
  :straight t
  :if (not my/lowpower)
  :config
  (setq geiser-default-implementation 'guile))

(use-package geiser-guile
  :straight t
  :after geiser)

(add-hook 'scheme-mode-hook #'aggressive-indent-mode)
(add-hook 'scheme-mode-hook #'lispy-mode)

CLIPS

An honorary Lisp

(use-package clips-mode
  :straight t
  :mode "\\.cl\\'"
  :config
  (add-hook 'clips-mode 'lispy-mode))

Python

Use Microsoft Language Server for Python.

For some reason it doesn't use pipenv python executable, so here is a small workaround.

(setq my/pipenv-python-alist '())

(defun my/get-pipenv-python ()
  (let ((default-directory (projectile-project-root)))
    (if (file-exists-p "Pipfile")
        (let ((asc (assoc default-directory my/pipenv-python-alist)))
          (if asc
              (cdr asc)
            (let ((python-executable
                   (string-trim (shell-command-to-string "PIPENV_IGNORE_VIRTUALENVS=1 pipenv run which python"))))
              (if (string-match-p ".*not found.*" python-executable)
                  (message "Pipfile found, but not pipenv executable!")
                (message (format "Found pipenv python: %s" python-executable))
                (add-to-list 'my/pipenv-python-alist (cons default-directory python-executable))
                python-executable))))
      "python")))

(use-package lsp-python-ms
  :straight t
  :defer t
  :if (not my/slow-ssh)
  :init (setq lsp-python-ms-auto-install-server t)
  :hook (python-mode . (lambda ()
                         (require 'lsp-python-ms)
                         (setq-local lsp-python-ms-python-executable (my/get-pipenv-python))
                         (lsp))))

(add-hook 'python-mode-hook #'smartparens-mode)
(add-hook 'python-mode-hook #'hs-minor-mode)

pipenv

Pipenv is a package manager for Python.

Automatically creates & manages virtualenvs and stores data in Pipfile and Pipfile.lock (like npm's package.json and package-lock.json).

(use-package pipenv
  :straight t
  :hook (python-mode . pipenv-mode)
  :if (not my/slow-ssh)
  :init
  (setq
   pipenv-projectile-after-switch-function
   #'pipenv-projectile-after-switch-extended))

yapf

yapf is a formatter for Python files.

References:

(use-package yapfify
  :straight (:repo "JorisE/yapfify" :host github)
  :commands (yapfify-region
             yapfify-buffer
             yapfify-region-or-buffer
             yapf-mode))

Global config:

[style]
based_on_style = facebook
column_limit = 80

isort

isort is a Python package to sort Python imports.

References:

(use-package py-isort
  :straight t
  :commands (py-isort-buffer py-isort-region))

The following bindings calls yapf & isort on the buffer

(my-leader-def
  :keymaps 'python-mode-map
  "rr" (lambda ()
         (interactive)
         (unless (and (fboundp #'org-src-edit-buffer-p) (org-src-edit-buffer-p))
           (py-isort-buffer))
         (yapfify-buffer)))

sphinx-doc

A package to generate sphinx-compatible docstrings.

(use-package sphinx-doc
  :straight t
  :hook (python-mode . sphinx-doc-mode)
  :config
  (my-leader-def
    :keymaps 'sphinx-doc-mode-map
    "rd" 'sphinx-doc))

pytest

pytest is an unit testing framework for Python.

Once again a function to set pytest executable from pipenv.

References:

(defun my/set-pipenv-pytest ()
  (setq-local
   python-pytest-executable
   (concat (my/get-pipenv-python) " -m pytest")))

(use-package python-pytest
  :straight t
  :commands (python-pytest-dispatch)
  :init
  (my-leader-def
    :keymaps 'python-mode-map
    :infix "t"
    "t" 'python-pytest-dispatch)
  :config
  <<override-pytest-run>>
  (add-hook 'python-mode-hook #'my/set-pipenv-pytest)
  (when (derived-mode-p 'python-mode)
    (my/set-pipenv-pytest)))
Fix comint buffer width

For some reason default comint output width is way too large.

To fix that, I've modified the following function in the python-pytest package.

(cl-defun python-pytest--run-as-comint (&key command)
  "Run a pytest comint session for COMMAND."
  (let* ((buffer (python-pytest--get-buffer))
         (process (get-buffer-process buffer)))
    (with-current-buffer buffer
      (when (comint-check-proc buffer)
        (unless (or compilation-always-kill
                    (yes-or-no-p "Kill running pytest process?"))
          (user-error "Aborting; pytest still running")))
      (when process
        (delete-process process))
      (let ((inhibit-read-only t))
        (erase-buffer))
      (unless (eq major-mode 'python-pytest-mode)
        (python-pytest-mode))
      (compilation-forget-errors)
      (display-buffer buffer)
      (setq command (format "export COLUMNS=%s; %s"
                            (- (window-width (get-buffer-window buffer)) 5)
                            command))
      (insert (format "cwd: %s\ncmd: %s\n\n" default-directory command))
      (setq python-pytest--current-command command)
      (when python-pytest-pdb-track
        (add-hook
         'comint-output-filter-functions
         'python-pdbtrack-comint-output-filter-function
         nil t))
      (run-hooks 'python-pytest-setup-hook)
      (make-comint-in-buffer "pytest" buffer "bash" nil "-c" command)
      (run-hooks 'python-pytest-started-hook)
      (setq process (get-buffer-process buffer))
      (set-process-sentinel process #'python-pytest--process-sentinel))))

code-cells

Support for text with magic comments.

(use-package code-cells
  :straight t
  :commands (code-cells-mode))

tensorboard

A function to start up TensorBoard.

(setq my/tensorboard-buffer "TensorBoard-out")

(defun my/tensorboard ()
  (interactive)
  (start-process
   "tensorboard"
   my/tensorboard-buffer
   "tensorboard"
   "serve"
   "--logdir"
   (car (find-file-read-args "Directory: " t)))
  (display-buffer my/tensorboard-buffer))

Java

(use-package lsp-java
  :straight t
  :after (lsp)
  :config
  (setq lsp-java-jdt-download-url "https://download.eclipse.org/jdtls/milestones/0.57.0/jdt-language-server-0.57.0-202006172108.tar.gz"))

(add-hook 'java-mode-hook #'smartparens-mode)
;; (add-hook 'java-mode-hook #'hs-minor-mode)
(my/set-smartparens-indent 'java-mode)

Go

(use-package go-mode
  :straight t
  :mode "\\.go\\'"
  :config
  (my/set-smartparens-indent 'go-mode)
  (add-hook 'go-mode-hook #'smartparens-mode)
  (add-hook 'go-mode-hook #'hs-minor-mode))

.NET

C#

(use-package csharp-mode
  :straight t
  :mode "\\.cs\\'"
  :config
  (add-hook 'csharp-mode-hook #'csharp-tree-sitter-mode)
  (add-hook 'csharp-tree-sitter-mode-hook #'smartparens-mode)
  (add-hook 'csharp-mode-hook #'hs-minor-mode)
  (my/set-smartparens-indent 'csharp-tree-sitter-mode))

MSBuild

(use-package csproj-mode
  :straight t
  :mode "\\.csproj\\'"
  :config
  (add-hook 'csproj-mode #'smartparens-mode))

fish

(use-package fish-mode
  :straight t
  :mode "\\.fish\\'"
  :config
 (add-hook 'fish-mode-hook #'smartparens-mode))

sh

(add-hook 'sh-mode-hook #'smartparens-mode)

Haskell

(use-package haskell-mode
  :straight t
  :mode "\\.hs\\'")

(use-package lsp-haskell
  :straight t
  :after (lsp haskell-mode))

JSON

(use-package json-mode
  :straight t
  :mode "\\.json\\'"
  :config
  (add-hook 'json-mode #'smartparens-mode)
  (add-hook 'json-mode #'hs-minor-mode)
  (my/set-smartparens-indent 'json-mode))

YAML

(use-package yaml-mode
  :straight t
  :mode "\\.yml\\'"
  :config
  (add-hook 'yaml-mode-hook 'smartparens-mode)
  (add-hook 'yaml-mode-hook 'highlight-indent-guides-mode)
  (add-to-list 'auto-mode-alist '("\\.yml\\'" . yaml-mode)))

.env

(use-package dotenv-mode
  :straight t
  :mode "\\.env\\..*\\'")

CSV

(use-package csv-mode
  :straight t
  :mode "\\.csv\\'")

OFF (OFF) PDF

A decent package to view PDFs in Emacs, but I prefer Zathura.

References:

(use-package pdf-tools
  :straight t
  :commands (pdf-tools-install))

Docker

(use-package dockerfile-mode
  :mode "Dockerfile\\'"
  :straight t
  :config
  (add-hook 'dockerfile-mode 'smartparens-mode))

Apps & Misc

Managing dotfiles

A bunch of functions for managing dotfiles with yadm.

Open Emacs config

(defun my/edit-configuration ()
  "Open the init file."
  (interactive)
  (find-file "~/Emacs.org"))

;; (defun my/edit-exwm-configuration ()
;;   "Open the exwm config file."
;;   (interactive)
;;   (find-file "~/.emacs.d/exwm.org"))

(general-define-key "C-c c" 'my/edit-configuration)
;; (general-define-key "C-c C" 'my/edit-exwm-configuration)
(my-leader-def "cc" 'my/edit-configuration)

Open Magit for yadm

Idea:

(with-eval-after-load 'tramp
  (add-to-list 'tramp-methods
               '("yadm"
                 (tramp-login-program "yadm")
                 (tramp-login-args (("enter")))
                 (tramp-login-env (("SHELL") ("/bin/sh")))
                 (tramp-remote-shell "/bin/sh")
                 (tramp-remote-shell-args ("-c")))))


(defun my/yadm-magit ()
  (interactive)
  (magit-status "/yadm::"))

(my-leader-def "cm" 'my/yadm-magit)

Open a dotfile

Open a file managed by yadm.

(defun my/open-yadm-file ()
  "Open a file managed by yadm"
  (interactive)
  (find-file
   (concat
    (file-name-as-directory (getenv "HOME"))
    (completing-read
     "yadm files: "
     (split-string
      (shell-command-to-string "yadm ls-files $HOME --full-name") "\n")))))

(general-define-key "C-c f" 'my/open-yadm-file)
(my-leader-def "cf" 'my/open-yadm-file)

Notmuch

My notmuch config.

References:

(use-package notmuch
  :ensure nil
  :commands (notmuch)
  :config
  (setq mail-specify-envelope-from t)
  (setq message-sendmail-envelope-from 'header)
  (setq mail-envelope-from 'header)
  (setq notmuch-always-prompt-for-sender t)
  (setq sendmail-program "/usr/bin/msmtp")
  (setq send-mail-function #'sendmail-send-it)
  (add-hook 'notmuch-hello-mode-hook
            (lambda () (display-line-numbers-mode 0)))
  (custom-set-faces
   `(notmuch-wash-cited-text ((t (:foreground ,(doom-color 'yellow)))))))

(my-leader-def "am" 'notmuch)

Elfeed

elfeed is an Emacs RSS client.

The advice there sets shr-use-fonts to nil while rendering HTML, so the elfeed-show buffer will use monospace font.

Using my own fork until the modifications are merged into master.

(use-package elfeed
  :straight (:repo "SqrtMinusOne/elfeed" :host github)
  :commands (elfeed)
  :init
  (my-leader-def "ae" 'elfeed)
  :config
  (advice-add #'elfeed-insert-html
              :around
              (lambda (fun &rest r)
                (let ((shr-use-fonts nil))
                  (apply fun r))))
  (custom-set-faces
   `(elfeed-search-tag-face ((t (:foreground ,(doom-color 'yellow))))))
  (evil-collection-define-key 'normal 'elfeed-search-mode-map
    "o" #'my/elfeed-search-filter-source
    "c" #'elfeed-search-clear-filter)
  (evil-collection-define-key 'normal 'elfeed-show-mode-map
    "ge" #'my/elfeed-show-visit-eww))

elfeed-org allows configuring Elfeed feeds with an Org file.

(use-package elfeed-org
  :straight t
  :after (elfeed)
  :config
  (elfeed-org))

Some additions

Filter elfeed search buffer by the feed under the cursor.

(defun my/elfeed-search-filter-source (entry)
  "Filter elfeed search buffer by the feed under cursor."
  (interactive (list (elfeed-search-selected :ignore-region)))
  (when (elfeed-entry-p entry)
    (elfeed-search-set-filter
     (concat
      "@6-months-ago "
      "+unread "
      "="
      (replace-regexp-in-string
       (rx "?" (* not-newline) eos)
       ""
       (elfeed-feed-url (elfeed-entry-feed entry)))))))

Open a URL with eww.

(defun my/elfeed-show-visit-eww ()
  "Visit the current entry in eww"
  (interactive)
  (let ((link (elfeed-entry-link elfeed-show-entry)))
    (when link
      (eww link))))

ERC

ERC is a built-it Emacs IRC client.

(my-leader-def "ai" #'erc-tls)

A plugin to highlight IRC nicknames:

(use-package erc-hl-nicks
  :hook (erc-mode . erc-hl-nicks-mode)
  :straight t)

Config of my ZNC instance.

(setq erc-server "sqrtminusone.xyz")
(setq erc-port 1984)
(setq erc-nick "sqrtminusone")
(setq erc-user-full-name "Pavel Korytov")
(setq erc-track-shorten-start 8)

Kill buffer on part.

(setq erc-kill-buffer-on-part t)

ZNC support. Seems to provide a few nice features for ZNC.

(use-package znc
  :straight t
  :after (erc))

Docker

A package to manage docker containers from Emacs.

(use-package docker
  :straight t
  :commands (docker)
  :init
  (my-leader-def "ao" 'docker))

Google Translate

Emacs interface to Google Translate.

Can't make it load lazily for some strange reason.

References:

(use-package google-translate
  :straight t
  :functions (my-google-translate-at-point google-translate--search-tkk)
  :custom
  (google-translate-backend-method 'curl)
  :config
  (require 'facemenu)
  (defun google-translate--search-tkk ()
    "Search TKK."
    (list 430675 2721866130))
  (defun my-google-translate-at-point()
    "reverse translate if prefix"
    (interactive)
    (if current-prefix-arg
        (google-translate-at-point)
      (google-translate-at-point-reverse)))
  (setq google-translate-translation-directions-alist
        '(("en" . "ru")
          ("ru" . "en"))))

(my-leader-def
  "atp" 'google-translate-at-point
  "atP" 'google-translate-at-point-reverse
  "atq" 'google-translate-query-translate
  "atQ" 'google-translate-query-translate-reverse
  "att" 'google-translate-smooth-translate)

Pomidor

A simple pomodoro technique timer.

(use-package pomidor
  :straight t
  :commands (pomidor)
  :init
  (my-leader-def "ap" #'pomidor)
  :config
  (setq pomidor-sound-tick nil)
  (setq pomidor-sound-tack nil)
  (evil-collection-define-key 'normal 'pomidor-mode-map
    (kbd "q") #'quit-window
    (kbd "Q") #'pomidor-quit
    (kbd "R") #'pomidor-reset
    (kbd "h") #'pomidor-hold
    (kbd "H") #'pomidor-unhold
    (kbd "RET") #'pomidor-stop
    (kbd "M-RET") #'pomidor-break))

EWW

Emacs built-in web browser. I wonder if anyone actually uses it.

I use it occasionally to open links in elfeed.

(defun my/toggle-shr-use-fonts ()
  "Toggle the shr-use-fonts variable in buffer"
  (interactive)
  (setq-local shr-use-fonts (not shr-use-fonts)))

(my-leader-def "aw" 'eww)

(general-define-key
 :keymaps 'eww-mode-map
 "+" 'text-scale-increase
 "-" 'text-scale-decrease)

Snow

(use-package snow
  :straight (:repo "alphapapa/snow.el" :host github)
  :commands (snow))

Zone

(use-package zone
  :ensure nil
  :config
  (setq original-zone-programs (copy-sequence zone-programs)))

(defun my/zone-with-select ()
  (interactive)
  (ivy-read "Zone programs"
            (cl-pairlis
             (cl-mapcar 'symbol-name original-zone-programs)
             original-zone-programs)
            :action (lambda (elem)
                      (setq zone-programs (vector (cdr elem)))
                      (zone))))

Discord integration

Integration with Discord.

Shows which file is being edited in Emacs.

(use-package elcord
  :straight t
  :if (and (string= (system-name) "pdsk") (not my/slow-ssh))
  :config
  (elcord-mode))
** Guix
#+begin_src emacs-lisp
(use-package guix
  :straight t
  :commands (guix)
  :init
  (my-leader-def "ag" 'guix))

Guix settings

Guix dependency Description
emacs-vterm A vterm package
ripgrep A recursive search tool
the-silver-searcher Another recursive search tool
(my/format-guix-dependencies)
(specifications->manifest
 '("emacs"
   <<packages()>>))