#+TITLE: Packages.El Literate: #+PROPERTY: header-args:elisp :tangle ~/.config/emacs/dotslash-modules/packages.el #+auto_tangle: t #+BEGIN_SRC elisp ;; NOTE: ;; This file is generated from packages.org #+END_SRC * Helpers Helper variables adopted from Doom Emacs I'm not sure if I'll ever use. #+BEGIN_SRC elisp (defconst IS-MAC (eq system-type 'darwin)) (defconst IS-LINUX (memq system-type '(gnu gnu/linux gnu/kfreebsd berkeley-unix))) (defconst IS-WINDOWS (memq system-type '(cygwin windows-nt ms-dos))) (defconst IS-BSD (memq system-type '(darwin berkeley-unix gnu/kfreebsd))) (defconst HAS-EMACS28+ (> emacs-major-version 27)) (defconst HAS-EMACS29+ (> emacs-major-version 28)) (defconst HAS-DYNMODULES (featurep 'dynamic-modules)) (defconst HAS-NATIVECOMP (featurep 'native-compile)) #+END_SRC * Theme :PROPERTIES: :ID: theme-test :END: I sometimes switch between these (sub-headings). To switch, set =:tangle yes/no=. #+BEGIN_SRC elisp (set-face-attribute 'completions-first-difference nil :weight 'normal) (defvar ./corfu-bar-color "gray20" ;; Default value is for dark themes "Color for the corfu scroll bar.") (defvar ./cursor-color "white" "Color for cursor") (defvar ./theme-type "dark" "Dark or light") #+END_SRC TODO: - Put all variable declarations in =init.org= - These theme configurations, define them as modules so I can easily switch between them at run time (and load my customizations for each theme together) - Also maybe (if possible) if I were to switch at run time ensure previous theme setting aren't retained. ** Nord Used by Rougier's NANO-emacs. The dark theme is kinda nice, like dracula but less purple, no pink, more cyan. But the muted colors are too unreadable (such as docstrings or comments). TODO: Customize the lower-contrast nord colors to be more readable. #+BEGIN_SRC elisp :tangle no (use-package nord-theme :config (load-theme 'nord t) ) #+END_SRC ** EF-themes (ef-light) Dazzling, tacky, elegant light theme. #+BEGIN_SRC elisp :tangle no (use-package ef-themes ;; :init ;; (set-face-attribute 'ef-themes-ui-variable-pitch nil :inherit 'variable-pitch) :config (load-theme 'ef-light t) (set-face-attribute 'font-lock-builtin-face nil :weight 'normal) (set-face-attribute 'font-lock-keyword-face nil :weight 'normal) (set-face-attribute 'font-lock-variable-name-face nil :foreground "black") (set-face-attribute 'font-lock-property-name-face nil :foreground "black") (set-face-attribute 'font-lock-constant-face nil :foreground "black") (set-face-attribute 'font-lock-string-face nil :foreground "dark green") (set-face-attribute 'window-divider nil :foreground "gray90") (set-face-attribute 'window-divider-first-pixel nil :foreground "white") (set-face-attribute 'window-divider-last-pixel nil :foreground "white") (setq ./corfu-bar-color "gray80") (set-face-attribute 'org-link nil :foreground "DodgerBlue3" :underline t) ) #+END_SRC ** EF-themes (ef-elea-dark) Current preferred dark theme. The pink accent and the brownish background reminds me of gruvbox, but the purple and the green makes it look different. Overall it's quite ugly; and that is the beauty of it! #+BEGIN_SRC elisp (use-package ef-themes :config (load-theme 'ef-elea-dark t) (set-face-attribute 'cursor nil :background "white") (set-face-attribute 'font-lock-builtin-face nil :weight 'normal) (set-face-attribute 'font-lock-keyword-face nil :weight 'normal) (set-face-attribute 'font-lock-variable-name-face nil :foreground "white") (set-face-attribute 'font-lock-property-name-face nil :foreground "white") (set-face-attribute 'font-lock-constant-face nil :foreground "white") (set-face-attribute 'window-divider nil :foreground "gray20") (set-face-attribute 'window-divider-first-pixel nil :foreground "black") (set-face-attribute 'window-divider-last-pixel nil :foreground "black") ) #+END_SRC ** Timu Spacegrey (dark) This theme has partial zenburn vibes (orange) but less brown. NGL it's so cute. Primary accent is like light brownish orange yet user-interaction accents use pink. :p This was my second ever emacs preferred theme (it was zenburn first then this). #+BEGIN_SRC elisp :tangle no (use-package timu-spacegrey-theme :after (org corfu vertico) :config ;;(load-theme 'modus-vivendi t) (load-theme 'timu-spacegrey t) (defface ./theme-completion-popup '((t :inherit 'fixed-pitch)) "Face for completion popup.") (defface ./theme-completion-selected '((t :background "gray25" :weight normal)) "Face for selected completion item.") (set-face-attribute 'corfu-default nil :inherit './theme-completion-popup) (set-face-attribute 'corfu-current nil :inherit './theme-completion-selected :weight 'normal) (set-face-attribute 'corfu-bar nil :background "gray40") (set-face-attribute 'vertico-current nil :inherit './theme-completion-selected) ;;(set-face-attribute 'line-number nil :background "bg") ;; Color from modulus vivendi (set-face-attribute 'show-paren-match nil :background "#6f3355") (set-face-attribute 'cursor nil :background "textcolor") (set-face-attribute 'org-link nil :foreground "deep sky blue" :underline t) :init (setq timu-spacegrey-mode-line-border t) (setq timu-spacegrey-org-intense-colors t timu-spacegrey-scale-org-document-title 1.7 timu-spacegrey-scale-org-document-info 1.4 timu-spacegrey-scale-org-level-1 1.6 timu-spacegrey-scale-org-level-2 1.3 timu-spacegrey-scale-org-level-3 1.2) ) #+END_SRC XXX: Somehow putting the org scale settings in =:init= works? And putting it in =:config= doesn't work?! ** Common settings for themes #+BEGIN_SRC elisp (setq ./theme-type (symbol-name (frame-parameter nil 'background-mode))) (setq ./cursor-color (if (string= ./theme-type "dark") "white" "black")) (set-face-attribute 'cursor nil :background ./cursor-color) ;; TODO: Do this for window divider and corfu UI items, and magit diff backgrounds. ;; and org-link #+END_SRC * Tab bar https://www.gnu.org/software/emacs/manual/html_node/emacs/Tab-Bars.html This makes the tabs in the tab bar fill the entire frame width, similar to qutebrowser. #+BEGIN_SRC elisp (setq tab-bar-auto-width-max nil) #+END_SRC Go to =*scratch*= buffer when opening new tabs, like browsers. I guess! #+BEGIN_SRC elisp (setq tab-bar-new-tab-choice "*scratch*") #+END_SRC Slightly more contrast #+BEGIN_SRC elisp ;; FIXME: Doesn't work (set-face-attribute 'tab-bar-tab-inactive nil :background "textBackgroundColor") #+END_SRC Tabs in emacs appears to be similar to Vim, where each tab can hold window split layouts. ** Default key-binds Tab bar keys have prefix =C-x t=, use which-key to explore the options from there. Switching tabs can be done with =C-TAB= and =S-C-TAB= * Evil My setup allows using Vim bindings in normal "mode" and emacs bindings in insert "mode". https://stackoverflow.com/questions/25542097/ I previously used this setting for achieve it: #+BEGIN_SRC elisp :tangle no ;; Use emacs keybindings in insert state 😱 (setq evil-disable-insert-state-bindings t) #+END_SRC It worked, but the bindings I've set for =evil-insert-state-map= elsewhere didn't take effect, so I opted for an alternative solutions from the stackoverflow question. Below is the =use-package= block for evil. Noweb references are used, which are defined later on in this section. #+BEGIN_SRC elisp :noweb yes (use-package evil :demand t :init <> :config <> <> (defun ./evil-off () "Call `turn-off-evil-mode' and show a message" (interactive) (turn-off-evil-mode) (message "Evil mode is turned off")) ;; Disable evil in some modes (dolist (mode '(elpaca-ui-mode dired-mode magit-mode)) (evil-set-initial-state mode 'emacs)) (evil-mode 1) ) #+END_SRC Turning off evil mode doesn't seem to work so for elpaca hook I decided to just enter insert instead. This allows Elpaca's shortcut keys to work, eg, =q=, =I=, =s=. ** Init These values must be set before evil load for them to take into effect. - Using =evil-search= allows search results to be kept highlighted when using =/=. I-search is still available in =C-s= in emacs state (see [[Consult]]). - Still unsure whether to stick to emacs regexp or use vim for evil-related searches, but this is adopted from doom. #+BEGIN_SRC elisp :noweb-ref evil-init :tangle no (setq evil-search-module 'evil-search evil-ex-search-vim-style-regexp t) #+END_SRC These are subject to change (REVIEW), adopted from doom. #+BEGIN_SRC elisp :noweb-ref evil-init :tangle no (setq evil-ex-visual-char-range t ; column range for ex commands evil-mode-line-format 'nil ;; more vim-like behavior evil-symbol-word-search t) #+END_SRC Set cursor to indicate the evil state. Generally =bar= means it's writable, =box= means movable, =hollow= means special. #+BEGIN_SRC elisp :noweb-ref evil-init :tangle no (setq evil-default-cursor 'box evil-normal-state-cursor 'box evil-emacs-state-cursor 'bar evil-insert-state-cursor 'bar evil-visual-state-cursor 'hollow) #+END_SRC These are copied from doom. #+BEGIN_SRC elisp :noweb-ref evil-init :tangle no ;; Only do highlighting in selected window so that Emacs has less work ;; to do highlighting them all. (setq evil-ex-interactive-search-highlight 'selected-window ;; It's infuriating that innocuous "beginning of line" or "end of line" ;; errors will abort macros, so suppress them: evil-kbd-macro-suppress-motion-error t) #+END_SRC Adopt vim's C-u and C-d scrolling #+BEGIN_SRC elisp :noweb-ref evil-init :tangle no (setq evil-want-C-u-scroll t) ; must be set in :init #+END_SRC I read somewhere that you can access X-Selection in linux from a vim register, forgot which one of =+= or === it was. Well that's kinda neat, but how would it be useful? If you want to paste, you can copy. Unless you want to paste two things, so you copy one, and leave the selection on another, so you can do ="+p= for one and ="=p= for the other? Oh well, I won't need that here (and possibly don't want that either, I'd like my clipboard to only contain things I explicitly copied). #+BEGIN_SRC elisp :noweb-ref evil-config :tangle no (setq evil-visual-update-x-selection-p nil) #+END_SRC Set =<= and =>= to shifting 2 spaces, this allows adding up shifts to do 4 spaces. Odd-numbered space indents is, well, um, weird (like 3). Sure if you can't decide between 2 and 4, but if you can't decide you can flip a coin, enforce it in =.editorconfig=, or use tabs and less the viewer decide! Indenting by 1 space possibly only exist in lisp where the entire program is a rather obtuse array-like structure, and we tend to align arguments that span multiple lines by the function name, which means any arbitrary indent width is possible. Well, for that I will be relying on my editor to automatically indent for me. So in conclusion, setting shift-width to 2 is the best option as of now. TODO: Like my neovim config, keep visual selection after shifting width. #+BEGIN_SRC elisp :noweb-ref evil-config :tangle no (setq evil-shift-width 2) #+END_SRC Allow evil motion keys to go across lines. If point is at beginning of line, and I use left arrow or =h=, point will then be at the end of the previous line. #+BEGIN_SRC elisp :noweb-ref evil-config :tangle no (setq evil-cross-lines t) #+END_SRC Set undo system to allow redos. TODO: - Save undos in undodir like vim, so when opening new files there's undo data - Should I use undo-tree? #+BEGIN_SRC elisp :noweb-ref evil-init :tangle no ;; REVIEW: Is this needed if evil-redo-function is set? (setq evil-undo-system 'undo-redo) ;; 'undo-redo from Emacs 28 (setq evil-undo-function 'undo) (setq evil-redo-function 'undo-redo) #+END_SRC ** Maps *** Misc maps This allows me to use emacs motion key bindings in evil insert mode. Super useful -- best of both worlds! #+BEGIN_SRC elisp :noweb-ref evil-maps :tangle no ;; Emacs keybindings in evil insert state - must be set in :config (setq evil-insert-state-map (make-sparse-keymap)) (define-key evil-insert-state-map (kbd "") 'evil-normal-state) (define-key evil-emacs-state-map (kbd "") 'evil-normal-state) #+END_SRC =C-e= and =C-y= are already bound in emacs state, so I use shift of them instead. #+BEGIN_SRC elisp :noweb-ref evil-maps :tangle no (define-key evil-insert-state-map (kbd "S-C-e") 'evil-scroll-line-down) (define-key evil-emacs-state-map (kbd "S-C-e") 'evil-scroll-line-down) (define-key evil-insert-state-map (kbd "S-C-y") 'evil-scroll-line-up) (define-key evil-emacs-state-map (kbd "S-C-y") 'evil-scroll-line-up) #+END_SRC =S-C-v/c= for clipboard: This is a solution I found on Doom Emacs forum. The problem was that if I use the system clipboard shortcut to copy things elsewhere, and I go to emacs, edit some text with =d=, =y= etc, then if I use the system clipboard shortcut to paste what I copied earlier, results from =d=/=y= are pasted instead. This setting bindings different keys for pasting from system clipboard within emacs - Ctrl shift C and V. It may be a little unergonomic going between these #+BEGIN_SRC elisp :noweb-ref evil-maps :tangle no ;; Don't put vim yanks into system clipboard ;; But use shift C-v / C-c to paste/copy from system clipboard instead (setq select-enable-clipboard nil) (global-set-key (kbd "S-C-c") #'clipboard-kill-ring-save) (global-set-key (kbd "S-C-v") #'clipboard-yank) (global-set-key (kbd "") 'keyboard-escape-quit) #+END_SRC Add some useful keys for command line (=:=) and global buffer switching using evil functions. #+BEGIN_SRC elisp :noweb-ref evil-maps :tangle no ;; FIXME (evil-set-leader nil (kbd "S-C-SPC")) ;; C-SPC is mark set (evil-set-leader 'normal (kbd "SPC")) (define-key evil-command-line-map "\C-a" 'move-beginning-of-line) (define-key evil-command-line-map "\C-e" 'move-end-of-line) ;; (define-key evil-command-line-map "\C-d" nil t) ;; (define-key evil-command-line-map "\C-l" nil t) (global-set-key (kbd "s-") #'previous-buffer) (global-set-key (kbd "s-") #'next-buffer) (global-set-key (kbd "S-s-") #'evil-window-down) (global-set-key (kbd "S-s-") #'evil-window-up) (global-set-key (kbd "S-s-") #'evil-window-left) (global-set-key (kbd "S-s-") #'evil-window-right) #+END_SRC *** Leader maps This solution for binding leader prefixes is found on evil-guide: https://github.com/noctuid/evil-guide?tab=readme-ov-file#leader-key #+BEGIN_SRC elisp :noweb-ref evil-maps :tangle no (defvar ./leader-map (make-sparse-keymap) "Keymap for leader shortcuts") (define-key evil-normal-state-map (kbd "SPC") ./leader-map) (define-key ./leader-map "q" #'evil-quit) (define-key ./leader-map "w" #'save-buffer) (define-key ./leader-map "x" #'evil-save-modified-and-close) (define-key ./leader-map "1" #'delete-other-windows) (defvar ./leader-buffer-map (make-sparse-keymap) "Keymap for leader shortcuts for buffers") (define-key ./leader-map "b" ./leader-buffer-map) (define-key ./leader-buffer-map "b" #'consult-buffer) (define-key ./leader-buffer-map "s" #'save-buffer) (define-key ./leader-buffer-map "d" #'evil-delete-buffer) (defvar ./leader-file-map (make-sparse-keymap) "Keymap for leader shortcuts for files") (define-key ./leader-map "f" ./leader-file-map) (define-key ./leader-file-map "f" #'find-file) (define-key ./leader-file-map "r" #'consult-recent-file) (define-key ./leader-file-map "p" #'project-find-file) (defvar ./leader-frame-map (make-sparse-keymap) "Keymap for leader shortcuts for frames") (define-key ./leader-map "F" ./leader-frame-map) (define-key ./leader-frame-map "q" #'delete-frame) (define-key ./leader-frame-map "d" #'delete-frame) (define-key ./leader-frame-map "u" #'undelete-frame) (define-key ./leader-frame-map "R" #'rename-frame) (define-key ./leader-frame-map "o" #'other-frame) (define-key ./leader-frame-map "c" #'clone-frame) #+END_SRC * Misc Other plugins (or apps) with not that much configuration. #+BEGIN_SRC elisp (use-package elpher) ;; eww is part of emacs now?!! ;;(use-package eww) (use-package visual-fill-column :init (setq-default visual-fill-column-center-text t)) (use-package imenu-list :config (setq imenu-list-auto-resize t) ;; Auto-update Ilist buffer :hook (imenu-list-major-mode . (lambda () (imenu-list-minor-mode 1) (visual-line-mode 1) ;; REVIEW (display-line-numbers-mode -1) (evil-insert-state 1)))) (use-package math-symbol-lists :after cape :config ;; This is actually for C-\, then select input "math", ;; then the Ω will show in the status bar. (quail-define-package "math" "UTF-8" "Ω" t) ;; (quail-define-rules ; add whatever extra rules you want to define here... ;; ("\\from" #X2190) ;; ("\\to" #X2192) ;; ("\\lhd" #X22B2) ;; ("\\rhd" #X22B3) ;; ("\\unlhd" #X22B4) ;; ("\\unrhd" #X22B5)) (mapc (lambda (x) (if (cddr x) (quail-defrule (cadr x) (car (cddr x))))) (append math-symbol-list-basic math-symbol-list-extended)) ) #+END_SRC Maybe I'll figure out a better way to organize list of modes where display-line-numbers-mode should be disabled. #+BEGIN_SRC elisp :tangle no ;; (use-package help ;; :elpaca nil ;; :hook ;; ((help-mode Custom-mode) . (lambda () (display-line-numbers-mode -1))) ;; ) #+END_SRC Dang Custom-mode is actually capitalized ** Dired #+BEGIN_SRC elisp ;; (use-package dired ;; :elpaca nil ;; :config (setq delete-by-moving-to-trash t ;; Emacs 29 dired-make-directory-clickable t dired-mouse-drag-files t ) ;;:hook ;; FIXME: replicate evil state cursor style switching here (defun ./cursor-toggle-readonly () (if buffer-read-only (progn (setq cursor-type 'box) (set-cursor-color "orange")) (setq cursor-type 'bar) (set-cursor-color ./cursor-color))) (add-hook 'read-only-mode-hook #'./cursor-toggle-readonly) (add-hook 'dired-mode-hook (lambda () (hl-line-mode) (./cursor-toggle-readonly))) ;; ) #+END_SRC ** Minibuffer Enjoy emacs' editting key chords while there's still a glimmer of space in your emacs that forces you to use it, child. #+BEGIN_SRC elisp (add-hook 'minibuffer-setup-hook (lambda () (setq cursor-type 'bar))) (add-hook 'minibuffer-exit-hook (lambda () (setq cursor-type 'box))) #+END_SRC ** Wrap region This plugin gives you true IDE-like behaviour of selecting some text, press ="= then it'll wrap your selection with quotes. It enables this for quotes and brackets by default, below I've added some more useful wrappers, some of which are also suggested from the wrap region README. #+BEGIN_SRC elisp (use-package wrap-region :config (wrap-region-add-wrappers '(("/* " " */" "#" (java-mode javascript-mode css-mode)) ("`" "`" nil (markdown-mode org-mode)) ("=" "=" nil (org-mode)) ("~" "~" nil (org-mode)) ("*" "*" nil (markdown-mode org-mode)))) :hook ((org-mode markdown-mode) . wrap-region-mode) ) #+END_SRC ** Magit #+BEGIN_SRC elisp (use-package magit) #+END_SRC ** Breadcrumb By the owner of both eglot and yasnippet: breadcrumb context in your headerline that uses project.el or imenu in that order! And yes you can even click on the breadcrumb components to jump to things like imenu. #+BEGIN_SRC elisp (use-package breadcrumb :diminish breadcrumb-mode :init (breadcrumb-mode 1)) #+END_SRC * Vertico #+BEGIN_SRC elisp (use-package vertico :init (vertico-mode) ;; Grow and shrink the Vertico minibuffer (setq vertico-resize t) ;; Optionally enable cycling for `vertico-next' and `vertico-previous'. (setq vertico-cycle t) :hook ;; For find-file, remove old file path if I start typing a new one ('rfn-eshadow-update-overlay-hook . #'vertico-directory-tidy) ) (use-package orderless :init (setq completion-styles '(orderless) completion-category-defaults nil completion-category-overrides '((file (styles partial-completion))))) ;; Persist history over Emacs restarts. Vertico sorts by history position. (use-package savehist :elpaca nil :init (savehist-mode)) ;; Pasted from vertico (use-package emacs :elpaca nil :init ;; Add prompt indicator to `completing-read-multiple'. ;; Alternatively try `consult-completing-read-multiple'. (defun crm-indicator (args) (cons (concat "[CRM] " (car args)) (cdr args))) (advice-add #'completing-read-multiple :filter-args #'crm-indicator) ;; Do not allow the cursor in the minibuffer prompt (setq minibuffer-prompt-properties '(read-only t cursor-intangible t face minibuffer-prompt)) (add-hook 'minibuffer-setup-hook #'cursor-intangible-mode) ;; Emacs 28: Hide commands in M-x which do not work in the current mode. ;; Vertico commands are hidden in normal buffers. ;; (setq read-extended-command-predicate ;; #'command-completion-default-include-p) ;; Enable recursive minibuffers (setq enable-recursive-minibuffers t) ;; From corfu ;; TAB cycle if there are only few candidates (setq completion-cycle-threshold 3) ;; Emacs 28: Hide commands in M-x which do not apply to the current mode. ;; Corfu commands are hidden, since they are not supposed to be used via M-x. ;; (setq read-extended-command-predicate ;; #'command-completion-default-include-p) ) #+END_SRC Marginalia shows description of each candidate in minibuffer completion next to candidates. #+BEGIN_SRC elisp (use-package marginalia :diminish :config (setq marginalia-annotators '(marginalia-annotators-heavy marginalia-annotators-light nil)) (marginalia-mode 1) ) #+END_SRC * Consult #+BEGIN_SRC elisp (use-package consult :config (global-set-key (kbd "C-s") 'consult-line) (define-key evil-insert-state-map (kbd "C-s") 'isearch-forward) (define-key evil-emacs-state-map (kbd "C-s") 'isearch-forward) (global-set-key (kbd "C-c g") 'consult-org-heading) (global-set-key (kbd "C-x C-b") 'consult-buffer) ;; Doesn't work? (global-set-key [?\C-\t] 'consult-buffer) (define-key minibuffer-local-map (kbd "C-r") 'consult-history) (setq completion-in-region-function #'consult-completion-in-region) ) #+END_SRC * Corfu Note that some color settings are set in [[Theme]] #+BEGIN_SRC elisp (use-package corfu :custom (corfu-cycle t) ;; Enable cycling for `corfu-next/previous' ;; Default is M-SPC, if M-SPC is bound like I have on my Mac (Alfred) S-M-SPC also works ;;(corfu-separator ?\s) ;; Orderless separator ;; separator: Quit at boundary if no `corfu-separator' inserted (corfu-quit-at-boundary 'separator) ;; separator: only stay alive if no match and `corfu-separator' inserted (corfu-quit-no-match 'separator) ;; Don't change what I typed to what I selected when previewing completions (corfu-preview-current nil) (corfu-preselect 'first) ;; Default = #'insert. Options: quit, nil ;;(corfu-on-exact-match nil) ;; Prevent last/first item being hidden behind windows ;; FIXME: Doesn't work (corfu-scroll-margin 2) (corfu-right-margin-width 2) ;; Enable Corfu only for certain modes. ;; :hook ((prog-mode . corfu-mode) ;; (shell-mode . corfu-mode) ;; (eshell-mode . corfu-mode)) ;; FIXME: doesn't work: evil insert/emacs keybinds takes higher precendence it seems (define-key corfu-map (kbd "") 'corfu-quit) :custom-face (corfu-border ((t (:background "gray20" :weight bold)))) (corfu-default ((t (:inherit fixed-pitch)))) :init ;; Recommended: Enable Corfu globally. ;; This is recommended since Dabbrev can be used globally (M-/). ;; See also `global-corfu-modes'. (global-corfu-mode) (corfu-popupinfo-mode 1) :config (setq corfu-bar-width 0.8) (set-face-attribute 'corfu-bar nil :background ./corfu-bar-color) (defun corfu-enable-always-in-minibuffer () "Enable Corfu in the minibuffer if Vertico/Mct are not active." (unless (or (bound-and-true-p mct--active) (bound-and-true-p vertico--input) (eq (current-local-map) read-passwd-map)) ;; (setq-local corfu-auto nil) ;; Enable/disable auto completion (setq-local corfu-echo-delay nil ;; Disable automatic echo and popup corfu-popupinfo-delay '(0 . 0)) ;; Use popupinfo in minibuffer too, why not? (corfu-mode 1))) (add-hook 'minibuffer-setup-hook #'corfu-enable-always-in-minibuffer 1) (setq corfu-popupinfo-delay '(0 . 0)) ) #+END_SRC ** Kind-icon + Corfu This is like one of those (few) times that I've cherished Custom's convenience. #+BEGIN_SRC elisp (use-package kind-icon :after corfu :custom (kind-icon-mapping ;; These are fetched (and cached) from pictogrammers.com/library/mdi '((array "ar" :icon "code-brackets" :face font-lock-type-face) (boolean "b" :face font-lock-builtin-face) (class "C" :face font-lock-type-face) ;; family-tree could be used. but too dense (color "#" :icon "palette" :face success) (command ">_" :face default) (constant "cn" :icon "lock-remove-outline" :face font-lock-constant-face) (constructor "C+" :icon "plus-circle-multiple" :face font-lock-function-name-face) (enummember "em" :icon "order-bool-ascending-variant" :face font-lock-builtin-face) (enum-member "em" :icon "order-bool-ascending-variant" :face font-lock-builtin-face) (enum "e" :icon "format-list-bulleted-square" :face font-lock-builtin-face) (event "ev" :icon "lightning-bolt-outline" :face font-lock-warning-face) (field "fd" :face font-lock-variable-name-face) (file "F" :icon "file-document-outline" :face font-lock-string-face) (folder "D" :icon "folder" :face font-lock-doc-face) (interface "if" :icon "application-brackets-outline" :face font-lock-type-face) (keyword "kw" :face font-lock-keyword-face) (macro "mc" :icon "lambda" :face font-lock-keyword-face) (magic "ma" :icon "shimmer" :face font-lock-builtin-face) (method "me" :face font-lock-function-name-face) (function "f" :icon "function" :face font-lock-function-name-face) (module "mo" :icon "package-variant-closed" :face font-lock-preprocessor-face) (numeric "0" :icon "numeric" :face font-lock-builtin-face) (operator "÷" :icon "division" :face font-lock-comment-delimiter-face) (param "pa" :icon "cog-outline" :face default) (property "pr" :icon "wrench" :face font-lock-variable-name-face) (reference "rf" :icon "library" :face font-lock-variable-name-face) (snippet "S" :face font-lock-string-face) (string "\"" :icon "text-box" :face font-lock-string-face) (struct "{}" :icon "code-braces" :face font-lock-variable-name-face) (text " " :face font-lock-doc-face) ; text-short could be used (typeparameter "tp" :icon "format-list-bulleted-type" :face font-lock-type-face) (type-parameter "tp" :icon "format-list-bulleted-type" :face font-lock-type-face) (unit "u" :icon "square-rounded-outline" :face font-lock-constant-face) (value "vl" :icon "plus-circle-outline" :face font-lock-builtin-face) (variable "v" :face font-lock-variable-name-face) (t "?" :face font-lock-warning-face))) (kind-icon-blend-background nil) :custom-face (kind-icon-default-face ((t (:background nil)))) :config ;;(setq kind-icon-default-face 'corfu-default) ; to compute blended backgrounds correctly (add-to-list 'corfu-margin-formatters #'kind-icon-margin-formatter)) #+END_SRC ** Cape + Corfu With references from System Crafter's crafted-emacs configuration #+BEGIN_SRC elisp (use-package cape ;;:after math-symbol-lists :config ;; Add useful defaults completion sources from cape ;; (add-to-list 'completion-at-point-functions #'cape-file) ;; ;;(add-to-list 'completion-at-point-functions #'cape-dabbrev) ;;(add-to-list 'completion-at-point-functions #'cape-tex) (add-to-list 'completion-at-point-functions #'cape-emoji) ;; Silence the pcomplete capf, no errors or messages! ;; Important for corfu (advice-add 'pcomplete-completions-at-point :around #'cape-wrap-silent) ;; Ensure that pcomplete does not write to the buffer ;; and behaves as a pure `completion-at-point-function'. (advice-add 'pcomplete-completions-at-point :around #'cape-wrap-purify) (define-key evil-insert-state-map (kbd "C-x C-f") 'cape-file) (define-key evil-insert-state-map (kbd "C-x C-d") 'cape-dict) (define-key evil-insert-state-map (kbd "C-x C-w") 'cape-dabbrev) (define-key evil-insert-state-map (kbd "C-x C-:") 'cape-emoji) (cape-char--define math "math" ?\\) (add-to-list 'completion-at-point-functions #'cape-math) (define-key evil-insert-state-map (kbd "C-x C-$") 'cape-math) :hook (eshell-mode-hook . (lambda () (setq-local corfu-quit-at-boundary t corfu-quit-no-match t corfu-auto nil) (corfu-mode))) ) #+END_SRC I disabled adding dabbrev to CAPF to prevent =corfu-candidate-overlay= (see below) from suggesting arbitrary text completions when I'm in comments or strings or whatever. It's annoying. ** Corfu Candidate Overlay It's like how copilot gives you a completion after your cursor... but this is corfu! (first candidate) Also like fish's autosuggestion. #+BEGIN_SRC elisp :noweb yes (use-package corfu-candidate-overlay :config (corfu-candidate-overlay-mode 1) ;; This is global (set-face-attribute 'corfu-candidate-overlay-face nil :foreground "dim grey") ;; Use TAB to accept a completion, how cool is that! <> (define-key evil-insert-state-map (kbd "TAB") './insert-state-tab) ) #+END_SRC The function below is the handler for the TAB key in evil insert state. The gist of what it does starts in the =(if at-heading [...])= block. The extra code before which is explained in the comment. The last time I used Doom, it doesn't support using =org-cycle= if point is at the end of line on an org heading. I have to move it *ON* the heading text for it to =org-cycle=. The first snippet below was my first attempt at this issue, before I checked the source code for corfu candidate overlay to obtain code for checking whether CAP is possible at point. The first snippet sort of works but is not as good, see the excessive comments. The second snippet is the tangled one, it works (for now). #+BEGIN_SRC elisp :tangle no (defun ./insert-state-tab () "Handle TAB key in insert state. Confirm candidate overlay or call `org-cycle' depending on position of current point. If it is at an org heading, or at the end of line that contains a folded org heading, then `org-cycle' is called. Otherwise `corfu-candidate-overlay-complete-at-point'." (interactive) (if (org-at-heading-p) (org-cycle) (let ((current-char (buffer-substring-no-properties (point) (+ (point) 1))) (current-line (buffer-substring-no-properties (line-beginning-position) (line-end-position)))) (if (and (string= current-char "\n") (org-invisible-p2)) ;; If point is at the very end of an org heading ;; `org-at-heading-p' returns nil so I have to check it ;; another way. ;; I'm honestly not sure if there is a case where the ;; condition above evaluates to true but we don't actually ;; want to `org-cycle', so I added a message. (progn ;;(end-of-line) ;; `end-of-line' actually moves point to position BEFORE ;; the ellipsis char where as evil's end of line moves ;; it after (as with if position is selected with mouse, ;; click at the end of line of folded org heading). ;; Initially I wanted to use this to put point in the ;; "visible" end of line position and use `org-cycle', ;; which should work (when called interactively). But ;; for some reason it didn't work when using this ;; function so I decided to use `evil-open-fold' ;; instead. ;; ;; Surprisingly evil's fold on org works even if point ;; is on the (weird) end of folded heading line ;; position, which as mentioned above is where ;; `org-at-heading-p' returns nil. ;; ;; TODO: Possibly a bug with org itself? (save-excursion ;; Point is still moved out of the heading line! (evil-open-fold) (message "Assumed to be at end of folded org heading line. \ If org-cycle is unwanted here. Please edit ./insert-state-tab function"))) ;; Reaching here if '(and (string= [...]) [...])' not true. ;; REVIEW: A way to fix all the excessive comments above is ;; to have a way of determining whether corfu-candidate CAP ;; could act, rather than checking with org. If ;; candidate-overlay cannot act I could just call ;; `evil-open-fold' and not bother with `org-cycle' at all. (corfu-candidate-overlay-complete-at-point) )) ;; Is it possible with this implementation to add further ;; functionality to this TAB key? I need a way to check if ;; candidate-overlay is visible. )) #+END_SRC Even though it ends up with repeated =corfu-candidate-overlay= checks, it's a cleaner and easier to maintain (and extend) implementation than the one above. #+BEGIN_SRC elisp :noweb-ref insert-state-tab-cmd :tangle no (defun ./insert-state-tab () "Handle TAB key in insert state. If corfu-candidate-overlay's overlay is active, calls `corfu-candidate-overlay--get-overlay-property', otherwise `evil-toggle-fold'. See my packages.org for this section for why I didn't use `org-cycle' here." (interactive) (if (overlayp corfu-candidate-overlay--overlay) (progn ;; This check is taken exactly from the implementation of ;; `corfu-candidate-overlay-complete-at-point's (as of ;; writing). (corfu-candidate-overlay--show) (if (and (overlayp corfu-candidate-overlay--overlay) (not (string= (corfu-candidate-overlay--get-overlay-property 'after-string) ""))) (corfu-candidate-overlay-complete-at-point) (if (string-match-p "^\*+ " (buffer-substring-no-properties (line-beginning-position) (line-end-position))) (evil-toggle-fold) (indent) ) )) (if (string-match-p "^\*+ " (buffer-substring-no-properties (line-beginning-position) (line-end-position))) (evil-toggle-fold) (indent-for-tab-command) ))) #+END_SRC * Which-Key #+BEGIN_SRC elisp (use-package which-key :diminish :config (which-key-setup-side-window-right) (which-key-mode 1)) #+END_SRC * Org #+BEGIN_SRC elisp :noweb yes (use-package org :elpaca nil :config <> ;; (dolist (item '( ;; Newline used to add prefix to tangled file ;; <>)) ;; (apply #'set-face-attribute item)) (custom-theme-set-faces 'user <> ) (org-babel-do-load-languages 'org-babel-load-languages '((emacs-lisp . t) (python . t) (lua . t) (js . t))) (defun org-babel-execute:nim (body params) "Execute a block of Nim code with org-babel." (let ((in-file (org-babel-temp-file "n" ".nim")) (verbosity (or (cdr (assq :verbosity params)) 0))) (with-temp-file in-file (insert body)) (org-babel-eval (format "nim compile --verbosity=%d --run %s" verbosity (org-babel-process-file-name in-file)) ""))) (defun org-babel-execute:moonscript (body params) "Execute a block of MoonScript code with org-babel." (let ((in-file (org-babel-temp-file "m" ".moon"))) (with-temp-file in-file (insert body)) (org-babel-eval (format "moon %s" (org-babel-process-file-name in-file)) ""))) :hook (org-mode . (lambda () (visual-line-mode 1) (variable-pitch-mode) (display-line-numbers-mode -1))) ) #+END_SRC REVIEW: Using =set-face-attribute= rather than =custom-theme-set-faces= doesn't work! Says =org-indent= invalid face... ** Org font faces #+BEGIN_SRC elisp :noweb-ref org-font-attributes :tangle no '(org-block ((t (:inherit fixed-pitch)))) '(org-code ((t (:inherit (shadow fixed-pitch))))) '(org-document-info ((t (:foreground "dark orange")))) '(org-document-info-keyword ((t (:inherit (shadow fixed-pitch))))) '(org-indent ((t (:inherit (org-hide fixed-pitch))))) '(org-link ((t (:foreground "deep sky blue" :underline t)))) '(org-meta-line ((t (:inherit (font-lock-comment-face fixed-pitch))))) '(org-property-value ((t (:inherit fixed-pitch))) t) '(org-block-begin-line ((t (:inherit (font-lock-comment-face fixed-pitch)))) t) '(org-block-end-line ((t (:inherit (font-lock-comment-face fixed-pitch)))) t) '(org-drawer ((t (:inherit fixed-pitch))) t) '(org-special-keyword ((t (:inherit (font-lock-comment-face fixed-pitch))))) '(org-table ((t (:inherit fixed-pitch)))) '(org-tag ((t (:inherit (shadow fixed-pitch) :weight bold :height 0.8)))) '(org-verbatim ((t (:inherit (shadow fixed-pitch))))) #+END_SRC ** Org config These have noweb-ref "org-config" and are put in the =:config= of =use-package= above. Enable indenting paragraphs under headings by default #+BEGIN_SRC elisp :noweb-ref org-config :tangle no (setq org-startup-indented t) #+END_SRC Don't indent stuff in SRC. They show up on exports and when viewers copy the entire SRC block the indents are also copied! (defaulted to 2 spaces). #+BEGIN_SRC elisp :noweb-ref org-config :tangle no (setq org-edit-src-content-indentation 0) #+END_SRC Indent sub-list items #+BEGIN_SRC elisp :noweb-ref org-config :tangle no (setq org-list-indent-offset 2) #+END_SRC ** Org superstar Org superstar is like org-bullets but with additional customizations as well as styling plain lists #+BEGIN_SRC elisp (use-package org-superstar :config (setq org-superstar-configure-like-org-bullets t) :hook (org-mode . (lambda () (org-superstar-mode 1)))) #+END_SRC ** Org auto tangle *Especially* useful for my literate emacs config. #+BEGIN_SRC elisp (use-package org-auto-tangle :defer t :hook (org-mode . org-auto-tangle-mode) :config (setq org-auto-tangle-babel-safelist '( "~/.config/emacs/packages.org" "~/.config/emacs/init.org"))) #+END_SRC * Eglot & tree sitter ** Some languages #+BEGIN_SRC elisp (use-package lua-mode) (use-package moonscript) (use-package nim-mode) #+END_SRC ** Eglot Eglot is now included in Emacs from version 29. #+BEGIN_SRC elisp (use-package eglot :elpaca nil :defer t :hook ((python-ts-mode go-ts-mode lua-mode) . eglot-ensure) ) #+END_SRC Tree-sitter as well, but you must manually clone the treesitter repo and =./build= for each language, the copy the output file to =/tree-sitter/= - Clone https://github.com/casouri/tree-sitter-module - Run =./build = - Copy the file in =./dist/= to =/tree-sitter/= I wrote a patch for =./build= to have it automatically copy the resulting file into where I want: #+BEGIN_SRC diff :tangle no diff --git a/build.sh b/build.sh index 25b5c1e..75a01b3 100755 --- a/build.sh +++ b/build.sh @@ -5,6 +5,7 @@ set -e lang=$1 topdir="$PWD" +destdir=$2 if [ "$(uname)" == "Darwin" ] then @@ -151,3 +152,7 @@ mkdir -p "${topdir}/dist" cp "libtree-sitter-${lang}.${soext}" "${topdir}/dist" cd "${topdir}" rm -rf "${lang}" + +if [ -n $destdir ]; then + mv "$topdir/dist/libtree-sitter-$lang.$soext" $destdir/ +fi #+END_SRC After applying this patch (you can save the file as =add-destdir.patch=, then run =git apply add-destdir.patch= from within the cloned repo), you can then use =./build.sh python ../tree-sitter=, which would build the tree sitter module for python, then copy the result into =../tree-sitter=. This is if your =tree-sitter= repo is cloned at =/tree-sitter-repo= for example. Configuration below enables tree-sitter mode for each major language mode I want to have tree-sitter for. #+BEGIN_SRC elisp ;; Open python files in tree-sitter mode. (add-to-list 'major-mode-remap-alist '(python-mode . python-ts-mode)) (add-to-list 'major-mode-remap-alist '(c++-mode . c++-ts-mode)) (add-to-list 'auto-mode-alist '("\\.go\\'" . (lambda () (go-ts-mode) ))) (add-to-list 'auto-mode-alist '("go.mod\\'" . (lambda () (go-mod-ts-mode) ))) #+END_SRC * Diminish Diminish allows us to use minor modes without showing it. Calling =diminish= to specify the mode to hide, (or specify 2nd argument for the alternative display text). #+BEGIN_SRC elisp ;; FIXME (use-package diminish :config (diminish 'buffer-face-mode) (diminish 'org-auto-tangle-mode) (diminish 'eldoc-mode) (diminish 'auto-revert-mode) (diminish 'visual-line-mode) (diminish 'org-indent-mode) (diminish 'subword-mode) ) #+END_SRC * Eshell Significant portions of this section is credited to: https://github.com/howardabrams/hamacs/blob/main/ha-eshell.org ** Opening files #+begin_src elisp (defun ./eshell-fn-on-files (fun1 fun2 args) "Call FUN1 on the first element in list, ARGS. Call FUN2 on all the rest of the elements in ARGS." (unless (null args) (let ((filenames (flatten-list args))) (funcall fun1 (car filenames)) (when (cdr filenames) (mapcar fun2 (cdr filenames)))) ;; Return an empty string, as the return value from `fun1' ;; probably isn't helpful to display in the `eshell' window. "")) #+end_src #+begin_src elisp (defun eshell/ff (&rest files) "find-file on first arg, find-file-other-window on rest" (./eshell-fn-on-files 'find-file 'find-file-other-window files)) (defun eshell/f (&rest files) "Edit one or more files in another window." (./eshell-fn-on-files 'find-file-other-window 'find-file-other-window files)) #+end_src In case I somehow end up in (n)vi(m), I can possibly use my vim's q to quit, but still. Oh yeah oopsie doopsie if I end up in nvim, since my leader there is SPC, same as doom emacs... Oh Noes! #+begin_src elisp (defalias 'eshell/emacs 'eshell/ff) (defalias 'eshell/vi 'eshell/ff) (defalias 'eshell/vim 'eshell/ff) (defalias 'eshell/nv 'eshell/ff) (defalias 'eshell/nvim 'eshell/ff) #+end_src #+begin_src elisp (defun eshell/less (&rest files) "view-file-other-window" (view-file-other-window files)) (defalias 'eshell/more 'eshell/less) #+end_src ** Aliases Some aliases >>> =eshell-aliases-file= #+begin_src shell :tangle ~/.config/emacs/eshell/alias alias ll exa -lahg --git -t modified alias clr clear 1 alias x exit alias d dired $1 #+end_src Kill window on exit https://stackoverflow.com/questions/51867693/emacs-eshell-kill-window-on-exit#51867960 #+begin_src elisp (defun ./eshell-exit-with-window () (when (not (one-window-p)) (delete-window))) (advice-add 'eshell-life-is-too-much :after './eshell-exit-with-window) #+end_src ** Useful functions #+begin_src elisp (defun eshell/do (&rest args) "Execute a command sequence over a collection of file elements. Separate the sequence and the elements with a `::' string. For instance: do chown _ angela :: *.org(u'oscar') The function substitutes the `_' sequence to a single filename element, and if not specified, it appends the file name to the command. So the following works as expected: do chmod a+x :: *.org" (seq-let (forms elements) (-split-on "::" args) (dolist (element (-flatten (-concat elements))) (message "Working on %s ... %s" element forms) (let* ((form (if (-contains? forms "_") (-replace "_" element forms) (-snoc forms element))) (cmd (car form)) (args (cdr form))) (eshell-named-command cmd args))))) #+end_src Clog up our M-x #+begin_src elisp (defun ./eshell--buffer-from-dir (dir) "Return buffer name of an Eshell based on DIR." (format "*eshell: %s*" (thread-first dir (split-string "/" t) (last) (car)))) (defun ./eshell-there (parent) "Open an eshell session in a PARENT directory. The window is smaller and named after this directory. If an Eshell is already present that has been named after PARENT, pop to that buffer instead." (if-let* ((term-name (./eshell--buffer-from-dir parent)) (buf-name (seq-contains (buffer-list) term-name (lambda (a b) (string-equal (buffer-name b) a))))) (pop-to-buffer buf-name) (let* ((default-directory parent) (height (/ (window-total-height) 3))) (split-window-vertically (- height)) (other-window 1) (setq eshell-buffer-name term-name) (eshell)))) (defun ./eshell-here () "Opens a new shell in the directory of the current buffer. Renames the eshell buffer to match that directory to allow more than one eshell window." (interactive) (./eshell-there (if (buffer-file-name) (file-name-directory (buffer-file-name)) default-directory))) (bind-key "C-`" './eshell-here) (defun ./eshell-send (command &optional dir) "Send COMMAND to the Eshell buffer named with DIR. The Eshell may have moved away from the directory originally opened with DIR, but it should have the name of the buffer. See `eshell--buffer-from-dir'." (interactive "sCommand to Send: ") (unless dir (setq dir (projectile-project-root))) (save-window-excursion (eshell-there dir) (goto-char (point-max)) (insert command) (eshell-send-input))) #+end_src #+begin_src elisp (defun ./execute-command-on-file-buffer (cmd) "Executes a shell command, CMD, on the current buffer's file. Appends the filename to the command if not specified, so: chmod a+x Works as expected. We replace the special variable `$$' with the filename of the buffer. Note that `eshell-command' executes this command, so eshell modifiers are available, for instance: mv $$ $$(:r).txt Will rename the current file to now have a .txt extension. See `eshell-display-modifier-help' for details on that." (interactive "sExecute command on File Buffer: ") (let* ((file-name (buffer-file-name)) (full-cmd (cond ((string-match (rx "$$") cmd) (replace-regexp-in-string (rx "$$") file-name cmd)) ((and file-name (string-match (rx (literal file-name)) cmd)) cmd) (t (concat cmd " " file-name))))) (message "Executing: %s" full-cmd) (eshell-command full-cmd))) #+end_src ** Use package - eshell settings #+begin_src elisp (use-package eshell :elpaca nil :init (setq eshell-error-if-no-glob t ;; This jumps back to the prompt: eshell-scroll-to-bottom-on-input 'all eshell-hist-ignoredups t eshell-save-history-on-exit t ;; Since eshell starts fast, let's dismiss it on exit: eshell-kill-on-exit t eshell-destroy-buffer-when-process-dies t ;; Parameter differences could be hard to remember. Maybe next time eshell-prefer-lisp-functions nil)) #+end_src ** EAT #+begin_src elisp (use-package eat :config (define-key eat-mode-map (kbd "C-c C-d") #'eat-self-input) ;; :hook ;; (eshell-mode . #'eat-eshell-mode) ) #+end_src #+begin_src elisp (provide 'packages) #+end_src