#+PROPERTY: header-args :mkdirp yes #+PROPERTY: header-args:bash :tangle-mode (identity #o755) :comments link :shebang "#!/usr/bin/env bash" #+PROPERTY: header-args:emacs-lisp :tangle ~/.emacs.d/init.el :mkdirp yes #+TODO: CHECK(s) | OFF(o) #+begin_quote 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. #+end_quote My [[https://www.gnu.org/software/emacs/][Emacs]] configuration. As with other files in the repo, parts prefixed with (OFF) are not used but kept for historic purposes. * Contents :PROPERTIES: :TOC: :include all :depth 4 :END: :CONTENTS: - [[#contents][Contents]] - [[#primary-setup][Primary setup]] - [[#measure-startup-speed][Measure startup speed]] - [[#straightel][straight.el]] - [[#use-package][use-package]] - [[#performance][Performance]] - [[#garbage-collection][Garbage collection]] - [[#run-garbage-collection-when-emacs-is-unfocused][Run garbage collection when Emacs is unfocused]] - [[#misc][Misc]] - [[#native-compilation][Native compilation]] - [[#anaconda--environment][Anaconda & environment]] - [[#custom-file-location][Custom file location]] - [[#no-littering][No littering]] - [[#global-editing-configuration][Global editing configuration]] - [[#general-keybindings-stuff][General keybindings stuff]] - [[#generalel][general.el]] - [[#which-key][which-key]] - [[#evil-mode][Evil mode]] - [[#evil][evil]] - [[#addons][Addons]] - [[#evil-collection][evil-collection]] - [[#more-keybindigs][More keybindigs]] - [[#escape-key][Escape key]] - [[#home--end][Home & end]] - [[#my-leader][My leader]] - [[#universal-argument][Universal argument]] - [[#profiler][Profiler]] - [[#buffer-switching][Buffer switching]] - [[#xref][xref]] - [[#folding][Folding]] - [[#zoom][Zoom]] - [[#editing-helpers][Editing helpers]] - [[#visual-fill-column-mode][Visual fill column mode]] - [[#smartparens][smartparens]] - [[#aggressive-indent][Aggressive Indent]] - [[#delete-trailing-whitespace][Delete trailing whitespace]] - [[#expand-region][Expand region]] - [[#various-settings][Various settings]] - [[#tabs][Tabs]] - [[#scrolling-config][Scrolling config]] - [[#clipboard][Clipboard]] - [[#backups][Backups]] - [[#undo-tree][Undo Tree]] - [[#help][Help]] - [[#ivy-counsel-swiper][Ivy, counsel, swiper]] - [[#ivy-rich][ivy-rich]] - [[#prescient][prescient]] - [[#keybindings][Keybindings]] - [[#off-helm][(OFF) Helm]] - [[#treemacs][Treemacs]] - [[#helper-functions][Helper functions]] - [[#projectile][Projectile]] - [[#company][Company]] - [[#git--magit][Git & Magit]] - [[#editorconfig][Editorconfig]] - [[#off-avy][(OFF) Avy]] - [[#snippets][Snippets]] - [[#time-trackers][Time trackers]] - [[#wakatime][WakaTime]] - [[#activitywatch][ActivityWatch]] - [[#ui][UI]] - [[#general-ui--gui-settings][General UI & GUI Settings]] - [[#theme--global-stuff][Theme & global stuff]] - [[#custom-theme][Custom theme]] - [[#font][Font]] - [[#custom-frame-title][Custom frame title]] - [[#tab-bar][Tab bar]] - [[#setup][Setup]] - [[#my-title][My title]] - [[#modeline][Modeline]] - [[#font-stuff][Font stuff]] - [[#emojis][Emojis]] - [[#ligatures][Ligatures]] - [[#icons][Icons]] - [[#highlight-todo][Highlight todo]] - [[#text-highlight-improvements][Text highlight improvements]] - [[#dired][Dired]] - [[#basic-config--keybindings][Basic config & keybindings]] - [[#addons][Addons]] - [[#dired-on-emacs-28][dired+ on Emacs 28]] - [[#subdirectories][Subdirectories]] - [[#tramp][TRAMP]] - [[#bookmarks][Bookmarks]] - [[#shells][Shells]] - [[#vterm][vterm]] - [[#configuration][Configuration]] - [[#subterminal][Subterminal]] - [[#dired-integration][Dired integration]] - [[#eshell][Eshell]] - [[#org-mode][Org Mode]] - [[#installation--basic-settings][Installation & basic settings]] - [[#encryption][Encryption]] - [[#org-contrib][org-contrib]] - [[#integration-with-evil][Integration with evil]] - [[#literate-programing][Literate programing]] - [[#python--jupyter][Python & Jupyter]] - [[#hy][Hy]] - [[#view-html-in-browser][View HTML in browser]] - [[#setup][Setup]] - [[#managing-jupyter-kernels][Managing Jupyter kernels]] - [[#do-not-wrap-output-in-emacs-jupyter][Do not wrap output in emacs-jupyter]] - [[#wrap-source-code-output][Wrap source code output]] - [[#productivity--knowledge-management][Productivity & Knowledge management]] - [[#capture-templates--various-settings][Capture templates & various settings]] - [[#custom-agendas][Custom agendas]] - [[#org-journal][Org Journal]] - [[#org-roam][Org Roam]] - [[#org-roam-protocol][org-roam-protocol]] - [[#org-ref][org-ref]] - [[#org-roam-bibtex][org-roam-bibtex]] - [[#autocommit][autocommit]] - [[#ui][UI]] - [[#off-instant-equations-preview][(OFF) Instant equations preview]] - [[#latex-fragments][LaTeX fragments]] - [[#better-headers][Better headers]] - [[#org-agenda-icons][Org Agenda Icons]] - [[#export][Export]] - [[#hugo][Hugo]] - [[#jupyter-notebook][Jupyter Notebook]] - [[#html-export][Html export]] - [[#latex][LaTeX]] - [[#keybindings--stuff][Keybindings & stuff]] - [[#copy-a-link][Copy a link]] - [[#presentations][Presentations]] - [[#toc][TOC]] - [[#system-configuration][System configuration]] - [[#tables-for-guix-dependencies][Tables for Guix Dependencies]] - [[#noweb-evaluations][Noweb evaluations]] - [[#yadm-hook][yadm hook]] - [[#off-eaf][(OFF) EAF]] - [[#installation][Installation]] - [[#config][Config]] - [[#programming][Programming]] - [[#general-setup][General setup]] - [[#lsp][LSP]] - [[#setup][Setup]] - [[#integrations][Integrations]] - [[#keybindings][Keybindings]] - [[#flycheck][Flycheck]] - [[#tree-sitter][Tree Sitter]] - [[#off-dap][(OFF) DAP]] - [[#off-tabnine][(OFF) TabNine]] - [[#off-code-compass][(OFF) Code Compass]] - [[#dependencies][Dependencies]] - [[#plugin][Plugin]] - [[#off-format-all][(OFF) Format-all]] - [[#general-additional-config][General additional config]] - [[#web-development][Web development]] - [[#emmet][Emmet]] - [[#prettier][Prettier]] - [[#typescript][TypeScript]] - [[#javascript][JavaScript]] - [[#jest][Jest]] - [[#vuejs][Vue.js]] - [[#mmm-mode-fix][mmm-mode fix]] - [[#svelte][Svelte]] - [[#scss][SCSS]] - [[#php][PHP]] - [[#latex][LaTeX]] - [[#auctex][AUCTeX]] - [[#bibtex][BibTeX]] - [[#import-sty][Import *.sty]] - [[#snippets][Snippets]] - [[#greek-letters][Greek letters]] - [[#english-letters][English letters]] - [[#math-symbols][Math symbols]] - [[#section-snippets][Section snippets]] - [[#other-markup-languages][Other markup languages]] - [[#markdown][Markdown]] - [[#plantuml][PlantUML]] - [[#languagetool][LanguageTool]] - [[#lisp][Lisp]] - [[#meta-lisp][Meta Lisp]] - [[#emacs-lisp][Emacs Lisp]] - [[#common-lisp][Common lisp]] - [[#clojure][Clojure]] - [[#hy][Hy]] - [[#scheme][Scheme]] - [[#clips][CLIPS]] - [[#python][Python]] - [[#pipenv][pipenv]] - [[#yapf][yapf]] - [[#isort][isort]] - [[#sphinx-doc][sphinx-doc]] - [[#pytest][pytest]] - [[#fix-comint-buffer-width][Fix comint buffer width]] - [[#code-cells][code-cells]] - [[#tensorboard][tensorboard]] - [[#java][Java]] - [[#go][Go]] - [[#net][.NET]] - [[#c][C#]] - [[#msbuild][MSBuild]] - [[#fish][fish]] - [[#sh][sh]] - [[#haskell][Haskell]] - [[#json][JSON]] - [[#yaml][YAML]] - [[#env][.env]] - [[#csv][CSV]] - [[#off-pdf][(OFF) PDF]] - [[#docker][Docker]] - [[#apps--misc][Apps & Misc]] - [[#managing-dotfiles][Managing dotfiles]] - [[#open-emacs-config][Open Emacs config]] - [[#open-magit-for-yadm][Open Magit for yadm]] - [[#open-a-dotfile][Open a dotfile]] - [[#internet--multimedia][Internet & Multimedia]] - [[#notmuch][Notmuch]] - [[#elfeed][Elfeed]] - [[#some-additions][Some additions]] - [[#youtube][YouTube]] - [[#emms][EMMS]] - [[#some-keybindings][Some keybindings]] - [[#fixes][Fixes]] - [[#eww][EWW]] - [[#erc][ERC]] - [[#google-translate][Google Translate]] - [[#discord-integration][Discord integration]] - [[#utilities][Utilities]] - [[#tldr-man-info][tldr, man, info]] - [[#docker][Docker]] - [[#progidy][Progidy]] - [[#proced][proced]] - [[#guix][Guix]] - [[#productivity][Productivity]] - [[#pomidor][Pomidor]] - [[#calendar][Calendar]] - [[#fun][Fun]] - [[#screenshotel][screenshot.el]] - [[#snow][Snow]] - [[#zone][Zone]] - [[#guix-settings][Guix settings]] :END: * 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. #+begin_src emacs-lisp (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) #+end_src ** 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: - [[https://github.com/raxod502/straight.el][straight.el repo]] #+begin_src emacs-lisp (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)) #+end_src ** 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: - [[https://github.com/jwiegley/use-package][use-package repo]] #+begin_src emacs-lisp (straight-use-package 'use-package) (eval-when-compile (require 'use-package)) ;; (setq use-package-verbose t) #+end_src ** 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 | #+begin_src emacs-lisp (setq gc-cons-threshold 80000000) (setq read-process-output-max (* 1024 1024)) #+end_src *** Run garbage collection when Emacs is unfocused Run GC when Emacs loses focus. Time will tell if that's a good idea. #+begin_src emacs-lisp (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)))) #+end_src *** Misc The following variable is true when my machine is not powerful enough for some resource-heavy packages. #+begin_src emacs-lisp (setq my/lowpower (string= (system-name) "azure")) #+end_src And the following is true if Emacs is meant to be used with TRAMP over slow ssh #+begin_src emacs-lisp (setq my/slow-ssh (string= (getenv "IS_TRAMP") "true")) #+end_src *** Native compilation Set number of jobs to 1 on low-power machines #+begin_src emacs-lisp (when my/lowpower (setq comp-async-jobs-number 1)) #+end_src ** Anaconda & environment [[https://www.anaconda.com/][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. Also, there are some strange things happening if vterm is launched with conda activated from Emacs, so I advice =conda-env-activate= to set an auxillary environment variable. References: - [[https://docs.anaconda.com/][Anaconda docs]] - [[https://github.com/necaris/conda.el][conda.el repo]] #+begin_src emacs-lisp (use-package conda :straight t :if (executable-find "conda") :config (setq conda-anaconda-home (string-replace "/bin/conda" "" (executable-find "conda"))) (setq conda-env-home-directory (expand-file-name "~/.conda/")) (setq conda-env-subdirectory "envs") (setenv "INIT_CONDA" "true") (advice-add 'conda-env-activate :after (lambda (&rest _) (setenv "EMACS_CONDA_ENV" conda-env-current-name))) (unless (getenv "CONDA_DEFAULT_ENV") (conda-env-activate "general"))) #+end_src 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: #+begin_src emacs-lisp (setenv "IS_EMACS" "true") #+end_src ** Custom file location By default, custom writes stuff to =init.el=, which is somewhat annoying. The following makes a separate file =custom.el= #+begin_src emacs-lisp (setq custom-file (concat user-emacs-directory "custom.el")) (load custom-file 'noerror) #+end_src ** No littering #+begin_src emacs-lisp (use-package no-littering :straight t) #+end_src * Global editing configuration ** General keybindings stuff *** general.el general.el provides a convenient interface to manage Emacs keybindings. References: - [[https://github.com/noctuid/general.el][general.el repo]] #+begin_src emacs-lisp (use-package general :straight t :config (general-evil-setup)) #+end_src *** 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: - [[https://github.com/justbur/emacs-which-key][which-key repo]] #+begin_src emacs-lisp (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) #+end_src ** 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: - [[https://github.com/emacs-evil/evil][evil repo]] - [[https://www.youtube.com/watch?v=JWD1Fpdd4Pc][(YouTube) Evil Mode: Or, How I Learned to Stop Worrying and Love Emacs]] *** evil Basic evil configuration. #+begin_src emacs-lisp (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) ) #+end_src *** Addons [[https://github.com/emacs-evil/evil-surround][evil-surround]] emulates one of my favorite vim plugins, surround.vim. Adds a lot of parentheses management options. #+begin_src emacs-lisp (use-package evil-surround :straight t :after evil :config (global-evil-surround-mode 1)) #+end_src [[https://github.com/linktohack/evil-commentary][evil-commentary]] emulates commentary.vim. #+begin_src emacs-lisp (use-package evil-commentary :straight t :after evil :config (evil-commentary-mode)) #+end_src [[https://github.com/blorbx/evil-quickscope][evil-quickscope]] emulates quickscope.vim. It highlights the important target characters for f, F, t, T keys. #+begin_src emacs-lisp (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))) #+end_src [[https://github.com/cofi/evil-numbers][evil-numbers]] allows incrementing and decrementing numbers at point. #+begin_src emacs-lisp (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)) #+end_src [[https://github.com/edkolev/evil-lion][evil-lion]] provides alignment operators. #+begin_src emacs-lisp (use-package evil-lion :straight t :config (setq evil-lion-left-align-key (kbd "g a")) (setq evil-lion-right-align-key (kbd "g A")) (evil-lion-mode)) #+end_src *** evil-collection [[https://github.com/emacs-evil/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 [[https://github.com/emacs-evil/evil-collection/tree/master/modes][modes]] folder. I don't enable the entire package, just the modes I need. #+begin_src emacs-lisp (use-package evil-collection :straight t :after evil :config (evil-collection-init '(eww proced emms pass calendar dired debug guix calc docker geiser pdf info elfeed edebug bookmark company vterm flycheck profiler cider explain-pause-mode notmuch custom xref eshell helpful compile comint magit prodigy))) #+end_src ** 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. #+begin_src emacs-lisp (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) #+end_src *** Home & end #+begin_src emacs-lisp (general-def :states '(normal insert visual) "" 'beginning-of-line "" 'end-of-line) #+end_src *** My leader Using the =SPC= key as a sort of leader key. #+begin_src emacs-lisp (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) #+end_src *** Universal argument Change the universal argument to =M-u= #+begin_src emacs-lisp (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) #+end_src *** Profiler The built-in profiler is a magnificent tool to troubleshoot performance issues. #+begin_src emacs-lisp (my-leader-def "Ps" 'profiler-start) (my-leader-def "Pe" 'profiler-stop) (my-leader-def "Pp" 'profiler-report) #+end_src *** Buffer switching Some keybindings I used in vim to switch buffers and can't let go of. #+begin_src emacs-lisp (general-define-key :keymaps 'override "C-" 'evil-window-right "C-" 'evil-window-left "C-" 'evil-window-up "C-" '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) #+end_src And winner-mode to keep the history of window states. #+begin_src emacs-lisp (winner-mode 1) (define-key evil-window-map (kbd "u") 'winner-undo) (define-key evil-window-map (kbd "U") 'winner-redo) #+end_src *** xref Some keybindings for xref, Emacs' built-in systems for managing identifiers. #+begin_src emacs-lisp (general-nmap "gD" 'xref-find-definitions-other-window "gr" 'xref-find-references) (my-leader-def "fx" 'xref-find-apropos) #+end_src *** Folding #+begin_src emacs-lisp (general-nmap :keymaps '(hs-minor-mode-map outline-minor-mode-map) "ze" 'hs-hide-level "TAB" 'evil-toggle-fold) #+end_src *** Zoom #+begin_src emacs-lisp (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) #+end_src ** Editing helpers *** Visual fill column mode #+begin_src emacs-lisp (use-package visual-fill-column :straight t :config (add-hook 'visual-fill-column-mode-hook (lambda () (setq visual-fill-column-center-text t)))) #+end_src *** 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: - [[https://github.com/Fuco1/smartparens][smartparens repo]] #+begin_src emacs-lisp (use-package smartparens :straight t) #+end_src *** 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: - [[https://github.com/Malabarba/aggressive-indent-mode][aggressive-indent-mode repo]] #+begin_src emacs-lisp (use-package aggressive-indent :commands (aggressive-indent-mode) :straight t) #+end_src *** Delete trailing whitespace Delete trailing whitespace on save, unless in particular modes where trailing whitespace is important, like Markdown. #+begin_src emacs-lisp (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)))) #+end_src *** Expand region #+begin_src emacs-lisp (use-package expand-region :straight t :commands (er/expand-region) :init (general-nmap "+" 'er/expand-region)) #+end_src ** Various settings *** Tabs Some default settings to manage tabs. #+begin_src emacs-lisp (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) #+end_src *** Scrolling config #+begin_src emacs-lisp (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) #+end_src *** Clipboard #+begin_src emacs-lisp (setq select-enable-clipboard t) (setq mouse-yank-at-point t) #+end_src *** Backups #+begin_src emacs-lisp (setq backup-inhibited t) (setq auto-save-default nil) #+end_src ** 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: - [[https://www.emacswiki.org/emacs/UndoTree][UndoTree on EmacsWiki]] #+begin_src emacs-lisp (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)) #+end_src ** Help [[https://github.com/Wilfred/helpful][helpful]] package improves the =*help*= buffer. #+begin_src emacs-lisp (use-package helpful :straight t :commands (helpful-callable helpful-variable helpful-key helpful-macro helpful-function helpful-command)) #+end_src 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. #+begin_src emacs-lisp (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 "" '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) #+end_src ** Ivy, counsel, swiper Minibuffer completion tools for Emacs. References: - [[https://oremacs.com/swiper/][repo]] - [[https://oremacs.com/swiper/][User Manual]] #+begin_src emacs-lisp (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) #+end_src *** ivy-rich [[https://github.com/Yevgnen/ivy-rich][ivy-rich]] provides more informative interface for ivy. #+begin_src emacs-lisp (use-package ivy-rich :straight t :after ivy :config (ivy-rich-mode 1) (setcdr (assq t ivy-format-functions-alist) #'ivy-format-function-line)) #+end_src *** prescient A package which enhances sorting & filtering of candidates. =ivy-prescient= adds integration with Ivy. References: - [[https://github.com/raxod502/prescient.el][prescient.el repo]] #+begin_src emacs-lisp :noweb yes (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 proced-filter-interactive proced-sort-interactive)) ;; Do not use prescient in find-file (ivy--alist-set 'ivy-sort-functions-alist #'read-file-name-internal #'ivy-sort-file-function-default)) #+end_src *** Keybindings #+begin_src emacs-lisp (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 "" 'ivy-call "M-RET" 'ivy-immediate-done [escape] 'minibuffer-keyboard-quit) #+end_src ** OFF (OFF) Helm Config for the Helm incremental completion framework. I switched to Ivy some time ago, but keep the configuration just in case. #+begin_src emacs-lisp :tangle no (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) #+end_src ** Treemacs [[https://github.com/Alexander-Miller/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. #+begin_src emacs-lisp (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) #+end_src *** Helper functions Function to open dired and vterm at given nodes. #+begin_src emacs-lisp (defun my/treemacs-open-dired () "Open dired at given treemacs node" (interactive) (let (path (treemacs--prop-at-point :path)) (dired path))) (defun my/treemacs-open-vterm () "Open vterm at given treemacs node" (interactive) (let ((default-directory (file-name-directory (treemacs--prop-at-point :path)))) (vterm))) (with-eval-after-load 'treemacs (general-define-key :keymaps 'treemacs-mode-map :states '(treemacs) "gd" 'my/treemacs-open-dired "gt" 'my/treemacs-open-vterm "`" 'my/treemacs-open-vterm)) #+end_src ** Projectile [[https://github.com/bbatsov/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. #+begin_src emacs-lisp (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) #+end_src ** Company A completion framework for Emacs. References: - [[http://company-mode.github.io/][company homepage]] - [[https://github.com/sebastiencs/company-box][company-box homepage]] #+begin_src emacs-lisp (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) #+end_src A company frontend with nice icons. #+begin_src emacs-lisp (use-package company-box :straight t :if (not my/lowpower) :after (company) :hook (company-mode . company-box-mode)) #+end_src ** Git & Magit [[https://magit.vc/][Magic]] is a git interface for Emacs. The closest non-Emacs alternative (sans actual clones) I know is [[https://github.com/jesseduffield/lazygit][lazygit]], which I used before Emacs. Also, [[https://github.com/emacsorphanage/git-gutter][git-gutter]] is plugin which shows git changes for each line (added/changed/deleted lines). #+begin_src emacs-lisp (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) #+end_src ** Editorconfig Editorconfig support for Emacs. References: - [[https://editorconfig.org/][Editorconfig reference]] #+begin_src emacs-lisp (use-package editorconfig :straight t :config (unless my/slow-ssh (editorconfig-mode 1)) (add-to-list 'editorconfig-indentation-alist '(emmet-mode emmet-indentation))) #+end_src ** OFF (OFF) Avy #+begin_src emacs-lisp :tangle no (use-package avy :straight t) (general-nmap "\\w" 'avy-goto-word-0-below) (general-nmap "\\b" 'avy-goto-word-0-above) #+end_src ** 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: - [[http://joaotavora.github.io/yasnippet/][yasnippet documentation]] #+begin_src emacs-lisp (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) #+end_src ** Time trackers A bunch of timetrackers I use. References: - [[https://wakatime.com][WakaTime]] - [[https://activitywatch.net/][ActivityWatch]] *** WakaTime Before I figure out how to package this for Guix: - Clone [[https://github.com/wakatime/wakatime-cli][the repo]] - Run ~go build~ - Copy the binary to the =~/bin= folder #+begin_src emacs-lisp (use-package wakatime-mode :straight t :config (advice-add 'wakatime-init :after (lambda () (setq wakatime-cli-path "/home/pavel/bin/wakatime"))) (global-wakatime-mode)) #+end_src *** ActivityWatch #+begin_src emacs-lisp (use-package request :straight t) (use-package activity-watch-mode :straight t :config (global-activity-watch-mode)) #+end_src * UI ** General UI & GUI Settings Disable GUI elements #+begin_src emacs-lisp (tool-bar-mode -1) (menu-bar-mode -1) (scroll-bar-mode -1) #+end_src Transparency #+begin_src emacs-lisp ;; (set-frame-parameter (selected-frame) 'alpha '(90 . 90)) ;; (add-to-list 'default-frame-alist '(alpha . (90 . 90))) #+end_src Prettify symbols #+begin_src emacs-lisp ;; (global-prettify-symbols-mode) #+end_src No start screen #+begin_src emacs-lisp (setq inhibit-startup-screen t) #+end_src Visual bell #+begin_src emacs-lisp (setq visible-bell 0) #+end_src y or n instead of yes or no #+begin_src emacs-lisp (defalias 'yes-or-no-p 'y-or-n-p) #+end_src Hide mouse cursor while typing #+begin_src emacs-lisp (setq make-pointer-invisible t) #+end_src 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. #+begin_src emacs-lisp (global-display-line-numbers-mode 1) (line-number-mode nil) (setq display-line-numbers-type 'visual) (column-number-mode) #+end_src Show pairs #+begin_src emacs-lisp (show-paren-mode 1) #+end_src Word wrap #+begin_src emacs-lisp (setq word-wrap 1) (global-visual-line-mode t) #+end_src Hightlight line #+begin_src emacs-lisp (global-hl-line-mode 1) #+end_src ** Theme & global stuff Dim inactive buffers. #+begin_src emacs-lisp (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)) #+end_src My colorscheme of choice. #+begin_src emacs-lisp (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)) #+end_src *** Custom theme A custom theme, dependent on Doom. I set all my custom variables there. A custom theme is necessary because if one calls =custom-set-faces= and =custom-set-variables= in code, whenever a variable is changed and saved in a customize buffer, data from all calls of these functions is saved as as well. Also, a hook allows me to change doom-theme more or less at will, although I do that only to switch to a light theme once in a blue moon. #+begin_src emacs-lisp (deftheme my-theme) (defun my/update-my-theme (&rest _) (custom-theme-set-faces 'my-theme `(tab-bar-tab ((t ( :background ,(doom-color 'bg) :foreground ,(doom-color 'yellow) :underline ,(doom-color 'yellow))))) `(org-block ((t (:background ,(color-darken-name (doom-color 'bg) 3))))) `(org-block-begin-line ((t ( :background ,(color-darken-name (doom-color 'bg) 3) :foreground ,(doom-color 'grey))))) `(auto-dim-other-buffers-face ((t (:background ,(color-darken-name (doom-color 'bg) 3))))) `(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))))) `(elfeed-search-tag-face ((t (:foreground ,(doom-color 'yellow)))))) (custom-theme-set-variables 'my-theme `(aweshell-invalid-command-color ,(doom-color 'red)) `(aweshell-valid-command-color ,(doom-color 'green))) (enable-theme 'my-theme)) (advice-add 'load-theme :after #'my/update-my-theme) (when (fboundp 'doom-color) (my/update-my-theme)) #+end_src *** 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: - [[https://nerdfonts.com][nerd fonts homepage]] #+begin_src emacs-lisp (set-frame-font "JetBrainsMono Nerd Font 10" nil t) #+end_src 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 #+begin_src emacs-lisp (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))))))) #+end_src ** 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 #+begin_src emacs-lisp (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) #+end_src *** My title Prepend tab name with the shortened projectile project title #+begin_src emacs-lisp (setq my/project-title-separators "[-_ ]") (setq my/project-names-override-alist '((".password-store" . "pass"))) (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) (or (cdr (assoc project-name my/project-names-override-alist)) (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) #+end_src ** Modeline A modeline from Doom Emacs. References: - [[https://github.com/seagle0128/doom-modeline][Doom Modeline]] #+begin_src emacs-lisp (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)) #+end_src ** Font stuff *** Emojis | Note | Type | |------+-----------------------------------------------------------| | TODO | Figure out how to display emojis without prettify symbols | #+begin_src emacs-lisp (use-package emojify :straight t :if (not my/lowpower) :hook (after-init . global-emojify-mode)) #+end_src *** Ligatures Ligature setup for the JetBrainsMono font. #+begin_src emacs-lisp (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)) #+end_src *** Icons #+begin_src emacs-lisp (use-package all-the-icons :straight t) #+end_src *** Highlight todo #+begin_src emacs-lisp (use-package hl-todo :hook (prog-mode . hl-todo-mode) :straight t) #+end_src ** Text highlight improvements Hightlight indent guides. #+begin_src emacs-lisp (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)) #+end_src Rainbow parentheses. #+begin_src emacs-lisp (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)))) ) #+end_src Highlight colors #+begin_src emacs-lisp (use-package rainbow-mode :commands (rainbow-mode) :straight t) #+end_src * Dired Dired is a built-in file manager. I use it as my primary file manager, hence the top level of config. ** Basic config & keybindings My config mostly follows ranger's and vifm's keybindings which I'm used to. #+begin_src emacs-lisp (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 "~" 'vterm (kbd "") 'dired-single-up-directory (kbd "") '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-" '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) #+end_src ** Addons [[https://www.emacswiki.org/emacs/DiredPlus][Dired+]] provides a lot of extensions for dired functionality. #+begin_src emacs-lisp :noweb yes (use-package dired+ :straight t :init (setq diredp-hide-details-initially-flag nil) :config <>) #+end_src Reuse the current dired buffer instead of spamming new ones. #+begin_src emacs-lisp (use-package dired-single :after dired :straight t) #+end_src Display icons for files. | Note | Type | |-----------+-----------------------------------------| | *ACHTUNG* | This plugin is slow as hell with TRAMP | #+begin_src emacs-lisp (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) (advice-add 'dired-kill-subdir :around #'all-the-icons-dired--refresh-advice)) #+end_src Provides stuff like =dired-open-xdg= #+begin_src emacs-lisp (use-package dired-open :straight t :commands (dired-open-xdg)) #+end_src vifm-like filter #+begin_src emacs-lisp (use-package dired-narrow :straight t :commands (dired-narrow) :config (general-define-key :keymaps 'dired-narrow-map [escape] 'keyboard-quit)) #+end_src *** dired+ on Emacs 28 It looks like dired+ is not quite compatible with Emacs 28. So I override certain functions for now. #+begin_src emacs-lisp :tangle no :noweb-ref diredp-fixes (defun dired-do-delete (&optional arg) "Delete all marked (or next ARG) files. `dired-recursive-deletes' controls whether deletion of non-empty directories is allowed." ;; This is more consistent with the file marking feature than ;; dired-do-flagged-delete. (interactive "P") (let (markers) (dired-internal-do-deletions (nreverse ;; this may move point if ARG is an integer (dired-map-over-marks (cons (dired-get-filename) (let ((m (point-marker))) (push m markers) m)) arg)) arg t) (dolist (m markers) (set-marker m nil)))) #+end_src ** Subdirectories Subdirectories are one of the interesting features of Dired. It allows displaying multiple folders on the same window. I add my own keybindings and some extra functionality. #+begin_src emacs-lisp (defun my/dired-open-this-subdir () (interactive) (dired (dired-current-directory))) (defun my/dired-kill-all-subdirs () (interactive) (let ((dir dired-directory)) (kill-buffer (current-buffer)) (dired dir))) (with-eval-after-load 'dired (evil-collection-define-key 'normal 'dired-mode-map "s" nil "ss" 'dired-maybe-insert-subdir "sl" 'dired-maybe-insert-subdir "sq" 'dired-kill-subdir "sk" 'dired-prev-subdir "sj" 'dired-next-subdir "sS" 'my/dired-open-this-subdir "sQ" 'my/dired-kill-all-subdirs (kbd "TAB") 'dired-hide-subdir)) #+end_src ** 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: #+begin_src emacs-lisp (setq tramp-verbose 1) #+end_src 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: #+begin_src emacs-lisp (setq remote-file-name-inhibit-cache nil) (setq vc-ignore-dir-regexp (format "\\(%s\\)\\|\\(%s\\)" vc-ignore-dir-regexp tramp-file-name-regexp)) #+end_src Also, here is a hack to make TRAMP find =ls= on Guix: #+begin_src emacs-lisp (with-eval-after-load 'tramp (setq tramp-remote-path (append tramp-remote-path '(tramp-own-remote-path)))) #+end_src ** 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 [[file:.emacs.d/dired-bookmarks.el][dired-bookmarks.el]] file, which looks like this: #+begin_example emacs-lisp :tangle no (setq my/dired-bookmarks '(("sudo" . "/sudo::/"))) #+end_example The file itself is encrypted with yadm. #+begin_src emacs-lisp (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))))) #+end_src * Shells ** vterm My terminal emulator of choice. References: - [[https://github.com/akermu/emacs-libvterm][emacs-libvterm repo]] *** Configuration I use the package from the Guix repository to avoid building libvterm. #+begin_src emacs-lisp (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-" 'evil-window-right "C-" 'evil-window-left "C-" 'evil-window-up "C-" 'evil-window-down "M-" 'vterm-send-left "M-" 'vterm-send-right "M-" 'vterm-send-up "M-" '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)) #+end_src *** Subterminal Open a terminal in the lower third of the frame with the =`= key. #+begin_src emacs-lisp (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)) #+end_src *** Dired integration A function to get pwd for vterm. Couldn't find a built-in function for some reason, but this seems to be working fine: #+begin_src emacs-lisp (defun my/vterm-get-pwd () (if vterm--process (file-truename (format "/proc/%d/cwd" (process-id vterm--process))) default-directory)) #+end_src Now we can open dired for vterm pwd: #+begin_src emacs-lisp (defun my/vterm-dired-other-window () "Open dired in vterm pwd in other window" (interactive) (dired-other-window (my/vterm-get-pwd))) (defun my/vterm-dired-replace () "Replace vterm with dired" (interactive) (let ((pwd (my/vterm-get-pwd))) (kill-process vterm--process) (dired pwd))) #+end_src The second function is particularly handy because that way I can alternate between vterm and dired. Keybindings: #+begin_src emacs-lisp (with-eval-after-load 'vterm (general-define-key :keymap 'vterm-mode-map :states '(normal) "gd" #'my/vterm-dired-other-window "gD" #'my/vterm-dired-replace)) #+end_src ** Eshell A shell written in Emacs lisp. I don't use it as of now, but keep the config just in case. #+begin_src emacs-lisp (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 "") '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) (when my/slow-ssh (add-hook 'eshell-mode-hook (lambda () (setq-local company-idle-delay 1000)))) (setq eshell-banner-message "")) (use-package aweshell :straight (:repo "manateelazycat/aweshell" :host github) :after eshell :config (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)) #+end_src * Org Mode The best feature of Emacs. Just after every other best feature of Emacs, probably. References: - [[https://orgmode.org/][Org Mode homepage]] - [[https://orgmode.org/manual/][Manual]] ** Installation & basic settings Use the built-in org mode. #+begin_src emacs-lisp :noweb yes (use-package org :straight t :defer t :config (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))) <> <> <> <> <>) #+end_src *** Encryption #+begin_src emacs-lisp :noweb-ref org-crypt-setup (require 'org-crypt) (org-crypt-use-before-save-magic) (setq org-tags-exclude-from-inheritance (quote ("crypt"))) (setq org-crypt-key "C1EC867E478472439CC82410DE004F32AFA00205") #+end_src *** org-contrib =org-contrib= is a package with various additions to Org. I use the following: - =ox-extra= - extensions for org export - =ol-notmuch= - integration with notmuch #+begin_src emacs-lisp (use-package org-contrib :straight (org-contrib :type git :host nil :repo "https://git.sr.ht/~bzg/org-contrib" :build t) :after (org) :config (require 'ox-extra) (require 'ol-notmuch) (ox-extras-activate '(latex-header-blocks ignore-headlines))) #+end_src ** Integration with evil #+begin_src emacs-lisp (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)) #+end_src ** Literate programing *** Python & Jupyter Use jupyter kernels for Org Mode. References: - [[https://github.com/nnicandro/emacs-jupyter][emacs-jupyter repo]] - [[https://github.com/jkitchin/scimax/blob/master/scimax.org][SCIMAX manual]] #+begin_src emacs-lisp :noweb-ref org-lang-setup (use-package jupyter :straight t :init (my-leader-def "ar" 'jupyter-run-repl)) #+end_src 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). #+begin_src emacs-lisp (defun my/jupyter-refresh-kernelspecs () "Refresh Jupyter kernelspecs" (interactive) (jupyter-available-kernelspecs t)) #+end_src 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. #+begin_src emacs-lisp (defun my/jupyter-refesh-langs () "Refresh Jupyter languages" (interactive) (org-babel-jupyter-aliases-from-kernelspecs t)) #+end_src *** Hy #+begin_src emacs-lisp :noweb-ref org-lang-setup (use-package ob-hy :straight t) #+end_src *** View HTML in browser Open HTML in the ~begin_export~ block with xdg-open. #+begin_src emacs-lisp (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)))))) #+end_src *** Setup Enable languages #+begin_src emacs-lisp :tangle no :noweb-ref org-lang-setup (org-babel-do-load-languages 'org-babel-load-languages '((emacs-lisp . t) (python . t) (sql . t) ;; (typescript .t) (hy . t) (shell . t) (octave . t) (jupyter . t))) (add-hook 'org-babel-after-execute-hook 'org-redisplay-inline-images) #+end_src Use Jupyter block instead of built-in Python. #+begin_src emacs-lisp :tangle no :noweb-ref org-lang-setup (org-babel-jupyter-override-src-block "python") (org-babel-jupyter-override-src-block "hy") #+end_src Turn of some minor modes in source blocks. #+begin_src emacs-lisp :tangle no :noweb-ref org-lang-setup (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))) #+end_src Async code blocks evaluations. Jupyter blocks have a built-in async, so they are set as ignored. #+begin_src emacs-lisp (use-package ob-async :straight t :after (org) :config (setq ob-async-no-async-languages-alist '("python" "hy" "jupyter-python" "jupyter-octave"))) #+end_src *** 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: #+begin_example #+PROPERTY: header-args:python :session #+end_example ~my/jupyter-connect-repl~ opens a =emacs-jupyter= REPL, connected to an active kernel. ~my/jupyter-qtconsole~ runs a standalone Jupyter QtConsole. Requirements: =ss= #+begin_src emacs-lisp (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)))) #+end_src 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. #+begin_src emacs-lisp (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)))))) #+end_src *** 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. #+begin_src emacs-lisp (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) #+end_src *** 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. #+begin_src emacs-lisp (defun my/org-strip-results (data) (replace-regexp-in-string ":\\(RESULTS\\|END\\):\n" "" data)) #+end_src 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. #+begin_src emacs-lisp (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))))) #+end_src To use, add the following snippet to the org file: #+begin_example #+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 #+end_example Example usage: #+begin_example :post out_wrap(name="fig:chart", caption="График", data=*this*) #+end_example ** Productivity & Knowledge management My on-going effort to get a productivity setup in Org. Some inspiration: - [[https://www.labri.fr/perso/nrougier/GTD/index.html][Nicolas P. Rougier. Get Things Done with Emacs]] - [[https://blog.jethro.dev/posts/org_mode_workflow_preview/][Jetro Kuan. Org-mode Workflow]] - [[https://www.alexeyshmalko.com/how-i-note/][Alexey Shmalko: How I note]] - [[https://rgoswami.me/posts/org-note-workflow/][Rohit Goswami: An Orgmode Note Workflow]] Used files #+begin_src emacs-lisp :tangle no :noweb-ref org-productivity-setup (setq org-directory (expand-file-name "~/Documents/org-mode")) (setq org-agenda-files '("inbox.org" "projects.org" "work.org")) ;; (setq org-default-notes-file (concat org-directory "/notes.org")) #+end_src Hotkeys #+begin_src emacs-lisp (my-leader-def "oc" 'org-capture) (my-leader-def "oa" 'org-agenda) #+end_src Refile targets #+begin_src emacs-lisp (setq org-refile-targets '(("projects.org" :maxlevel . 2) ("work.org" :maxlevel . 2))) (setq org-refile-use-outline-path 'file) (setq org-outline-path-complete-in-steps nil) #+end_src *** Capture templates & various settings Settings for Org capture mode. The goal here is to have a non-disruptive process to capture various ideas. #+begin_src emacs-lisp :tangle no :noweb-ref org-productivity-setup (setq org-capture-templates `(("i" "Inbox" entry (file "inbox.org") ,(concat "* TODO %?\n" "/Entered on/ %U")) ("e" "email" entry (file "inbox.org") ,(concat "* TODO %:from %:subject \n" "/Entered on/ %U\n" "/Received on/ %:date-timestamp-inactive\n" "%a\n")) ("f" "elfeed" entry (file "inbox.org") ,(concat "* TODO %:elfeed-entry-title\n" "/Entered on/ %U\n" "%a\n")))) #+end_src Effort estimation #+begin_src emacs-lisp :tangle no :noweb-ref org-productivity-setup (add-to-list 'org-global-properties '("Effort_ALL" . "0 0:05 0:10 0:15 0:30 0:45 1:00 2:00 4:00")) #+end_src Log DONE time #+begin_src emacs-lisp :tangle no :noweb-ref org-productivity-setup (setq org-log-done 'time) #+end_src *** Custom agendas #+begin_src emacs-lisp (defun my/org-scheduled-get-time () (let ((scheduled (org-get-scheduled-time (point)))) (if scheduled (format-time-string "%Y-%m-%d" scheduled) ""))) (setq org-agenda-custom-commands `(("p" "My outline" ((agenda "") (todo "NEXT" ((org-agenda-prefix-format " %i %-12:c [%e] ") (org-agenda-overriding-header "Next tasks"))) (tags-todo "inbox" ((org-agenda-overriding-header "Inbox") (org-agenda-prefix-format " %i %-12:c") (org-agenda-hide-tags-regexp "."))) (tags-todo "+waitlist+SCHEDULED<=\"<+14d>\"" ((org-agenda-overriding-header "Waitlist") (org-agenda-hide-tags-regexp "waitlist") (org-agenda-prefix-format " %i %-12:c %-12(my/org-scheduled-get-time)"))))) ("tp" "Personal tasks" ((tags-todo "personal" ((org-agenda-prefix-format " %i %-12:c [%e] "))))))) #+end_src *** Org Journal [[https://github.com/bastibe/org-journal][org-journal]] is a plugin for maintaining journal in org mode. I want to have its entries separate from my potential base. #+begin_src emacs-lisp (use-package org-journal :straight t :after org :config (setq org-journal-dir (concat org-directory "/journal")) (setq org-journal-file-type 'weekly) (setq org-journal-file-format "%Y-%m-%d.org") (setq org-journal-date-format "%A, %Y-%m-%d") (setq org-journal-enable-encryption t)) (my-leader-def :infix "oj" "j" 'org-journal-new-entry "o" 'org-journal-open-current-journal-file "s" 'org-journal-search) #+end_src *** Org Roam [[https://github.com/org-roam/org-roam][org-roam]] is a plain-text knowledge database. | Guix dependency | |-----------------------| | emacs-emacsql-sqlite3 | | graphviz | References: - [[https://github.com/org-roam/org-roam/wiki/Hitchhiker%27s-Rough-Guide-to-Org-roam-V2][Hitchhiker's Rough Guide to Org roam V2]] #+begin_src emacs-lisp (use-package emacsql-sqlite :defer t :straight (:type built-in)) (use-package org-roam :straight t :after org :init (setq org-roam-directory (concat org-directory "/roam")) (setq org-roam-file-extensions '("org")) (setq org-roam-v2-ack t) (setq orb-insert-interface 'ivy-bibtex) :config (org-roam-setup) (setq org-roam-capture-templates `(("d" "default" plain "%?" :if-new (file+head "%<%Y%m%d%H%M%S>-${slug}.org" "#+title: ${title}\n") :unnarrowed t))) (require 'org-roam-protocol)) (my-leader-def :infix "or" "i" 'org-roam-node-insert "r" 'org-roam-node-find "g" 'org-roam-graph "c" 'org-roam-capture "b" 'org-roam-buffer-toggle) (with-eval-after-load 'org (my-leader-def :keymap 'org-mode-map :infix "or" "t" 'org-roam-tag-add "T" 'org-toam-tag-remove) (general-define-key :keymap 'org-mode-map "C-c i" 'org-id-get-create "C-c l o" 'org-roam-node-insert)) #+end_src **** org-roam-protocol Open links such as =org-protocol://= from browser. Run =M-x server-start= for org-protocol to work. #+begin_src conf :tangle ~/.local/share/applications/org-protocol.desktop [Desktop Entry] Name=Org-Protocol Exec=emacsclient %u Icon=emacs-icon Type=Application Terminal=false MimeType=x-scheme-handler/org-protocol #+end_src Don't forget to run the following after setup: #+begin_src bash :tangle no xdg-mime default org-protocol.desktop x-scheme-handler/org-protocol #+end_src *** org-ref | Type | Description | |------+---------------------------------| | TODO | Figure out how not to load Helm | [[https://github.com/jkitchin/org-ref][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 [[file:.emacs.d/straight/repos/org-ref/org-ref.el][org-ref.el]] file. I haven't found a way to do this without modifying the package source yet. #+begin_src emacs-lisp (use-package org-ref :straight (:files (:defaults (:exclude "*helm*"))) :init (setq org-ref-completion-library 'org-ref-ivy-cite) (setq bibtex-dialect 'biblatex) (setq org-ref-default-bibliography '("~/Documents/org-mode/bibliography.bib")) (setq reftex-default-bibliography org-ref-default-bibliography) (setq bibtex-completion-bibliography org-ref-default-bibliography) :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 "C-c l h" 'org-ref-cite-hydra/body) (general-define-key :keymaps 'bibtex-mode-map "M-RET" 'org-ref-bibtex-hydra/body) (add-to-list 'orhc-candidate-formats '("online" . " |${=key=}| ${title} ${url}"))) #+end_src *** org-roam-bibtex Integration with bibtex and org-ref. There are some problems with org roam v2, so I disabled it as of now. I will probably use another way of managing bibliography notes anyway. #+begin_src emacs-lisp (use-package org-roam-bibtex :straight (:host github :repo "org-roam/org-roam-bibtex") :after (org-roam org-ref) :disabled :config (org-roam-bibtex-mode)) #+end_src ** UI *** OFF (OFF) Instant equations preview Instant math previews for org mode. References: - [[https://github.com/yangsheng6810/org-latex-impatient][org-latex-impatient repo]] #+begin_src emacs-lisp (use-package org-latex-impatient :straight (:repo "yangsheng6810/org-latex-impatient" :branch "master" :host github) :hook (org-mode . org-latex-impatient-mode) :disabled :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")) #+end_src *** LaTeX fragments A function to enable LaTeX native highlighting. Not setting as default, because it loads LaTeX stuff. #+begin_src emacs-lisp (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))) (sp-local-pair 'org-mode "$" "$") (sp--remove-local-pair "'")) #+end_src Call the function before opening an org file or reopen a buffer after calling the function. Scale latex fragments preview. #+begin_src emacs-lisp :noweb-ref org-ui-setup :tangle no (setq my/org-latex-scale 1.75) (setq org-format-latex-options (plist-put org-format-latex-options :scale my/org-latex-scale)) #+end_src 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 #+begin_src emacs-lisp :noweb-ref org-ui-setup :tangle no (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)) #+end_src *** Better headers #+begin_src emacs-lisp (use-package org-superstar :straight t :hook (org-mode . org-superstar-mode)) #+end_src *** Org Agenda Icons Categories are broad labels to group agenda items. #+begin_src emacs-lisp :noweb-ref org-ui-setup :tangle no (if (not my/lowpower) (setq org-agenda-category-icon-alist `(("inbox" ,(list (all-the-icons-faicon "inbox")) nil nil :ascent center) ("work" ,(list (all-the-icons-faicon "cog")) nil nil :ascent center) ("education" ,(list (all-the-icons-material "build")) nil nil :ascent center) ("personal" ,(list (all-the-icons-faicon "music")) nil nil :ascent center) ("misc" ,(list (all-the-icons-material "archive")) nil nil :ascent center) ;; ("lesson" ,(list (all-the-icons-faicon "book")) nil nil :ascent center) ;; ("meeting" ,(list (all-the-icons-material "chat")) nil nil :ascent center) ;; ("event" ,(list (all-the-icons-octicon "clock")) nil nil :ascent center) ("." ,(list (all-the-icons-faicon "circle-o")) nil nil :ascent center)))) #+end_src ** Export *** Hugo #+begin_src emacs-lisp (use-package ox-hugo :straight t :after ox) #+end_src *** Jupyter Notebook #+begin_src emacs-lisp (use-package ox-ipynb :straight (:host github :repo "jkitchin/ox-ipynb") :after ox) #+end_src *** Html export #+begin_src emacs-lisp (use-package htmlize :straight t :after ox :config (setq org-html-htmlize-output-type 'css)) #+end_src *** LaTeX Add a custom LaTeX template without default packages. Packages are indented to be imported with function from [[Import *.sty]]. #+begin_src emacs-lisp (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)) #+end_src ** Keybindings & stuff #+begin_src emacs-lisp :tangle no :noweb-ref org-keys-setup (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-" 'org-next-visible-heading "S-" '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) #+end_src *** Copy a link #+begin_src emacs-lisp :noweb-ref org-keys-setup (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) #+end_src ** Presentations Doing presentations with [[https://github.com/rlister/org-present][org-present]]. #+begin_src emacs-lisp (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 "" 'my/present-next-with-latex "" '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)))) #+end_src ** TOC Make a TOC inside the org file. References: - [[https://github.com/alphapapa/org-make-toc][alphapapa/org-make-toc]] #+begin_src emacs-lisp (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) #+end_src ** 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. - If =CATEGORY= is passed, a column with name =[C|c]ategory= will be used to filter results. That way one file can be used to produce multiple manifests. - If =CATEGORY= is not passed, entries with non-empty category will be filtered out - If there is a =[D|d]isabled= column, entries which have non-empty value in this column will be filtered out. #+begin_src emacs-lisp :noweb-ref guix-tables (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)))) (disabled-name-index (cl-position nil (mapcar #'substring-no-properties (nth 0 table)) :test (lambda (_ elem) (string-match-p ".*[D|d]isabled.*" elem))))) (when dep-name-index (dolist (elem (cdr table)) (when (and ;; Category (or ;; Category not set and not present in the table (and (or (not category) (string-empty-p category)) (not category-name-index)) ;; Category is set and present in the table (and category-name-index (not (string-empty-p category)) (string-match-p category (nth category-name-index elem)))) ;; Not disabled (or (not disabled-name-index) (string-empty-p (nth disabled-name-index elem)))) (add-to-list 'dependencies (substring-no-properties (nth dep-name-index elem))))))))) dependencies)) #+end_src Now, join dependencies list to make it compatible with Scheme: #+begin_src emacs-lisp :noweb-ref guix-tables (defun my/format-guix-dependencies (&optional category) (mapconcat (lambda (e) (concat "\"" e "\"")) (my/extract-guix-dependencies category) "\n")) #+end_src *** Noweb evaluations Turn off eval confirmations for configuration files. #+begin_src emacs-lisp (setq my/org-config-files '("/home/pavel/Emacs.org" "/home/pavel/Desktop.org" "/home/pavel/Console.org" "/home/pavel/Guix.org" "/home/pavel/Mail.org")) (add-hook 'org-mode-hook (lambda () (when (member (buffer-file-name) my/org-config-files) (setq-local org-confirm-babel-evaluate nil)))) #+end_src *** yadm hook A script to run tangle from CLI. #+begin_src emacs-lisp :tangle ~/.config/yadm/hooks/run-tangle.el :noweb yes (require 'org) (org-babel-do-load-languages 'org-babel-load-languages '((emacs-lisp . t) (shell . t))) ;; Do not ask to confirm evaluations (setq org-confirm-babel-evaluate nil) <> ;; A few dummy modes to avoid being prompted for comment systax (define-derived-mode fish-mode prog-mode "Fish" (setq-local comment-start "# ") (setq-local comment-start-skip "#+[\t ]*")) (define-derived-mode yaml-mode text-mode "YAML" (setq-local comment-start "# ") (setq-local comment-start-skip "#+ *")) (mapcar #'org-babel-tangle-file '("/home/pavel/Emacs.org" "/home/pavel/Desktop.org" "/home/pavel/Console.org" "/home/pavel/Guix.org" "/home/pavel/Mail.org")) #+end_src To launch from CLI, run: #+begin_src bash :tangle no emacs -Q --batch -l run-tangle.el #+end_src I have added this line to yadm's =post_alt= hook, so tangle is ran after =yadm alt= * OFF (OFF) EAF [[https://github.com/manateelazycat/emacs-application-framework][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 #+begin_src bash :tangle no pip install qtconsole markdown qrcode[pil] PyQt5 PyQtWebEngine #+end_src ** Config #+begin_src emacs-lisp :tangle no (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")) #+end_src * Programming ** General setup *** LSP LSP-mode provides an IDE-like experience for Emacs - real-time diagnostic, code actions, intelligent autocompletion, etc. References: - [[https://emacs-lsp.github.io/lsp-mode/][lsp-mode homepage]] **** Setup #+begin_src emacs-lisp (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)) #+end_src **** 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. #+begin_src emacs-lisp ;; (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) #+end_src **** Keybindings #+begin_src emacs-lisp (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) #+end_src *** Flycheck A syntax checking extension for Emacs. Integrates with LSP-mode, but can also use various standalone checkers. References: - [[https://www.flycheck.org/en/latest/][Flycheck homepage]] #+begin_src emacs-lisp (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)))) #+end_src *** 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: - [[https://tree-sitter.github.io/tree-sitter/][Tree-sitter library]] - [[https://ubolonton.github.io/emacs-tree-sitter/][Emacs Tree-sitter]] #+begin_src emacs-lisp (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) #+end_src *** 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: - [[https://emacs-lsp.github.io/dap-mode/][dap-mode homepage]] #+begin_src emacs-lisp :tangle no (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)))) #+end_src *** 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: - [[https://www.tabnine.com/][TabNine Homepage]] #+begin_src emacs-lisp :tangle no (use-package company-tabnine :straight t :if (not my/lowpower) :after company :config (add-to-list 'company-backends #'company-tabnine)) #+end_src *** OFF (OFF) Code Compass A set of code analysing tools. References: - [[https://github.com/ag91/code-compass][code-compass repo]] **** Dependencies #+begin_src emacs-lisp :tangle no (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) #+end_src **** Plugin #+begin_src emacs-lisp :tangle no (use-package code-compass :straight ( :repo "ag91/code-compass" :files ("code-compass.el") :branch "main" )) #+end_src *** CHECK (OFF) Format-all #+begin_src emacs-lisp :tangle no (use-package format-all :straight t) #+end_src *** General additional config Make smartparens behave the way I like for C-like languages. #+begin_src emacs-lisp (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")))) #+end_src Override flycheck checker with eslint. #+begin_src emacs-lisp (defun my/set-flycheck-eslint() "Override flycheck checker with eslint." (setq-local lsp-diagnostic-package :none) (setq-local flycheck-checker 'javascript-eslint)) #+end_src ** Web development Configs for various web development technologies I'm using. *** Emmet [[https://emmet.io/][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
| My bit of config here: - makes Emmet activate only in certain mmm-mode submodes. - makes =TAB= the only key I have to use #+begin_src emacs-lisp (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 "" 'emmet-prev-edit-point)) #+end_src *** Prettier #+begin_src emacs-lisp (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)) #+end_src *** TypeScript #+begin_src emacs-lisp (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)) #+end_src *** JavaScript #+begin_src emacs-lisp (add-hook 'js-mode-hook #'smartparens-mode) (add-hook 'js-mode-hook #'hs-minor-mode) (my/set-smartparens-indent 'js-mode) #+end_src *** Jest #+begin_src emacs-lisp (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)) #+end_src *** Vue.js #+begin_src emacs-lisp :noweb yes (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))) #+end_src **** mmm-mode fix References: - [[https://github.com/purcell/mmm-mode/issues/112][mmm-mode issue]] #+begin_src emacs-lisp :noweb-ref override-mmm-mode-func :tangle no (defun mmm-syntax-propertize-function (start stop) (let ((saved-mode mmm-current-submode) (saved-ovl mmm-current-overlay)) (mmm-save-changed-local-variables mmm-current-submode mmm-current-overlay) (unwind-protect (mapc (lambda (elt) (let* ((mode (car elt)) (func (get mode 'mmm-syntax-propertize-function)) (beg (cadr elt)) (end (nth 2 elt)) (ovl (nth 3 elt)) syntax-ppss-cache syntax-ppss-last) (goto-char beg) (mmm-set-current-pair mode ovl) (mmm-set-local-variables mode mmm-current-overlay) (save-restriction (if mmm-current-overlay (narrow-to-region (overlay-start mmm-current-overlay) (overlay-end mmm-current-overlay)) (narrow-to-region beg end)) (cond (func (funcall func beg end)) (font-lock-syntactic-keywords (let ((syntax-propertize-function nil)) (font-lock-fontify-syntactic-keywords-region beg end)))) (run-hook-with-args 'mmm-after-syntax-propertize-functions mmm-current-overlay mode beg end)))) (mmm-regions-in start stop)) (mmm-set-current-pair saved-mode saved-ovl) (mmm-set-local-variables (or saved-mode mmm-primary-mode) saved-ovl)))) #+end_src *** Svelte #+begin_src emacs-lisp (use-package svelte-mode :straight t :mode "\\.svelte\\'" :config (add-hook 'svelte-mode-hook 'my/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)) #+end_src *** SCSS #+begin_src emacs-lisp (add-hook 'scss-mode-hook #'smartparens-mode) (add-hook 'scss-mode-hook #'hs-minor-mode) (my/set-smartparens-indent 'scss-mode) #+end_src *** PHP #+begin_src emacs-lisp (use-package php-mode :straight t :mode "\\.php\\'") #+end_src ** LaTeX *** AUCTeX The best LaTeX editing environment I've found so far. References: - [[https://www.gnu.org/software/auctex/][AUCTeX homepage]] #+begin_src emacs-lisp :noweb yes (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) (when (boundp 'tex--prettify-symbols-alist) (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) <> <> <> <>) #+end_src *** BibTeX #+begin_src emacs-lisp (use-package ivy-bibtex :commands (ivy-bibtex) :straight t :init (my-leader-def "fB" 'ivy-bibtex)) (add-hook 'bibtex-mode 'smartparens-mode) #+end_src *** Import *.sty A function to import =.sty= files to the LaTeX document. #+begin_src emacs-lisp (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))))) #+end_src *** 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. #+begin_src emacs-lisp :noweb-ref init-greek-latex-snippets (setq my/greek-alphabet '(("a" . "\\alpha") ("b" . "\\beta" ) ("g" . "\\gamma") ("d" . "\\delta") ("e" . "\\epsilon") ("z" . "\\zeta") ("h" . "\\eta") ("o" . "\\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)) #+end_src **** English letters #+begin_src emacs-lisp :noweb-ref init-english-latex-snippets (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)) #+end_src **** Math symbols #+begin_src emacs-lisp :noweb-ref init-math-latex-snippets (setq my/latex-math-symbols '(("x" . "\\times") ("." . "\\cdot") ("v" . "\\forall") ("s" . "\\sum_{$1}^{$2}$0") ("p" . "\\prod_{$1}^{$2}$0") ("d" . "\\partial") ("e" . "\\exists") ("i" . "\\int_{$1}^{$2}$0") ("c" . "\\cap") ("u" . "\\cup") ("0" . "\\emptyset") ("^" . "\\widehat{$1}$0") ("_" . "\\overline{$1}$0") ("~" . "\\sim") ("|" . "\\mid") ("_|" . "\\perp"))) (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)) #+end_src **** Section snippets Section snippets. The code turned out to be more complicated than just writing the snippets by hand. #+begin_src emacs-lisp :noweb-ref init-section-latex-snippets (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) #+end_src ** Other markup languages *** Markdown #+begin_src emacs-lisp (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-" 'markdown-promote "M-" 'markdown-demote)) ;; (use-package livedown ;; :straight (:host github :repo "shime/emacs-livedown") ;; :commands livedown-preview ;; :config ;; (setq livedown-browser "qutebrowser")) #+end_src *** PlantUML #+begin_src emacs-lisp (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) #+end_src *** 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: - [[https://languagetool.org/][LanguageTool homepage]] - [[https://dev.languagetool.org/http-server][LanguageTool http server]] - [[https://github.com/mhayashi1120/Emacs-langtool][LanguageTool for Emacs repo]] #+begin_src emacs-lisp (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) #+end_src ** Lisp These are your father's parentheses. Elegant weapons for a more... civilized age. *** Meta Lisp Some packages for editing various Lisps. #+begin_src emacs-lisp (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)) #+end_src *** Emacs Lisp #+begin_src 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) #+end_src *** Common lisp #+begin_src emacs-lisp (add-hook 'lisp-mode-hook #'aggressive-indent-mode) ;; (add-hook 'emacs-lisp-mode-hook #'smartparens-strict-mode) (add-hook 'lisp-mode-hook #'lispy-mode) #+end_src *** Clojure #+begin_src emacs-lisp (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) #+end_src *** Hy Python requirements: - =hy= - =jedhy= #+begin_src emacs-lisp (use-package hy-mode :straight t :mode "\\.hy\\'" :config (add-hook 'hy-mode-hook #'lispy-mode) (add-hook 'hy-mode-hook #'aggressive-indent-mode)) #+end_src *** Scheme #+begin_src emacs-lisp (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) #+end_src *** CLIPS An honorary Lisp #+begin_src emacs-lisp (use-package clips-mode :straight t :mode "\\.cl\\'" :config (add-hook 'clips-mode 'lispy-mode)) #+end_src ** Python Use [[https://github.com/Microsoft/python-language-server][Microsoft Language Server for Python]]. For some reason it doesn't use pipenv python executable, so here is a small workaround. #+begin_src emacs-lisp (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-pyright :straight t :defer t :if (not my/slow-ssh) :hook (python-mode . (lambda () (require 'lsp-pyright) (setq-local lsp-pyright-python-executable-cmd (my/get-pipenv-python)) (lsp)))) (add-hook 'python-mode-hook #'smartparens-mode) (add-hook 'python-mode-hook #'hs-minor-mode) #+end_src *** pipenv [[https://github.com/pypa/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=). #+begin_src emacs-lisp (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)) #+end_src *** yapf [[https://github.com/google/yapf][yapf]] is a formatter for Python files. | Guix dependency | |-----------------| | python-yapf | References: - [[https://github.com/google/yapf][yapf repo]] - [[https://github.com/JorisE/yapfify][yapfify.el repo]] #+begin_src emacs-lisp (use-package yapfify :straight (:repo "JorisE/yapfify" :host github) :commands (yapfify-region yapfify-buffer yapfify-region-or-buffer yapf-mode)) #+end_src Global config: #+begin_src conf-windows :tangle .config/yapf/style :comments link [style] based_on_style = facebook column_limit = 80 #+end_src *** isort [[https://github.com/PyCQA/isort][isort]] is a Python package to sort Python imports. | Guix dependency | |-----------------| | python-isort | References: - [[https://pycqa.github.io/isort/][isort docs]] - [[https://github.com/paetzke/py-isort.el][py-isort.el repo]] #+begin_src emacs-lisp (use-package py-isort :straight t :commands (py-isort-buffer py-isort-region)) #+end_src The following bindings calls yapf & isort on the buffer #+begin_src emacs-lisp (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))) #+end_src *** sphinx-doc A package to generate sphinx-compatible docstrings. #+begin_src emacs-lisp (use-package sphinx-doc :straight t :hook (python-mode . sphinx-doc-mode) :config (my-leader-def :keymaps 'sphinx-doc-mode-map "rd" 'sphinx-doc)) #+end_src *** pytest [[https://docs.pytest.org/en/6.2.x/][pytest]] is an unit testing framework for Python. Once again a function to set pytest executable from pipenv. References: - [[https://docs.pytest.org/en/6.2.x/][pytest docs]] - [[https://github.com/wbolster/emacs-python-pytest][emacs-python-pytest]] #+begin_src emacs-lisp :noweb yes (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 <> (add-hook 'python-mode-hook #'my/set-pipenv-pytest) (when (derived-mode-p 'python-mode) (my/set-pipenv-pytest))) #+end_src **** 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. #+begin_src emacs-lisp :noweb-ref override-pytest-run :tangle no (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)))) #+end_src *** code-cells Support for text with magic comments. #+begin_src emacs-lisp (use-package code-cells :straight t :commands (code-cells-mode)) #+end_src *** tensorboard A function to start up [[https://www.tensorflow.org/tensorboard][TensorBoard]]. #+begin_src emacs-lisp (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)) #+end_src ** Java #+begin_src emacs-lisp (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) #+end_src ** Go #+begin_src emacs-lisp (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)) #+end_src ** .NET *** C# | Guix dependencies | Disabled | |-------------------+----------| | omnisharp | t | | dotnet | t | #+begin_src emacs-lisp (use-package csharp-mode :straight t :mode "\\.cs\\'" :config (setq lsp-csharp-server-path (executable-find "omnisharp-wrapper")) (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)) #+end_src *** MSBuild #+begin_src emacs-lisp (use-package csproj-mode :straight t :mode "\\.csproj\\'" :config (add-hook 'csproj-mode #'smartparens-mode)) #+end_src ** fish #+begin_src emacs-lisp (use-package fish-mode :straight t :mode "\\.fish\\'" :config (add-hook 'fish-mode-hook #'smartparens-mode)) #+end_src ** sh #+begin_src emacs-lisp (add-hook 'sh-mode-hook #'smartparens-mode) #+end_src ** Haskell #+begin_src emacs-lisp (use-package haskell-mode :straight t :mode "\\.hs\\'") (use-package lsp-haskell :straight t :after (lsp haskell-mode)) #+end_src ** JSON #+begin_src emacs-lisp (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)) #+end_src ** YAML #+begin_src emacs-lisp (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))) #+end_src ** .env #+begin_src emacs-lisp (use-package dotenv-mode :straight t :mode "\\.env\\..*\\'") #+end_src ** CSV #+begin_src emacs-lisp (use-package csv-mode :straight t :mode "\\.csv\\'") #+end_src ** OFF (OFF) PDF A decent package to view PDFs in Emacs, but I prefer Zathura. References: - https://github.com/vedang/pdf-tools/ #+begin_src emacs-lisp :tangle no (use-package pdf-tools :straight t :commands (pdf-tools-install)) #+end_src ** Docker #+begin_src emacs-lisp (use-package dockerfile-mode :mode "Dockerfile\\'" :straight t :config (add-hook 'dockerfile-mode 'smartparens-mode)) #+end_src * Apps & Misc ** Managing dotfiles A bunch of functions for managing dotfiles with yadm. *** Open Emacs config #+begin_src emacs-lisp (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) #+end_src *** Open Magit for yadm Idea: - [[https://www.reddit.com/r/emacs/comments/gjukb3/yadm_magit/]] #+begin_src emacs-lisp (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) #+end_src *** Open a dotfile Open a file managed by yadm. #+begin_src emacs-lisp (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) #+end_src ** Internet & Multimedia *** Notmuch My notmuch config now resides in [[file:Mail.org][Mail.org]]. #+begin_src emacs-lisp (load-file (expand-file-name "mail.el" user-emacs-directory)) #+end_src *** Elfeed [[https://github.com/skeeto/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. #+begin_src emacs-lisp (use-package elfeed :straight (:repo "SqrtMinusOne/elfeed" :host github) :commands (elfeed) :init (my-leader-def "ae" 'elfeed) :config (setq elfeed-db-directory "~/.elfeed") (setq elfeed-enclosure-default-dir (expand-file-name "~")) (advice-add #'elfeed-insert-html :around (lambda (fun &rest r) (let ((shr-use-fonts nil)) (apply fun r)))) (evil-collection-define-key 'normal 'elfeed-search-mode-map "o" #'my/elfeed-search-filter-source "c" #'elfeed-search-clear-filter "gl" (lambda () (interactive) (elfeed-search-set-filter "+later"))) (evil-collection-define-key 'normal 'elfeed-show-mode-map "ge" #'my/elfeed-show-visit-eww)) #+end_src [[https://github.com/remyhonig/elfeed-org][elfeed-org]] allows configuring Elfeed feeds with an Org file. #+begin_src emacs-lisp (use-package elfeed-org :straight t :after (elfeed) :config (setq rmh-elfeed-org-files '("~/.emacs.d/elfeed.org")) (elfeed-org)) #+end_src **** Some additions Filter elfeed search buffer by the feed under the cursor. #+begin_src emacs-lisp (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))))))) #+end_src Open a URL with eww. #+begin_src emacs-lisp (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)))) #+end_src **** YouTube | Guix dependency | |-----------------| | mpv | | youtube-dl | A function to open YouTube link with mpv #+begin_src emacs-lisp (setq my/youtube-dl-quality-list '("bestvideo[height<=720]+bestaudio/best[height<=720]" "bestvideo[height<=480]+bestaudio/best[height<=480]" "bestvideo[height<=1080]+bestaudio/best[height<=1080]")) (setq my/youtube-dl-quality-default "bestvideo[height<=720]+bestaudio/best[height<=720]") (defun my/open-youtube-video (link) "Open Youtube URL with mpv" (interactive "MURL: ") (let ((quality (completing-read "Quality: " my/youtube-dl-quality-list nil t)) (watch-id (cadr (assoc "watch?v" (url-parse-query-string (substring (url-filename (url-generic-parse-url link)) 1)))))) (if (not watch-id) (message "Can't find youtube link") (let ((yt-link (concat "https://www.youtube.com/watch?v=" watch-id)) (watch-name (concat "mpv-" watch-id))) (start-process watch-name watch-name "mpv" yt-link (format "--ytdl-format=%s" quality)))))) #+end_src And a function to open YouTube link from elfeed #+begin_src emacs-lisp (defun my/elfeed-open-mpv () (interactive) "Open MPV for the current entry" (my/open-youtube-video (elfeed-entry-link elfeed-show-entry))) (with-eval-after-load 'elfeed (evil-collection-define-key 'normal 'elfeed-show-mode-map "gm" #'my/elfeed-open-mpv)) #+end_src *** EMMS EMMS is the Emacs Multi-Media System. I am currently trying to control MPD from Emacs with its help. References: - [[https://www.gnu.org/software/emms/manual/][EMMS Manual]] - [[https://www.youtube.com/watch?v=xTVN8UDScqk][Uncle Dave's video]] #+begin_src emacs-lisp :noweb yes (use-package emms :straight t :commands (emms-smart-browse emms-browser) :init (my-leader-def :infix "as" "s" 'emms-smart-browse "b" 'emms-browser "p" 'emms-pause "q" 'emms-stop "h" 'emms-previous "l" 'emms-next "u" 'emms-player-mpd-connect) (setq emms-mode-line-icon-enabled-p nil) :config (require 'emms-setup) (require 'emms-player-mpd) (emms-all) ;; MPD setup (setq emms-source-file-default-directory (expand-file-name "~/Music/")) (add-to-list 'emms-info-functions 'emms-info-mpd) (add-to-list 'emms-player-list 'emms-player-mpd) (setq emms-player-mpd-server-name "localhost") (setq emms-player-mpd-server-port "6600") (setq emms-player-mpd-music-directory "~/Music") (emms-player-mpd-connect) ;; Clear MPD playlist on clearing EMMS playlist ;; IDK if this is fine for MPD playlists, I don't use them anyhow (add-hook 'emms-playlist-cleared-hook 'emms-player-mpd-clear) ;; evil-lion shadows ga bindings (add-hook 'emms-browser-mode-hook (lambda () (evil-lion-mode -1))) <>) #+end_src **** Some keybindings #+begin_src emacs-lisp (with-eval-after-load 'emms-browser (evil-collection-define-key 'normal 'emms-browser-mode-map "q" 'quit-window)) (with-eval-after-load 'emms (evil-collection-define-key 'normal 'emms-playlist-mode-map "q" 'quit-window)) #+end_src **** Fixes Some fixes until I submit a patch. For some reason EMMS doesn't fetch =albumartist= from MPD. Overriding this function fixes that. #+begin_src emacs-lisp :tangle no :noweb-ref emms-fixes (defun emms-info-mpd-process (track info) (dolist (data info) (let ((name (car data)) (value (cdr data))) (setq name (cond ((string= name "artist") 'info-artist) ((string= name "albumartist") 'info-albumartist) ((string= name "composer") 'info-composer) ((string= name "performer") 'info-performer) ((string= name "title") 'info-title) ((string= name "album") 'info-album) ((string= name "track") 'info-tracknumber) ((string= name "disc") 'info-discnumber) ((string= name "date") 'info-year) ((string= name "genre") 'info-genre) ((string= name "time") (setq value (string-to-number value)) 'info-playing-time) (t nil))) (when name (emms-track-set track name value))))) #+end_src Also, =emms-player-mpd-get-alists= has an interesting bug. This function parses the response to =listallinfo=, which looks something like this: #+begin_example tag1: value1 tag2: value2 ... tag1: value1' tag2: value2' #+end_example This structure has to be converted to list of alists, which looks like: #+begin_example (("tag1" . "value1" "tag2" . "value2") ("tag1" . "value1'" ("tag2" . "value2'"))) #+end_example The original implementation creates a new alist whenever it encounters a tag it has already put in the current alist. Which doesn't work too well if some tags don't repeat, if the order is messed up, etc. Fortunately, according to the [[https://mpd.readthedocs.io/en/latest/protocol.html#command-lsinfo][protocol specification]], each new record has to start with =file=, =directory= or =playlist=. I've overridden the function with that in mind and it fixed the import, at least in my case. #+begin_src emacs-lisp :tangle no :noweb-ref emms-fixes (defun emms-player-mpd-get-alists (info) "Turn the given parsed INFO from MusicPD into an list of alists. The list will be in reverse order." (when (and info (null (car info)) ; no error has occurred (cdr info)) ; data exists (let ((alists nil) (alist nil) cell) (dolist (line (cdr info)) (when (setq cell (emms-player-mpd-parse-line line)) (if (member (car cell) '("file" "directory" "playlist")) (setq alists (cons alist alists) alist (list cell)) (setq alist (cons cell alist))))) (when alist (setq alists (cons alist alists))) alists))) #+end_src *** EWW Emacs built-in web browser. +I wonder if anyone actually uses it.+ I use it occasionally to open links in elfeed. #+begin_src emacs-lisp (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) #+end_src *** ERC ERC is a built-it Emacs IRC client. #+begin_src emacs-lisp (my-leader-def "ai" #'erc-tls) #+end_src A plugin to highlight IRC nicknames: #+begin_src emacs-lisp (use-package erc-hl-nicks :hook (erc-mode . erc-hl-nicks-mode) :straight t) #+end_src Config of my ZNC instance. #+begin_src emacs-lisp (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) #+end_src Kill buffer on part. #+begin_src emacs-lisp (setq erc-kill-buffer-on-part t) #+end_src ZNC support. Seems to provide a few nice features for ZNC. #+begin_src emacs-lisp (use-package znc :straight t :after (erc)) #+end_src *** Google Translate Emacs interface to Google Translate. Can't make it load lazily for some strange reason. References: - [[https://github.com/atykhonov/google-translate][google-translate repo]] - [[https://github.com/atykhonov/google-translate/issues/137#issuecomment-728278849][issue with ttk error fix]] #+begin_src emacs-lisp (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) #+end_src *** Discord integration Integration with Discord. Shows which file is being edited in Emacs. In order for this to work in Guix, a service is necessary - [[file:Desktop.org::*Discord rich presence][Discord rich presence]]. #+begin_src emacs-lisp (use-package elcord :straight t :if (and (or (string= (system-name) "indigo") (string= (system-name) "eminence")) (not my/slow-ssh)) :config (elcord-mode) (add-to-list 'elcord-boring-buffers-regexp-list (rx bos (+ num) "-" (+ num) "-" (+ num) ".org" eos)) (add-to-list 'elcord-boring-buffers-regexp-list (rx bos (= 14 num) "-" (* not-newline) ".org" eos))) #+end_src ** Utilities *** tldr, man, info [[https://tldr.sh/][tldr]] is a collaborative project providing cheatsheets for various console commands. For some reason, the built-in download is broken, so I use my own function. #+begin_src emacs-lisp (use-package tldr :straight t :commands (tldr) :config (setq tldr-source-zip-url "https://github.com/tldr-pages/tldr/archive/refs/heads/main.zip") (defun tldr-update-docs () (interactive) (shell-command-to-string (format "curl -L %s --output %s" tldr-source-zip-url tldr-saved-zip-path)) (when (file-exists-p "/tmp/tldr") (delete-directory "/tmp/tldr" t)) (shell-command-to-string (format "unzip -d /tmp/tldr/ %s" tldr-saved-zip-path) nil nil) (when (file-exists-p tldr-directory-path) (delete-directory tldr-directory-path 'recursive 'no-trash)) (shell-command-to-string (format "mv %s %s" "/tmp/tldr/tldr-main" tldr-directory-path)))) (my-leader-def "hT" 'tldr) #+end_src Of course, Emacs can also display man and info pages. #+begin_src emacs-lisp (setq Man-width-max 180) (my-leader-def "hM" 'man) (evil-collection-define-key 'normal 'Info-mode-map (kbd "RET") 'Info-follow-nearest-node) (defun my/man-fix-width (&rest _) (setq-local Man-width (- (window-width) 4))) (advice-add #'Man-update-manpage :before #'my/man-fix-width) #+end_src *** pass I use [[https://www.passwordstore.org/][pass]] as my password manager. Expectedly, there is Emacs frontend for it. #+begin_src emacs-lisp (use-package pass :straight t :commands (pass) :init (my-leader-def "ak" #'pass) :config (setq pass-show-keybindings nil)) #+end_src *** Docker A package to manage docker containers from Emacs. The file =progidy-config.el= sets variable =my/docker-directories=, which allows to #+begin_src emacs-lisp (use-package docker :straight t :commands (docker) :init (my-leader-def "ao" 'docker)) #+end_src By default, docker commands are ran in =default-directory=. Even worse, transient doesn't allow to set =default-directory= temporarily, via =let=. But often I don't want to change =default-directory= of a buffer (e.g. via Dired) to run a command from there. So I decided to implement a following advice: #+begin_src emacs-lisp (setq my/selected-docker-directory nil) (defun my/docker-override-dir (fun &rest args) (let ((default-directory (or my/selected-docker-directory default-directory))) (setq my/selected-docker-directory nil) (apply fun args))) #+end_src It overrides =default-directory= for the first launch of a function. Now, add the advice to the required functions from =docker.el=: #+begin_src emacs-lisp (with-eval-after-load 'docker (advice-add #'docker-compose-run-docker-compose-async :around #'my/docker-override-dir) (advice-add #'docker-compose-run-docker-compose :around #'my/docker-override-dir) (advice-add #'docker-run-docker-async :around #'my/docker-override-dir) (advice-add #'docker-run-docker :around #'my/docker-override-dir)) #+end_src And here is a function which prompts user for the directory. File =progidy-config.el= sets an alist of possible directories, look the section about [[*Progidy][progidy]]. #+begin_src emacs-lisp (defun my/docker-from-dir () (interactive) (when (not (boundp 'my/docker-directories)) (load (concat user-emacs-directory "prodigy-config"))) (let* ((directories (mapcar (lambda (el) (cons (format "%-30s %s" (car el) (cdr el)) (cdr el))) my/docker-directories)) (selected-directory (cdr (assoc (completing-read "Docker: " directories nil nil "^") directories)))) (setq my/selected-docker-directory selected-directory) (docker))) (my-leader-def "aO" 'my/docker-from-dir) #+end_src *** Progidy [[https://github.com/rejeep/prodigy.el][prodigy.el]] is a package to run various services. I've previously used tmuxp + tmux, but want to try this as well. The actual service definitions are in the =~/.emacs.d/prodigy.org=, which tangles to =prodigy-config.el=. Both files are encrypted in yadm, as they contain personal data. #+begin_src emacs-lisp (use-package prodigy :straight t :commands (prodigy) :init (my-leader-def "ap" 'prodigy) :config (when (not (boundp 'my/docker-directories)) (load (concat user-emacs-directory "prodigy-config"))) (evil-collection-define-key 'normal 'prodigy-view-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)) #+end_src *** proced proced is a Emacs built-it process viewer, like top. #+begin_src emacs-lisp (my-leader-def "ah" 'proced) (add-hook 'proced-mode-hook (lambda () (visual-line-mode -1) (setq-local truncate-lines t))) #+end_src *** Guix An Emacs package to help managing GNU Guix. #+begin_src emacs-lisp (use-package guix :straight t :commands (guix) :init (my-leader-def "ag" 'guix)) #+end_src ** Productivity *** Pomidor A simple pomodoro technique timer. #+begin_src emacs-lisp (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)) #+end_src *** Calendar Emacs' built-in calendar. Can even calculate sunrise and sunset times. #+begin_src emacs-lisp (setq calendar-date-style 'iso) ;; YYYY/mm/dd (setq calendar-week-start-day 1) (setq calendar-time-display-form '(24-hours ":" minutes)) (setq calendar-latitude 59.9375) (setq calendar-longitude 30.308611) #+end_src ** Fun *** screenshot.el Tecosaur's plugin to make beautiful code screenshots. | Guix dependency | |-----------------| | imagemagick | #+begin_src emacs-lisp (use-package screenshot :straight (:repo "tecosaur/screenshot" :host github :files ("screenshot.el")) :commands (screenshot) :init (my-leader-def "S" 'screenshot)) #+end_src *** Snow #+begin_src emacs-lisp (use-package snow :straight (:repo "alphapapa/snow.el" :host github) :commands (snow)) #+end_src *** Zone #+begin_src emacs-lisp (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)))) #+end_src * Guix settings | Guix dependency | Description | |---------------------+-------------------------------| | emacs-vterm | A vterm package | | ripgrep | A recursive search tool | | the-silver-searcher | Another recursive search tool | #+NAME: packages #+begin_src emacs-lisp :tangle no (my/format-guix-dependencies) #+end_src #+begin_src scheme :tangle .config/guix/manifests/emacs.scm :noweb yes (specifications->manifest '("emacs-native-comp" <>)) #+end_src