My Emacs configuration
Table of Contents
- 1. Emacs stuff
- 1.1. early-init.el
- 1.2. Preamble
- 1.3. straight
- 1.4. org
- 1.5. Misc
- 1.6. Minibuffer
- 1.7. Completions
- 1.8. Window management
- 1.9. Text editing
- 1.9.1. Misc
- 1.9.2. Auto-saving
- 1.9.3. imenu
- 1.9.4. Filling
- 1.9.5. Transpose
- 1.9.6. Narrow to what I mean
- 1.9.7. White spaces
- 1.9.8. Version Control
- 1.9.9. Auto insert mode
- 1.9.10. DIRED
- 1.9.11. Project
- 1.9.12. Scratchpads
- 1.9.13. Occur & loccur
- 1.9.14. hideshow
- 1.9.15. Smartparens
- 1.9.16. Flymake
- 1.9.17. Flyspell and friends
- 1.9.18. Typo(graphical stuff)
- 1.9.19. hippie expand
- 1.9.20. isearch
- 1.9.21. etags
- 1.9.22. view mode
- 1.9.23. pdf-tools
- 1.9.24. avy
- 1.9.25. iedit
- 1.9.26. editorconfig
- 1.9.27. Compilation
- 1.9.28. Languages
- 1.9.28.1. jump to matching paren
- 1.9.28.2. eglot
- 1.9.28.3. prog-mode
- 1.9.28.4. text-mode
- 1.9.28.5. diff
- 1.9.28.6. elisp
- 1.9.28.7. Common LISP
- 1.9.28.8. Clojure
- 1.9.28.9. Scheme
- 1.9.28.10. Elastic search mode
- 1.9.28.11. SQL
- 1.9.28.12. nxml
- 1.9.28.13. web
- 1.9.28.14. CSS
- 1.9.28.15. javascript
- 1.9.28.16. C
- 1.9.28.17. Go
- 1.9.28.18. Perl
- 1.9.28.19. Python
- 1.9.28.20. sh-mode
- 1.9.28.21. Lua
- 1.9.28.22. GDScript
- 1.9.28.23. YAML
- 1.9.28.24. TOML
- 1.9.28.25. Gemini (text/gemini)
- 1.9.28.26. Markdown
- 1.10. Applications
- 1.10.1. bins
- 1.10.2. sam for the rescue!
- 1.10.3. eshell
- 1.10.4. vterm ― when eshell isn't enough
- 1.10.5. ibuffer
- 1.10.6. help
- 1.10.7. gdb
- 1.10.8. Literate calc-mode
- 1.10.9. keycast
- 1.10.10. email
- 1.10.11. news
- 1.10.12. pass
- 1.10.13. vmd
- 1.10.14. sndio
- 1.10.15. EMMS
- 1.10.16. eww
- 1.10.17. exwm
- 1.10.18. elpher
- 1.10.19. pq
- 1.10.20. nov.el
- 1.10.21. Toxe
- 1.10.22. rcirc
- 1.10.23. circe
- 1.10.24. telega
- 1.10.25. elfeed
- 1.10.26. firefox integration
- 1.11. "smol" packages
Table of Contents
- 1. Emacs stuff
- 1.1. early-init.el
- 1.2. Preamble
- 1.3. straight
- 1.4. org
- 1.5. Misc
- 1.6. Minibuffer
- 1.7. Completions
- 1.8. Window management
- 1.9. Text editing
- 1.9.1. Misc
- 1.9.2. Auto-saving
- 1.9.3. imenu
- 1.9.4. Filling
- 1.9.5. Transpose
- 1.9.6. Narrow to what I mean
- 1.9.7. White spaces
- 1.9.8. Version Control
- 1.9.9. Auto insert mode
- 1.9.10. DIRED
- 1.9.11. Project
- 1.9.12. Scratchpads
- 1.9.13. Occur & loccur
- 1.9.14. hideshow
- 1.9.15. Smartparens
- 1.9.16. Flymake
- 1.9.17. Flyspell and friends
- 1.9.18. Typo(graphical stuff)
- 1.9.19. hippie expand
- 1.9.20. isearch
- 1.9.21. etags
- 1.9.22. view mode
- 1.9.23. pdf-tools
- 1.9.24. avy
- 1.9.25. iedit
- 1.9.26. editorconfig
- 1.9.27. Compilation
- 1.9.28. Languages
- 1.9.28.1. jump to matching paren
- 1.9.28.2. eglot
- 1.9.28.3. prog-mode
- 1.9.28.4. text-mode
- 1.9.28.5. diff
- 1.9.28.6. elisp
- 1.9.28.7. Common LISP
- 1.9.28.8. Clojure
- 1.9.28.9. Scheme
- 1.9.28.10. Elastic search mode
- 1.9.28.11. SQL
- 1.9.28.12. nxml
- 1.9.28.13. web
- 1.9.28.14. CSS
- 1.9.28.15. javascript
- 1.9.28.16. C
- 1.9.28.17. Go
- 1.9.28.18. Perl
- 1.9.28.19. Python
- 1.9.28.20. sh-mode
- 1.9.28.21. Lua
- 1.9.28.22. GDScript
- 1.9.28.23. YAML
- 1.9.28.24. TOML
- 1.9.28.25. Gemini (text/gemini)
- 1.9.28.26. Markdown
- 1.10. Applications
- 1.10.1. bins
- 1.10.2. sam for the rescue!
- 1.10.3. eshell
- 1.10.4. vterm ― when eshell isn't enough
- 1.10.5. ibuffer
- 1.10.6. help
- 1.10.7. gdb
- 1.10.8. Literate calc-mode
- 1.10.9. keycast
- 1.10.10. email
- 1.10.11. news
- 1.10.12. pass
- 1.10.13. vmd
- 1.10.14. sndio
- 1.10.15. EMMS
- 1.10.16. eww
- 1.10.17. exwm
- 1.10.18. elpher
- 1.10.19. pq
- 1.10.20. nov.el
- 1.10.21. Toxe
- 1.10.22. rcirc
- 1.10.23. circe
- 1.10.24. telega
- 1.10.25. elfeed
- 1.10.26. firefox integration
- 1.11. "smol" packages
1. Emacs stuff
Here be dragons.
1.1. early-init.el
Starting from emacs 27 (I guess) there is an additional
configuration file called early-init.el
. As the name suggest,
this is called before the usual init.el
or .emacs
.
Even if when it's called there is no frame available yet, I found that there's a small boost in the startup by disabling all this cruft before it gets even rendered:
(when (fboundp 'menu-bar-mode) (menu-bar-mode -1)) (when (fboundp 'tool-bar-mode) (tool-bar-mode -1)) (when (fboundp 'scroll-bar-mode) (scroll-bar-mode -1)) (when (fboundp 'horizontal-scroll-bar-mode) (horizontal-scroll-bar-mode -1))
I also use it to add two very important paths to the the
load-path
: the lisp
directory, which holds various smallish
packages and the mu4e source code.
(add-to-list 'load-path (expand-file-name "lisp" user-emacs-directory)) (add-to-list 'load-path "/usr/local/share/emacs/site-lisp/mu4e")
Another thing do here is load my custom modeline and theme, now
that the load-path
is correctly populated.
(require 'my-modeline) (load-theme 'minimal-light t)
Even if I'll be called out for cargo culting, I've seen this suggestion in various configuration and, so far, it hasn't caused any drawbacks:
;; Resizing the Emacs frame can be a terribly expensive part of ;; changing the font. By inhibiting this, we easily halve startup ;; times with fonts that are larger than the system default. (setq frame-inhibit-implied-resize t)
The last trick is to bump the GC threshold during the initialisation. Beware: while this is usually fine, Emacs will die from OOM error if bootstrapping straight.el and fetching all the packages! So remember to disable this when moving this configuration to another place!.
(defvar op/default-gc-cons-threshold gc-cons-threshold "Backup of the default GC threshold.") (defvar op/default-gc-cons-percentage gc-cons-percentage "Backup of the default GC cons percentage.") ;; boost the gc during the load (setq gc-cons-threshold most-positive-fixnum ; 2^61 bytes gc-cons-percentage 0.6) ;; and reset it to "normal" when done (add-hook 'emacs-startup-hook (lambda () (setq gc-cons-threshold op/default-gc-cons-threshold gc-cons-percentage op/default-gc-cons-percentage)))
1.2. Preamble
;; -*- lexical-binding: t; -*- (require 'cl-lib) (message "here be dragons")
A bit of clojure-ness is always accepted! I copied the idea of a
comment
macro from there. In elisp there is a ignore
function,
but that evaluates its argument, yielding always nil, whereas
comment
doesn't evaluate its body. It's useful to temporary
disable bits of code.
(defmacro comment (&rest _body) "Ignore BODY, just like `ignore', but this is a macro." '())
One drawbacks of using a literate init file is that customisation
get lost every time it get tangled, because they're usually
appended at the end of the user-init-file
. The simplest solution
I found is to move the custom stuff in another file, so it gets
persisted:
(setq custom-file (expand-file-name "custom.el" user-emacs-directory)) (load custom-file)
$CDPATH
produce strange effects on eshell
$ echo $CDPATH
.:/home/op/w:/usr/ports:/usr/ports/mystuff:/home/op/quicklisp/local-projects
$ pwd
~
$ cd .
$ pwd
~/.emacs.d/
$ wtf?
So just disable it for the time being. It's not particularly useful inside eshell anyway
(setenv "CDPATH" nil)
1.3. straight
I'm currently using straight.el
and use-package
to manage
external packages. It makes easy to pull packages from git, so one
can do local modifications and test right away. I don't know if
it's better than other similar tools (cask, qelpa, …), it's the
first one I tried and so far I like it.
project
from ELPA instead
of using the one bundled with Emacs. Since I'm using Emacs from
the master branch it's pretty annoying. I've found that requiring
project here will avoid loading the ELPA one.
(require 'project)
(defvar bootstrap-version) (defvar straight-use-package-by-default t) (defvar straight-disable-native-compile t) (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)) (straight-use-package 'use-package)
1.4. org
One day I'll split this manegeable chunks, but today it's not that day.
(use-package org :straight nil :bind (("C-c c" . org-capture) ("C-c a" . org-agenda) ("<f7> s" . org-store-link) :map org-src-mode-map ("C-x w" . org-edit-src-exit) ("C-x C-s" . org-edit-src-exit)) :hook ((org-mode . op/org-setup)) :custom ((org-todo-keywords '((sequence "TODO" "WAITING" "|" "DONE") (sequence "IDEA" "WRITING" "|" "POSTED") (sequence "REPORT" "BUG" "KNOWCAUSE" "|" "FIXED") (sequence "|" "CANCELLED"))) (org-capture-templates '(("n" "annotate something" entry (file "~/org/personal.org") "* %? :note:\n %a") ("t" "something to do" entry (file "~/org/personal.org") "* TODO %?\n %a") ("b" "bug" entry (file "~/org/personal.org") "* REPORT %?\n %a"))) (org-adapt-indentation t) (org-ellipsis " [+]") (org-imenu-depth 4) (org-startup-folded t) (org-startup-with-inline-images t) (org-fontify-quote-and-verse-blocks t) (org-use-speed-commands t) (org-src-window-setup 'current-window) (org-directory "~/org") (org-agenda-files '("~/org")) (org-refile-use-outline-path t) (org-outline-path-complete-in-steps nil) (org-refile-targets '((nil :maxlevel . 3) (org-agenda-files :maxlevel . 3))) (org-src-fontify-natively t) (org-clock-out-remove-zero-time-clocks t) (org-clock-out-when-done t) (org-clock-auto-clock-resolution '(when-no-clock-is-running)) (org-clock-report-include-clocking-task t) (org-time-stamp-rounding-minutes '(1 1)) (org-clock-history-length 23) (org-clock-in-resume t) (org-confirm-babel-evaluate nil)) :config (require 'org-protocol) (defun op/org-setup () (hl-line-mode +1) (auto-fill-mode +1) (whitespace-mode -1) (setq-local cursor-type 'bar) (setq-local delete-trailing-lines t) (add-hook 'before-save-hook #'delete-trailing-whitespace nil t)) (org-link-set-parameters "gemini" :follow (lambda (p) (elpher-go (concat "gemini:" p))) :display 'full) (org-babel-do-load-languages 'org-babel-load-languages '((emacs-lisp . t) (C . t) (R . t) (sql . t) (lisp . t) (shell . t) (sqlite . t) (python . t) (gnuplot . t))) (setq org-babel-lisp-eval-fn #'sly-eval) <<org-roam>> <<org-tree-slide>>)
I'm having some problems with org, in particular C-c C-e ...
doesn't export. Probably it's because I'm ending up with org
from Emacs and not from straight, or something like that. This
seems to fix the problem, but I'd like to avoid this workaround
(add-hook 'after-init-hook #'org-reload)
Org uses htmlize to prettify the code when exporting:
(use-package htmlize)
To fix some "alignment" problem with unicode characters in tables
(but not also) there is a valign
package!
(use-package valign :straight (:type git :host github :repo "casouri/valign") :defer t :hook ((org-mode . valign-mode)) :custom ((valign-fancy-bar t)))
1.4.0.1. TODO wasn't valign included into ELPA?
1.4.1. org-roam
;; NOTE: needs sqlite3 (use-package org-roam :init (setq org-roam-v2-ack t) ; yeah, I know I'm on v2 :custom ((org-roam-directory "~/org-roam")) :hook ((after-init . org-roam-setup)) :bind (("C-z r l" . org-roam-buffer-toggle) ("C-z r f" . org-roam-node-find) ("C-z r i" . org-roam-node-insert)) :config (comment (make-directory org-roam-directory)))
1.4.2. Presentations in org-mode
(use-package org-tree-slide :custom ((org-image-actual-width nil)) :config (defun op/org-present-frame () (let ((frame (make-frame `( ;(minibuffer . nil) (title . "Presentation") (menu-bar-lines . 0) (tool-bar-lines . 0) (vertical-scroll-bars . nil) (left-fringe . 0) (right-fringe . 0) (internal-border-width . 10) ;(cursor-type . nil) )))) (select-frame-set-input-focus frame) (toggle-frame-fullscreen) (raise-frame frame) frame)) (defun op/org-present () (interactive) (let ((name "*presentazione*")) (ignore-errors (kill-buffer name)) ;; (with-current-buffer (make-indirect-buffer (current-buffer) ;; name)) (op/org-present-frame) (org-display-inline-images) (olivetti-mode) (olivetti-set-width 90) (call-interactively #'org-tree-slide-mode) (text-scale-adjust 3))))
1.4.3. Org publish
Org publish is a library that allows to generate sets of documents
from a directory tree. It provides some basic mechanisms to copy
files around, converting org files to other formats (HTML for
instance). I know some people use it to generate static websites,
I'm using it to publish my dots
repo on the web (and soon on
Gemini!)
The variable org-publish-project-alist
as an alist of ("name"
props...)
.
To publish org files as another file and copy files as-is, the
best way I found is to define multiple targets, one for org and
one for the copy, and require with the :components
props from
another target.
(with-eval-after-load 'org (setq org-publish-project-alist '(("dots-org" :base-directory "~/dots" :base-extension "org" :publishing-directory "~/w/blog/resources/dots/" :recursive t :publishing-function org-html-publish-to-html) ("dots-org-gmi" :base-directory "~/dots" :base-extension "org" :publishing-directory "~/w/blog/resources/dots/" :recursive t :publishing-function org-gemini-publish-to-gemini) ("dots-static" :base-directory "~/dots" :base-extension "css\\|png\\|jpg\\|jpeg" :publishing-directory "~/w/blog/resources/dots/" :recursive t :publishing-function org-publish-attachment) ("dots" :components ("dots-org" "dots-org-gmi" "dots-static")))) (define-key global-map (kbd "C-z p p") #'org-publish) (define-key global-map (kbd "C-z p P") #'org-publish-all))
1.5. Misc
The following are some misc customizations. They can't be split in
their own blocks, either because are variables defined in C or are
defined in lisp files that we can't require
. Either the way,
it's probably self-explanatory.
(use-package emacs :straight nil :custom ((use-dialog-box nil) (x-stretch-cursor t) (sentence-end-double-space t) (require-final-newline t) (visible-bell nil) (load-prefer-newer t)) :bind (("M-z" . zap-up-to-char)) :config ;; free the C-z key (define-key global-map (kbd "C-z") nil) ;; these becomes buffer-local when set (setq-default scroll-up-aggressively 0.0 scroll-down-aggressively 0.0 scroll-preserve-screen-position t next-screen-context-lines 1) ;; fix hangs due to pasting from xorg -- workaround, not a solution :/ (setq x-selection-timeout 1) (add-hook 'after-make-frame-functions (lambda (_frame) (setq x-selection-timeout 1))) (fset 'yes-or-no-p 'y-or-n-p))
I'm using a custom keyboard layout, where the numbers are actually
symbols, and to type numbers I have to hold shift. Normally, this
is not a problem, I type symbols more frequently than numbers
anyway, but it's handy to have a quick shortcut for C-u 0
,
instead of doing C-u s-!
or C-s-!
(0 is s-!
here).
Introducing C-!
(defun op/digit-argument-zero () "Like `digit-argument', but set the arg to 0 unconditionally." (interactive) (prefix-command-preserve-state) (setq prefix-arg 0)) (define-key global-map (kbd "C-!") #'op/digit-argument-zero)
I always end up trying to execute unload-theme
instead of
disable-theme
when I want to get rid of a theme. If to load a
theme I have to M-x load-theme
, why the dual operation is
disable-theme
? Who knows, but I'll keep the alias.
(defalias 'unload-theme #'disable-theme)
Pasting from the primary selection is handy in various situations,
but having to press mouse2
with a surgical-precision is not
something I like. Taken from a conversation with cage, here's a
better way:
(defun paste-at-point () (interactive) (insert (gui-get-primary-selection))) (define-key global-map (kbd "<mouse-2>") #'paste-at-point) (define-key global-map (kbd "S-<insert>") #'paste-at-point)
1.5.1. Font
I discovered this font thanks to a submission on the ports@ mailing list. I'm just trying it for now, I'm not sure if I really like it.
I'm trying iosevka again. Mononoki is cool, but I like fonts that takes as little horizontal space as possible, and iosevka seems tiny, yet readable.
(let ((font "Iosevka Term Curly Medium 9")) (add-to-list 'default-frame-alist `(font . ,font)) (set-face-attribute 'default t :font font :height 100) (set-face-attribute 'default nil :font font :height 100) (set-frame-font font nil t))
Also, I'd like emojis to be rendered…
(set-fontset-font "fontset-default" 'unicode "Noto Emoji" nil 'prepend)
1.5.2. tab-bar
I initially thought I would never used the tab-bar
, but now here
we are. How ironic. Anyway, please don't show the tab-bar when
there is only one tab:
(setq tab-bar-show 1)
1.5.3. bookmarks
Emacs lets one keep bookmarks on various places (usually files) to quickly jump around.
(use-package bookmark :straight nil :bind (("C-z b b" . bookmark-jump) ("C-z b a" . bookmark-set) ("C-z b l" . list-bookmarks)))
1.5.4. save the place
save-place-mode
remembers the position of the point in a buffer
and, when re-opening it, restores the point. I don't know how it
handles the fact that a buffer can be viewed in different window,
each one with its point, but anyway it seems handy.
(use-package saveplace :straight nil :config (save-place-mode 1))
1.5.5. history
savehist
is similar to saveplace
, but save history. I don't
know exactly what histories it saves, but when it doubt, save it!
(use-package savehist :straight nil :config (savehist-mode))
1.5.6. Uniquify
Buffer names must be unique. This package permits to tweak the rules that Emacs uses to uniquify those names. The following seems pretty handy, especially wrt project structures like Clojure
(use-package uniquify :straight nil :custom ((uniquify-buffer-name-style 'forward) (uniquify-strip-common-suffix t)))
1.5.7. Hydra
I use hydra for various thing, hence why it's in the "misc" section.
These are some general hydras that I find useful. They are used mostly to quickly "repeat" the last command.
(use-package hydra :config (defhydra hydra-windowsize (global-map "C-x") ("{" shrink-window-horizontally) ("}" enlarge-window-horizontally)) (defhydra hydra-grep-like (global-map "M-g") ("n" next-error "next") ("p" previous-error "prev") ("RET" nil :exit t) ("C-l" recenter-top-bottom) ("q" nil :exit t)) (defhydra hydra-other-window (global-map "C-x") ("o" other-window "next window") ("O" (other-window -1) "previous window")) (hydra-set-property 'hydra-other-window :verbosity 0) (defhydra hydra-other-tab (global-map "C-x t") ("o" tab-next) ("O" tab-previous) ("q" nil :exit t)) (hydra-set-property 'hydra-other-tab :verbosity 0))
1.5.8. desktop.el
The desktop package saves and restore the emacs session. This is
especially useful when using the emacs daemon. Truth to be told,
I'm thinking of getting rid of this in favour of something like
recentf
.
desktop.el
in favour of recentf
,
let's see how it goes!
(use-package desktop :straight nil :hook ((after-init . desktop-read) (after-init . desktop-save-mode)) :custom ((desktop-base-file-name ".desktop") (desktop-base-lock-name ".desktop.lock") (desktop-restore-eager 8) (desktop-restore-frames nil)))
1.5.9. recentf
(require 'recentf) (recentf-mode t) (setq recentf-max-saved-items 80) (defun op/find-recentf (file) "Use `completing-read' to open a recent FILE." (interactive (list (completing-read "Find recent file: " recentf-list))) (when file (find-file file))) (define-key global-map (kbd "C-x C-r") #'op/find-recentf)
1.5.10. Gemini for thingatpoint
I don't exactly remember why, but this should enable the
gemini://
scheme in some kind of buffers.
(use-package thingatpt :config (add-to-list 'thing-at-point-uri-schemes "gemini://"))
1.5.11. browse-url
Browse URLs, and add Gemini support.
(use-package browse-url :bind ("<f9>" . browse-url) :config (add-to-list 'browse-url-default-handlers '("\\`gemini:" . op/browse-url-elpher)) (defun op/browse-url-elpher (url &rest _args) "Open URL with `elpher-go'." (elpher-go url)))
1.5.12. variable pitch mode (aka non monospace)
I like to use variable-pitch-mode
in some text buffers (org and
gemini usually), but sometimes I'd like a way to toggle it. While
M-x variable-pitch-mode RET
is a solution, binding a key is
faster:
(define-key global-map (kbd "C-z V") #'variable-pitch-mode)
1.5.13. form-feed
The form-feed
ASCII character (0x0C or 12) was used to signal
the end of the page. It's still used (albeit not that frequently)
in code to divide a file into logical "pages".
The form-feed
packages changes how these ^L
characters are
rendered, it turns them into a line spanning the entire window
width.
(use-package form-feed :config (global-form-feed-mode))
1.6. Minibuffer
all hail the minibuffer
This allows to launch a command that uses the minibuffer while already inside the minibuffer.
(setq enable-recursive-minibuffers t)
I'm generally pretty lazy, so why pressing shift to get the case right?
(setq completion-ignore-case t
read-file-name-completion-ignore-case t
read-buffer-completion-ignore-case t)
Misc enhancement to the minibuffer behaviour.
;; add prompt inidcator to `completing-read-multiple'. (defun op/crm-indicator (args) (cons (concat "[CRM] " (car args)) (cdr args))) (advice-add #'completing-read-multiple :filter-args #'op/crm-indicator) (setq minibuffer-prompt-properties '(read-only true cursor-intangible t face minibuffer-prompt)) (add-hook 'minibuffer-setup-hook #'cursor-intangible-mode)
1.6.1. Marginalia
Enhances the minibuffer completions with additional informations
(use-package marginalia :custom (marginalia-annotators '(marginalia-annotators-heavy marginalia-annotators-light nil)) :init (marginalia-mode))
1.6.2. Orderless
Controls the sorting of the minibuffer completions. I still have to tweak it a little bit, but I'm overall happy.
(use-package orderless :custom ((completion-styles '(orderless)) (completion-category-defaults nil) (completion-category-overrides '((file (styles . (partial-completion)))))))
1.6.3. Consult
Consult enhances various command by using the minibuffer.
(use-package consult :bind (("C-c h" . consult-history) ("C-c m" . consult-mode-command) ("C-c b" . consult-bookmark) ("C-c k" . consult-kmacro) ("C-x M-:" . consult-complex-command) ("C-x b" . consult-buffer) ("C-x 4 b" . consult-buffer-other-window) ("C-x 5 b" . consult-buffer-other-frame) ("M-#" . consult-register-load) ("M-'" . consult-register-store) ("C-M-#" . consult-register) ("M-g e" . consult-compile-error) ("M-g g" . consult-goto-line) ("M-g M-g" . consult-goto-line) ("M-g o" . consult-outline) ("M-g m" . consult-mark) ("M-g k" . consult-global-mark) ("M-g i" . consult-imenu) ("M-g I" . consult-project-imenu) ("M-s f" . op/consult-find) ("M-s g" . consult-grep) ("M-s l" . consult-line) ("M-s k" . consult-keep-lines) ("M-s u" . consult-focus-lines) ("M-s e" . consult-isearch)) :custom ((register-preview-delay 0) (register-preview-function #'consult-register-format) ;; use consult to select xref locations with preview (xref-show-xrefs-function #'consult-xref) (xref-show-definitions-function #'consult-xref) (consult-narrow-key "<") (consult-project-root #'project-roots) (consult-find-args "find .") (consult-grep-args "grep --null --line-buffered --ignore-case -RIn")) :init (advice-add #'register-preview :override #'consult-register-window) :config ;; make narrowing help available in the minibuffer. (define-key consult-narrow-map (vconcat consult-narrow-key "?") #'consult-narrow-help) ;; a find-builder that works with OpenBSD' find (defun op/consult--find-builder (input) "Build command line given INPUT." (pcase-let* ((cmd (split-string-and-unquote consult-find-args)) (type (consult--find-regexp-type (car cmd))) (`(,arg . ,opts) (consult--command-split input)) (`(,re . ,hl) (funcall consult--regexp-compiler arg type))) (when re (list :command (append cmd (cdr (mapcan (lambda (x) `("-and" "-iname" ,(format "*%s*" x))) re)) opts) :highlight hl)))) (defun op/consult-find (&optional dir) (interactive "P") (let* ((prompt-dir (consult--directory-prompt "Find" dir)) (default-directory (cdr prompt-dir))) (find-file (consult--find (car prompt-dir) #'op/consult--find-builder "")))))
1.6.4. Affe
This is a new-ish package from the same author of consult and marginalia. Honestly, I still have to use it, so this is more a remainder of its existance.
(use-package affe :straight (:type git :host github :repo "minad/affe") :after orderless :custom ((affe-regexp-function #'orderless-pattern-compiler) (affe-highlight-function #'orderless-highlight-matches)))
1.6.5. Vertico
Vertico is just like selectrum or icomplete-vertical. It's written by the same author of consult, so at this point I thought of keeping the streak and using this
M-x
halts
emacs for like 3-4 seconds before any UI show up. I should spend
some time profiling it, but for the time being switch back to
Selectrum.
giving it another try
(use-package vertico :config (vertico-mode))
1.6.6. Selectrum
I'm gonna give vertico another try, this is not tangled anymore.
(use-package selectrum :custom ((selectrum-highlight-candidates-function #'orderless-highlight-matches) (orderless-skip-highlighting (lambda () selectrum-is-active))) :config (selectrum-mode +1) <<selectrum-embark>>)
Unlike vertico, selectrum needs something more to integrate with embark. This is taken from the Embark wiki:
(defun op/refresh-selectrum () (setq selectrum--previous-input-string nil)) (add-hook 'embark-pre-action-hook #'op/refresh-selectrum)
1.6.7. embark
Embark provides custom actions on the minibuffer (technically everywhere, but I only use it in the minibuffer.)
embark-become
is a command I should use more. It provides a way
to "change" the minibuffer while retaining the input. For
instance, I often do C-x b <something>
just to see that I
haven't a buffer, and then C-x C-f
to open it. With
embark-become
I can transform the switch-buffer
command to
the find-file
command without the abort C-g
in between and
retain the input.
(use-package embark :straight (:type git :host github :repo "oantolin/embark") :bind (("C-." . embark-act) :map minibuffer-local-completion-map ("M-t" . embark-act) ("M-h" . embark-become) :map minibuffer-local-map ("M-t" . embark-act) ("M-h" . embark-become)))
1.7. Completions
I'm trying corfu at the moment. It has still some bugs for me, but I haven't found a way to reproduce, so I can't report them.
(use-package corfu :custom (corfu-cycle t) :config (corfu-global-mode +1))
1.8. Window management
This is a bit topic for me, and the only thing that I'm not completely happy with. Fortunately, as time goes, I'm less annoyed with it, bit by bit.
1.8.1. The window package
This does a lot of stuff, from the split logic to customising the thresholds. One of these days I'll split in multiple pieces.
(use-package window :straight nil :bind (("C-x +" . balance-windows-area)) :custom ((window-combination-resize t) (even-window-sizes 'heigth-only) (window-sides-vertical nil) (switch-to-buffer-in-dedicated-window 'pop) (split-height-threshold 160) (split-width-threshold 110) (split-window-preferred-function #'op/split-window-sensibly)) :config (defun op/split-window-prefer-horizontal (&optional window) "Based on `split-window-sensibly', but designed to prefer a horizontal split. It prefers windows tiled side-by-side. Taken from emacs.stackexchange.com. Optional argument WINDOW is the current window." (let ((window (or window (select-window)))) (or (and (window-splittable-p window t) ;; split window horizontally (with-selected-window window (split-window-right)))) (and (window-splittable-p window) ;; split window vertically (with-selected-window window (split-window-below))) (and ;; if window is the only usable window on its frame and is not ;; the minibuffer window, try to split it horizontally ;; disregarding the value of `split-height-threshold'. (let ((frame (window-frame window))) (or (eq window (frame-root-window frame)) (catch 'done (walk-window-tree (lambda (w) (unless (or (eq w window) (window-dedicated-p w)) (throw 'done nil))) frame) t))) (not (window-minibuffer-p window)) (let ((split-width-threshold 0)) (when (window-splittable-p window t) (with-selected-window window (split-window-right))))))) (defun op/split-window-sensibly (&optional window) "Splitting window function. Intended to use as `split-window-preferred-function'. Also taken from stackexchange with edits. Optional argument WINDOW is the window." (let ((window (or window (selected-window)))) (with-selected-window window (if (> (window-total-width window) (* 2 (window-total-width window))) (op/split-window-sensibly window) (split-window-sensibly window))))))
1.8.2. Placement with shackle
Shackle is an easy way to customise the display rules for windows
rather than messing up with display-buffer-alist
.
(use-package shackle :custom ((shackle-rules (let ((repls "\\*\\(cider-repl\\|sly-mrepl\\|ielm\\)") (godot "\\*godot - .*\\*") (vcs "\\*\\(Flymake\\|Package-Lint\\|vc-\\(git\\|got\\) :\\).*") (elfeed "\\*elfeed-entry\\*") (vmd "\\*vmd console .*")) `((compilation-mode :noselect t :align above :size 0.2) ("*Async Shell Command*" :ignore t) (,repls :regexp t :align below :size 0.3) (,godot :regexp t :align t :size 0.3) (occur-mode :select t :align right :size 0.3) (diff-mode :select t) (help-mode :select t :align left :size 0.3) (,vcs :regexp t :align above :size 0.15 :select t) (,elfeed :regexp t :align t :select t :size 0.75) (,vmd :regexp t :align below :select t :size 0.3)))) (shackle-default-rule nil ; '(:inhibit-window-quit t) )) :config (shackle-mode))
1.8.3. History
Winner saves the window placement and allows to travel back and forth in time. Also add an hydra for that for extra comfort.
(use-package winner :straight nil :config (winner-mode 1) (defhydra hydra-winner (winner-mode-map "C-c") ("<left>" (progn (winner-undo) (setq this-command 'winner-undo)) "undo") ("h" (progn (winner-undo) (setq this-command 'winner-undo)) "undo") ("<right>" winner-redo "redo") ("l" winner-redo "redo") ("q" nil :exit nil)))
1.8.4. Switch window
The builtin windmove package provides function to move between
windows in the same frame easily. Unfortunately, I don't use this
package often enough, I usually C-x o
.
(defhydra hydra-windmove (global-map "M-r") ("h" windmove-left) ("j" windmove-down) ("k" windmove-up) ("l" windmove-right) ("q" nil :exit nil)) (hydra-set-property 'hydra-windmove :verbosity 0)
1.8.5. Layouts
transpose-frame
provides various function to change the window
layout in the current frame. Since my memory is pretty limited,
an hydra is needed.
(use-package transpose-frame :bind ("C-#" . my/hydra-window/body) :commands (transpose-frame flip-frame flop-frame rotate-frame rotate-frame-clockwise rotate-frame-anti-anticlockwise) :config (defhydra hydra-window (:hint nil) " ^File/Buffer^ ^Movements^ ^Misc^ ^Transpose^ ^^^^^^^^------------------------------------------------------------------------------ _b_ switch buffer ^ ^ hjkl _0_ delete _t_ transpose frame _f_ find file _o_ other window _1_ delete other _M-f_ flip frame _s_ save conf _O_ OTHER window _2_ split below _M-C-f_ flop frame _r_ reload conf ^ ^ _3_ split right _M-s_ rotate frame ^ ^ ^ ^ _SPC_ balance _M-r_ rotate clockw. ^^^^------------------------------- _v_ split horiz. _M-C-r_ rotate anti clockw. _?_ toggle help ^ ^ _-_ split vert. ^ ^ ^ ^ _C-l_ recenter line " ("?" (hydra-set-property 'hydra-window :verbosity (if (= (hydra-get-property 'hydra-window :verbosity) 1) 0 1))) ("b" switch-to-buffer) ("f" (call-interactively #'find-file)) ("s" window-configuration-to-register) ("r" jump-to-register) ("k" windmove-up) ("j" windmove-down) ("h" windmove-left) ("l" windmove-right) ("o" (other-window 1)) ("O" (other-window -1)) ("C-l" recenter-top-bottom) ("0" delete-window) ("1" delete-other-windows) ("2" split-window-below) ("3" split-window-right) ;; v is like a |, no? ("v" split-window-horizontally) ("-" split-window-vertically) ("SPC" balance-windows) ("t" transpose-frame) ("M-f" flip-frame) ("M-C-f" flop-frame) ("M-s" rotate-frame) ("M-r" rotate-frame-clockwise) ("M-C-r" rotate-frame-anti-anticlockwise) ("q" nil :exit nil) ("RET" nil :exit nil) ("C-g" nil :exit nil)) (defun my/hydra-window/body () (interactive) (hydra-set-property 'hydra-window :verbosity 0) (hydra-window/body)))
1.8.6. Side windows
Side windows are an interesting concept. Emacs reserve an optional space at the top, bottom, left and right of the frame for these side windows. You can think of them as a dockable space, akin to the panels in IDEs.
I'm finding useful to keep an IRC buffer at the bottom of the frame, to avoid jumping from the "code" frame to the "chat" frame or switch buffers continuously.
The following functions helps achieve this:
(defun op/buffer-to-side-window (place) "Place the current buffer in the side window at PLACE." (interactive (list (intern (completing-read "Which side: " '(top left right bottom))))) (let ((buf (current-buffer))) (display-buffer-in-side-window buf `((window-height . 0.15) (side . ,place) (slot . -1) (window-parameters . ((no-delete-other-windows . t) (no-other-window t))))) (delete-window)))
See that no-other-window
? it means that the side window won't be
accessible by other-window
means (i.e. C-x o
). Which brings
us to Ace Windows.
1.8.7. Ace window
(use-package ace-window :bind (("C-z o" . ace-window)) :custom ((aw-keys '(?a ?o ?e ?u ?i ?d ?h ?t ?n ?s)) (aw-dispatch-always t) (aw-minibuffer-flag t)))
1.9. Text editing
1.9.1. Misc
Usually I don't need to waste space for a column with the line numbers, it's something that it's just not useful. Anyway, there are specific times where this is handy, so reserve a key for it.
(define-key global-map (kbd "C-z n") #'display-line-numbers-mode)
Better defaults
(define-key global-map (kbd "M-SPC") #'cycle-spacing) (define-key global-map (kbd "M-u") #'upcase-dwim) (define-key global-map (kbd "M-l") #'downcase-dwim) (define-key global-map (kbd "M-c") #'capitalize-dwim)
Scroll-lock is sometimes useful to re-read the code. The idea is
that command that usually moves the point (e.g. next-line
) now
scroll the buffer keeping the point in the same "visual"
position. I've also got a keyboard with a Scr Lk
key, so why
don't use it?
The only small annoyance is that I've bound <up>
and <down>
to
some variants of copy-from-above-command
, so revert that when
we're on scroll-lock mode.
Unfortunately, this doesn't seem to work :/
(use-package scroll-lock :straight nil :bind (:map scroll-lock-mode ("<down>" . scroll-lock-next-line) ("<up>" . scroll-lock-previous-line)))
1.9.2. Auto-saving
I have a problem with compulsive saving. I type C-x C-s
every
few keystroke to write the buffer I'm editing.
I'm trying to make emacs do that for me, so make it save early
instead of waiting me to press the combination. Normally emacs
uses an auto-save
file, but if the global minor mode
auto-save-visited-mode
is active, it actually saves the file.
(auto-save-visited-mode +1)
This is still not enough. By default it saves every 5 seconds, which is obviously wrong. Five seconds are like an eternity! I'm auto-saving every two seconds, but I'm tempted to drop to one second.
(setq auto-save-visited-interval 2)
I'm only scared of the consequences of this over TRAMP. I don't use it very often, but I guess that something to disable locally auto-save-visited-mode could be implemented.
1.9.3. imenu
Imenu is a mean of navigation in a buffer. It can act like a TOC, for instance.
Prevent stale entries by always rescan the buffer
(setq imenu-auto-rescan t)
1.9.4. Filling
This is a useful function copied from somewhere I don't remember, sorry unknown author!
It makes fill-paragraph
"toggable": M-q
once to fill, M-q
again to un-fill!
(defun op/fill-or-unfill (fn &optional justify region) "Meant to be an adviced :around `fill-paragraph'. FN is the original `fill-column'. If `last-command' is `fill-paragraph', unfill it, fill it otherwise. Inspired from a post on endless parentheses. Optional argument JUSTIFY and REGION are passed to `fill-paragraph'." (let ((fill-column (if (eq last-command 'fill-paragraph) (progn (setq this-command nil) (point-max)) fill-column))) (funcall fn justify region))) (advice-add 'fill-paragraph :around #'op/fill-or-unfill)
1.9.5. Transpose
This is an idea that I stole from prot' dotemacs. It augments the
various transpose-*
commands so they respect the region: if
(use-region-p)
then transpose the thing at the extremes of the
region, otherwise operates as usual.
(the code is somewhat different from prot, but the idea is the same)
(defmacro op/deftranspose (name scope key doc) "Macro to produce transposition functions. NAME is the function's symbol. SCOPE is the text object to operate on. Optional DOC is the function's docstring. Transposition over an active region will swap the object at mark (region beginning) with the one at point (region end). It can optionally define a key for the defined function in the `global-map' if KEY is passed. Originally from protesilaos' dotemacs." (declare (indent defun)) `(progn (defun ,name (arg) ,doc (interactive "p") (let ((x (intern (format "transpose-%s" ,scope)))) (if (use-region-p) (funcall x 0) (funcall x arg)))) ,(when key `(define-key global-map (kbd ,key) #',name)))) (op/deftranspose op/transpose-lines "lines" "C-x C-t" "Transpose lines or swap over active region.") (op/deftranspose op/transpose-paragraphs "paragraphs" "C-S-t" "Transpose paragraph or swap over active region.") (op/deftranspose op/transpose-sentences "sentences" "C-x M-t" "Transpose sentences or swap over active region.") (op/deftranspose op/transpose-sexps "sexps" "C-M-t" "Transpose sexps or swap over active region.") (op/deftranspose op/transpose-words "words" "M-t" "Transpose words or swap over active region.")
A command I have to try to use more is transpose-regions
(define-key global-map (kbd "C-x C-M-t") #'transpose-regions)
1.9.5.1. TODO cycle-region is worth a try
1.9.6. Narrow to what I mean
Narrowing is really a powerful mechanism of Emacs. It lets one show only a part of a buffer. Unfortunately, the default keys aren't that great, and there's space for a do what I mean command. The following is adapted from a post on endless parentheses.
(defun op/narrow-or-widen-dwim (p) "Widen if the buffer is narrowed, narrow-dwim otherwise. Dwim means: region, org-src-block, org-subtree or defun, whichever applies first. Narrowing to org-src-blocks actually calls `org-edit-src-code'. With prefix P, don't widen, just narrow even if buffer is already narrowed. With P being -, narrow to page instead of to defun. Taken from endless parentheses." (interactive "P") (declare (interactive-only)) (cond ((and (buffer-narrowed-p) (not p)) (widen)) ((region-active-p) (narrow-to-region (region-beginning) (region-end))) ((derived-mode-p 'org-mode) ;; `org-edit-src-code' isn't a real narrowing (cond ((ignore-errors (org-edit-src-code) t)) ((ignore-errors (org-narrow-to-block) t)) (t (org-narrow-to-subtree)))) ((eql p '-) (narrow-to-page)) (t (narrow-to-defun)))) (define-key global-map (kbd "C-c w") #'op/narrow-or-widen-dwim)
1.9.7. White spaces
Nothing bothers me more than trailing white spaces, so enable
whitespace-mode
for programming and text buffers.
Also, I like to use TAB
to trigger the completions-at-point
,
and while there customize tab behaviours.
Furthermore, use hard tabs by default; op/disable-tabs
will be
added as mode hook for buffers that needs "soft" tabs.
(use-package whitespace :straight nil :custom ((whitespace-style '(face trailing)) (backward-delete-char-untabify-method 'hungry) (tab-always-indent 'complete) (tab-width 8)) :hook ((conf-mode . op/enable-tabs) (text-mode . op/enable-tabs) (prog-mode . op/enable-tabs) (prog-mode . whitespace-mode) (text-mode . whitespace-mode)) :config (setq-default indent-tabs-mode t) (defun op/enable-tabs () "Enable `indent-tabs-mode' in the current buffer." (interactive) (setq-local indent-tabs-mode t)) (defun op/disable-tabs () "Disable `indent-tabs-mode' in the current buffer." (interactive) (setq-local indent-tabs-mode nil)) ;; TODO: remove (dolist (hook '(emacs-lisp-mode-hook)) (add-hook hook 'op/disable-tabs)))
1.9.8. Version Control
1.9.8.1. Backups
Albeit not exactly a version control system, the backup system is indeed very usefuly. By defaults backup are created alongside the original files. I don't like that, and prefer to move everything into a separate backup directory.
By the way, it's incredibly useful to keep backups. I once deleted a file, and manage to recover it because of Emacs' backups!
(defconst op/backup-dir (expand-file-name "backups" user-emacs-directory)) (unless (file-exists-p op/backup-dir) (make-directory op/backup-dir)) (setq backup-directory-alist `(("." . ,op/backup-dir)))
1.9.8.2. Log
It's handy to have auto-fill-mode
enabled while writing the
commit message inside a log-edit-mode
buffer. It saves a few
M-q
(use-package log-edit :straight nil :hook ((log-edit-mode . auto-fill-mode)))
1.9.8.3. Got
Game of Trees is a version control system written by Stefan Sperling.
Game of Trees (Got) is a version control system which prioritizes ease of use and simplicity over flexibility.
Got is still under development; it is being developed on OpenBSD and its main target audience are OpenBSD developers.
Got uses Git repositories to store versioned data. Git can be used for any functionality which has not yet been implemented in Got. It will always remain possible to work with both Got and Git on the same repository.
I'm trying to complete vc-got
, a VC backend for Got.
(use-package vc-got :straight nil :load-path "~/w/vc-got/" :defer t :init (add-to-list 'vc-handled-backends 'Got) (add-to-list 'vc-directory-exclusion-list ".got"))
1.9.9. Auto insert mode
auto-insert-mode
is an elisp library that automatically inserts
text into new buffers based on the file extension or major mode.
For instance, trying to open a .el
(Emacs LISP) file will insert
the entire GPL notice, and also other stuff. This automatic
insert can be interactive, too.
(add-hook 'after-init-hook #'auto-insert-mode)
(with-eval-after-load 'autoinsert
<<c-skeleton>>
<<go-skeleton>>
<<clojure-skeleton>>
<<perl-skeleton>>
<<svg-skeleton>>)
I prefer the ISC license, and tend to use that for almost all the C I write:
(define-auto-insert '("\\.c\\'" . "C skeleton") '("Description: " "/*" \n > "* Copyright (c) " (format-time-string "%Y") " " user-full-name " <" user-mail-address ">" \n > "*" \n > "* Permission to use, copy, modify, and distribute this software for any" \n > "* purpose with or without fee is hereby granted, provided that the above" \n > "* copyright notice and this permission notice appear in all copies." \n > "*" \n > "* THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES" \n > "* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF" \n > "* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR" \n > "* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES" \n > "* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN" \n > "* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF" \n > "* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE." \n > "*/" \n \n > _ \n \n))
I added a skeleton for go files:
(define-auto-insert '("\\.go\\'" . "Go skeleton") '("Short description: " "package " (completing-read "Go package: " `("main" ,(file-name-nondirectory (directory-file-name default-directory)))) \n \n > _ \n))
The clojure skeleton inserts the correct ns
form at the top of
the buffer:
(defun op/cloj-ns () "Return the clojure namespace (as string) for the current file. Stolen from the ``ns'' yasnippet from yasnippet-snippets." (cl-flet ((try-src-prefix (path src-prfx) (let ((parts (split-string path src-prfx))) (when (= (length parts) 2) (cadr parts))))) (let* ((p (buffer-file-name)) (p2 (cl-first (cl-remove-if-not #'identity (mapcar (lambda (prfx) (try-src-prefix p prfx)) '("/src/cljs/" "/src/cljc/" "/src/clj/" "/src/" "/test/"))))) (p3 (file-name-sans-extension p2)) (p4 (mapconcat #'identity (split-string p3 "/") "."))) (replace-regexp-in-string "_" "-" p4)))) (define-auto-insert '("\\.\\(clj\\|cljs\\|cljc\\)\\'" . "Clojure skeleton") '("Short description: " "(ns " (op/cloj-ns) ")" \n \n > _ \n))
(define-auto-insert '("\\.pl\\'" . "Perl skeleton") '("Name: " "#!/usr/bin/env perl" \n "#" \n "# Copyright (c) " (format-time-string "%Y") " " user-full-name " <" user-mail-address ">" \n "#" \n "# Permission to use, copy, modify, and distribute this software for any" \n "# purpose with or without fee is hereby granted, provided that the above" \n "# copyright notice and this permission notice appear in all copies." \n "#" \n "# THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES" \n "# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF" \n "# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR" \n "# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES" \n "# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN" \n "# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF" \n "# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE." \n \n "use v5.10;" \n "use strict;" \n "use warnings;" \n \n _ \n \n "__END__" "\n\n" "=head1 NAME" "\n\n" str "\n\n" "=head1 SYNOPSIS" "\n\n\n" "=head1 DESCRIPTION" "\n\n\n" "=cut" "\n"))
I'm also writing some small SVGs in Emacs, and I keep forgetting
the right xmlns
…
(define-auto-insert '("\\.svg\\'" . "SVG Skeleton") '("Name: " "<svg xmlns=\"http://www.w3.org/2000/svg\"" \n " version=\"1.1\"" \n " width=\"\"" \n " height=\"\">" " " _ \n "</svg>"))
1.9.10. DIRED
By default dired will show, other than the files, also various
other data about every file (like owner, permissions, …) in a
format similar to ls -lah
. This is indeed useful, but usually I
don't need to see all that informations, and they steal precious
space, hence dired-hide-details-mode
.
In the same spite, most of the time I'm not interested in certain
kinds of files (like object files or similar garbage), so hide
them too by default with dired-omit-mode
.
Finally, wdired
is awesome, reserve a key for it!
(use-package dired :straight nil :hook ((dired-mode . dired-hide-details-mode) (dired-mode . dired-omit-mode)) :bind (:map dired-mode-map ("C-c w" . wdired-change-to-wdired-mode)) :config (require 'dired-x) (setq dired-listing-switches "-lahF" dired-dwim-target t dired-deletion-confirmer 'y-or-n-p dired-omit-files "\\`[.]?#\\|\\`[.][.]?\\'\\|*\\.o\\`\\|*\\.log\\`"))
1.9.11. Project
(with-eval-after-load 'project
<<project-try-local>>)
This is a bulit-in package to manage "projects" (that is, directory trees commonly called "projects")
It provides various commands that operate on the project, like
project-find-file
and project-query-replace-regexp
.
By default a project is something that is managed by a VCS, such
as git
. However, sometimes is useful to mark something as a
project without actually create a repo for it. This code, adapted
from something that I found online I don't remember where, adds
another implementation for the project backend that consider a
project something that has a .project
file.
(defun op/project-try-local (dir) "Determine if DIR is a local project. DIR must include a .project file to be considered a project." (when-let (root (locate-dominating-file dir ".project")) (cons 'local root))) (add-to-list 'project-find-functions #'op/project-try-local) (cl-defmethod project-root ((project (head local))) (cdr project))
1.9.11.1. TODO add some mechanism to ignore files
1.9.12. Scratchpads
Scratchpads are useful. I wrote a small package to create custom
scratchpads on-the-fly. By default it creates a *scratch*<n>
buffer in the current major-mode
, but the starting mode can be
chosen by invoking scratchpad-new-scratchpad
with a prefix
argument.
(use-package scratchpads :bind ("C-z s" . scratchpads-new-scratchpad) :straight nil)
1.9.13. Occur & loccur
Occur is a grep-like functionality for Emacs. It populates the
*occur*
buffer with the lines matching a certain regexp in the
current buffer. It's super-useful.
(use-package replace :straight nil :bind (("C-c o" . occur)))
loccur
is similar, but instead of using a separate buffer, it
visually hides all the non-matching lines, also super useful!
(use-package loccur :bind (("C-c O" . loccur)))
1.9.14. hideshow
Hideshow is a built-in package to fold section of code. It has
some really awkward keybindings under C-c @
, but otherwise is
nice, sometimes.
(add-hook 'prog-mode-hook #'hs-minor-mode)
1.9.15. Smartparens
Smartparens has become my go-to package for managing parethesis and the like. The peculiar thing is that, unlike packages such as paredit, it works on any language, not only lisp-y ones.
(use-package smartparens :bind (:map smartparens-mode-map ("C-M-f" . sp-forward-sexp) ("C-M-b" . sp-backward-sexp) ("C-M-a" . sp-beginning-of-sexp) ("C-M-e" . sp-end-of-sexp) ("C-M-n" . sp-next-sexp) ("C-M-p" . sp-previous-sexp) ("C-(" . sp-forward-barf-sexp) ("C-)" . sp-forward-slurp-sexp) ("C-{" . sp-backward-barf-sexp) ("C-}" . sp-backward-slurp-sexp) ("C-k" . sp-kill-hybrid-sexp) ("C-," . sp-rewrap-sexp) :map emacs-lisp-mode-map (";" . sp-comment) :map lisp-mode-map (";" . sp-comment)) :hook ((prog-mode . turn-on-smartparens-strict-mode) (web-mode . op/sp-web-mode) (LaTeX-mode . turn-on-smartparens-strict-mode)) :custom ((sp-highlight-pair-overlay nil)) :config (require 'smartparens-config) (with-eval-after-load 'clojure-mode (define-key clojure-mode-map ";" #'sp-comment)) (with-eval-after-load 'scheme-mode (define-key scheme-mode-map ";" #'sp-comment)) (sp-with-modes 'org-mode (sp-local-pair "=" "=" :wrap "C-=")) (bind-key [remap c-electric-backspace] #'sp-backward-delete-char smartparens-strict-mode-map) (sp-local-pair 'log-edit-mode "`" "'") (defun op/sp-web-mode () (setq web-mode-enable-auto-pairing nil)) (defun op/newline-indent (&rest _ignored) (split-line) (indent-for-tab-command)) (let ((c-like '(awk-mode c++mode cc-mode c-mode css-mode go-mode java-mode js-mode json-mode python-mode web-mode es-mode perl-mode lua-mode))) (dolist (x `(("{" . ,c-like) ("[" . ,c-like) ("(" . (sql-mode ,@c-like)))) (dolist (mode (cdr x)) (sp-local-pair mode (car x) nil :post-handlers '((op/newline-indent "RET") (op/newline-indent "<return>")))))) (defun op/inside-comment-or-string-p () "T if point is inside a string or comment." (let ((s (syntax-ppss))) (or (nth 4 s) ;comment (nth 3 s)))) (defun op/current-line-str () "Return the current line as string." (buffer-substring-no-properties (line-beginning-position) (line-end-position))) (defun op/maybe-add-semicolon-paren (_id action _ctx) "Insert semicolon after parens when appropriat. Mainly useful in C and derived, and only when ACTION is insert." (when (eq action 'insert) (save-excursion ;; caret is between parens (|) (forward-char) (let ((line (op/current-line-str))) (when (and (looking-at "\\s-*$") (not (string-match-p (regexp-opt '("if" "else" "switch" "for" "while" "do" "define") 'words) line)) (string-match-p "[\t ]" line) (not (op/inside-comment-or-string-p))) (insert ";")))))) (let ((c-like-modes-list '(c-mode c++-mode java-mode perl-mode))) (sp-local-pair c-like-modes-list "(" nil :post-handlers '(:add op/maybe-add-semicolon-paren))) (defhydra hydra-sp (:hint nil) " Moving^^^^ Slurp & Barf^^ Wrapping^^ Sexp juggling^^^^ Destructive ------------------------------------------------------------------------------------------------------------------------ [_a_] beginning [_n_] down [_h_] bw slurp [_R_] rewrap [_S_] split [_t_] transpose [_c_] change inner [_w_] copy [_e_] end [_N_] bw down [_H_] bw barf [_u_] unwrap [_s_] splice [_A_] absorb [_C_] change outer [_f_] forward [_p_] up [_l_] slurp [_U_] bw unwrap [_r_] raise [_E_] emit [_k_] kill [_g_] quit [_b_] backward [_P_] bw up [_L_] barf [_(__{__[_] wrap (){}[] [_j_] join [_o_] convolute [_K_] bw kill [_q_] quit" ("?" (hydra-set-property 'hydra-sp :verbosity 1)) ;; moving ("a" sp-beginning-of-sexp) ("e" sp-end-of-sexp) ("f" sp-forward-sexp) ("b" sp-backward-sexp) ("n" sp-down-sexp) ("N" sp-backward-down-sexp) ("p" sp-up-sexp) ("P" sp-backward-up-sexp) ;; slurping & barfing ("h" sp-backward-slurp-sexp) ("H" sp-backward-barf-sexp) ("l" sp-forward-slurp-sexp) ("L" sp-forward-barf-sexp) ;; wrapping ("R" sp-rewrap-sexp) ("u" sp-unwrap-sexp) ("U" sp-backward-unwrap-sexp) ("(" sp-wrap-round) ("[" sp-wrap-square) ("{" sp-wrap-curly) ;; sexp juggling ("S" sp-split-sexp) ("s" sp-splice-sexp) ("r" sp-raise-sexp) ("j" sp-join-sexp) ("t" sp-transpose-sexp) ("A" sp-absorb-sexp) ("E" sp-emit-sexp) ("o" sp-convolute-sexp) ;; destructive editing ("c" sp-change-inner :exit t) ("C" sp-change-enclosing :exit t) ("k" sp-kill-sexp) ("K" sp-backward-kill-sexp) ("w" sp-copy-sexp) ("q" nil) ("g" nil)) (define-key global-map (kbd "s-c") (lambda () (interactive) (hydra-set-property 'hydra-sp :verbosity 0) (hydra-sp/body))))
1.9.15.1. TODO the configuration is quite long, can it be made modular?
1.9.16. Flymake
Flymake marks errors in buffer, using various means. LSP is one
of those. For starters, enable it for every prog-mode
buffer
(add-hook 'prog-mode-hook #'flymake-mode)
Tweak its settings a bit
(setq flymake-fringe-indicator-position 'left-fringe
flymake-suppress-zero-counters t
flymake-start-on-flymake-mode t
flymake-no-changes-timeout nil
flymake-start-on-save-buffer t
flymake-proc-compilation-prevents-syntax-check t
flymake-wrap-around nil)
and make a hydra for it
(with-eval-after-load 'flymake (defhydra hydra-flymake (flymake-mode-map "C-c !") ("n" flymake-goto-next-error) ("p" flymake-goto-prev-error) ("RET" nil :exit t) ("q" nil :exit t)))
1.9.17. Flyspell and friends
Flyspell is Flymake, but for natural languages! /s
(add-hook 'text-mode-hook #'flyspell-mode)
1.9.17.1. guess language
One annoying thing of not being a native English speaker is that
I need Emacs to handle more than one language. That means
constantly M-x ispell-change-dictionary
, or one cane use
guess-language
!
It uses a statistical method to detect the language, which seems
to work pretty well for English and Italian. It even supports
multiple languages in the same buffer (as long as they appear in
different paragraphs). The only drawback is that sometimes Emacs
gets stuck executing ispell
, but a pkill -USR2
on the server
pid fixes it.
(use-package guess-language :hook (text-mode . guess-language-mode) :config (setq guess-language-langcodes '((en . ("en_GB" "English")) (it . ("it" "Italian"))) guess-language-languages '(en it) guess-language-min-paragraph-length 45))
1.9.18. Typo(graphical stuff)
Typo transforms certain character into their "typographical"
counterpart. I like to use it when writing in my blog, so enable
it for gemini-mode
.
(use-package typo :hook ((gemini-mode . typo-mode)) :config (push '("Italian" "“" "”" "‘" "’" "«" "»") typo-quotation-marks))
Olivetti mode "centers" the buffer, it's nice when writing text:
(use-package olivetti :hook ((gemini-mode . olivetti-mode) (markdown-mode . olivetti-mode)))
I also do typos pretty often, and abbrev is handy for those occasions and accents (like "perchè" instead of "perché").
my-abbrev
is a package-like file where I store the abbreviations
I need.
(use-package my-abbrev :straight nil)
1.9.19. hippie expand
This is a "dumb" completion method. It tries a couple of method to complete the word before the cursor. Turns out, for how rudimentary it may be, it's often precise.
(define-key global-map (kbd "M-/") #'hippie-expand) (setq hippie-expand-try-functions-list '(try-expand-dabbrev try-expand-dabbrev-all-buffers try-expand-dabbrev-from-kill try-complete-file-name-partially try-complete-file-name try-expand-all-abbrevs try-expand-list try-expand-line try-complete-lisp-symbol-partially try-complete-lisp-symbol))
1.9.20. isearch
Some very small tweaks for isearch
(setq isearch-lazy-count t search-whitespace-regexp ".*?" isearch-allow-scroll 'unlimited)
1.9.21. etags
Reload tags without asking
(setq tags-revert-without-query 1)
1.9.22. view mode
Sometimes it's handy to make a buffer read-only. Also, define some key to easily navigate in read-only buffers.
(use-package view :straight nil :bind (("C-x C-q" . view-mode) :map view-mode-map ("n" . next-line) ("p" . previous-line) ("l" . recenter-top-bottom)))
1.9.23. pdf-tools
Not really text-related, but still.
(use-package pdf-tools :bind (:map pdf-view-mode-map ("C-s" . isearch-forward)) :custom (pdf-annot-activate-created-annotations t) :init (pdf-tools-install))
Works great on OpenBSD. It would be cool to make a package out of it, but since it requires tablist from melpa it may be a problem?
this post to hints on how to integrate it with AucTeX.
see1.9.24. avy
I definitely need to use it more. It allows to quickly jump around, both in the same and in other buffers.
(use-package avy :custom ((avy-keys '(?s ?n ?t ?h ?d ?i ?u ?e ?o ?a))) :bind (("M-g c" . avy-goto-char) ("M-g C" . avy-goto-char-2) ("M-g w" . avy-goto-word-1) ("M-g f" . avy-goto-line) :map isearch-mode-map ("C-'" . avy-isearch)))
1.9.25. iedit
I tried to use multiple-cursor
, but I just fail. iedit
does
99% of what I need.
The following is a small tweak for it, maybe it's unnecessary as I haven't read the documentation in depth.
(use-package iedit :bind (("C-;" . op/iedit-dwim)) :config (defun op/iedit-dwim (arg) "Start iedit but do what I mean. With a prefix (i.e. non-nil ARG) just execute `iedit-mode'; if the region is active start iedit in the current defun (as by `narrow-to-defun') with the current selection as replacement search string. if a region is not active, do the same but with `current-word'. Inspired, but modified, by the masteringemacs.org article." (interactive "P") (if arg (iedit-mode) (let (beg end) (save-excursion (save-restriction (widen) (narrow-to-defun) (setq beg (point-min) end (point-max)))) (cond (iedit-mode (iedit-done)) ((use-region-p) (iedit-start (regexp-quote (buffer-substring-no-properties (mark) (point))) beg end)) (t (iedit-start (concat "\\<" (regexp-quote (current-word)) "\\>") beg end)))))))
1.9.26. editorconfig
I don't use it very often, so this bit not actually included in the configuration, but when you need it, it's handy:
(use-package editorconfig :config (editorconfig-mode +1))
1.9.27. Compilation
M-x compile RET
(or recompile
) spawn a buffer with the output
of make. Generally speaking, auto scroll on that is useless, but
I keep this bit here in case I'll ever change my mind.
(setq compilation-scroll-output nil)
Even if, to be completely honest, keeping it at the top means I
can M-g n=/=p
easily…
1.9.28. Languages
1.9.28.1. jump to matching paren
The idea behind this is really cool. Pressing %
with the
cursor on (or before) a parenthesis (of any kind) will jump to
the other side. Unfortunately, it doesn't play well with
Clojure, where %
is used for the "terse" lambda syntax
(i.e. #(assoc foo :bar %)
)
(use-package paren :straight nil ;; :bind (("%" . op/match-paren)) :config (show-paren-mode +1) ;; thanks, manual (defun op/match-paren (arg) "Go to the matchig paren if on a paren; otherwise self-insert." (interactive "p") (cond ((looking-at "\\s(") (forward-list 1) (backward-char 1)) ((looking-at "\\s)") (forward-char 1) (backward-list 1)) (t (self-insert-command (or arg 1))))))
1.9.28.2. eglot
LSP stands for Language Something Protocol
, developed by M$ for
vs-code, but − bear with me, it's weird to say it − it seems a
decent idea.
There are two major implementations for emacs: lsp-mode
and
eglot
. lsp-mode is too noisy for me, I prefer eglot
as it's
less intrusive
(use-package eglot :bind (:map eglot-mode-map ("<f1>" . eglot-code-actions) ("<f2>" . eglot-format)) :config (add-to-list 'eglot-server-programs '(c-mode . ("clangd" "--header-insertion=never"))))
clangd
has an annoying "feature": it automatically adds include
when it thinks they're needed.
Additionally, various LSP backend (at least gopls
) like to
highlight the symbol at point in the buffer, which gets super
annoying, it turns your buffer into some sort of Christmas tree
every time you move the point around. Eglot has the concept of
"ignored server capabilities" where it would fake to understand
some capabilities, but don't actually apply them.
(with-eval-after-load 'eglot (add-to-list 'eglot-ignored-server-capabilites :documentHighlightProvider))
Protip: when working on a C project, one needs a
compile-commands.json
file. But, most of the time, a simple
compile_flags.txt
with the $CFLAGS
one per line is enough.
See gmid Makefile for instance, but usually this is enough:
compile_commands.txt: printf "%s\n" ${CFLAGS} > $@
1.9.28.3. prog-mode
Enable auto-fill for comments in prog-mode
buffers:
(defun op/auto-fill-comment () "Enable auto-fill for comments." (setq-local comment-auto-fill-only-comments t) (auto-fill-mode)) (add-hook 'prog-mode-hook #'op/auto-fill-comment)
I finally found a usage for the arrow keys:
copy-from-above-command
! I've bound that function to the up
arrow, so it's easy to copy the previous line. The function
bound to the down arrow duplicates the current line.
(define-key prog-mode-map [up] #'copy-from-above-command) (defun op/dup-line () "Duplicate the current line, using `copy-from-above-command'." (interactive) (save-excursion (forward-line 1) (open-line 1) (copy-from-above-command)) (call-interactively #'next-line)) (define-key prog-mode-map [down] #'op/dup-line)
1.9.28.4. text-mode
Enable abbrev-mode in text buffers:
(add-hook 'text-mode-hook #'abbrev-mode)
1.9.28.5. diff
A small usability tweak for diff-mode
: I like to have M-SPC
scroll down instead then up, just like in telescope!
(with-eval-after-load 'diff-mode (define-key diff-mode-map (kbd "M-SPC") #'scroll-down-command))
1.9.28.6. elisp
Enable prettify and checkdock in emacs lisp mode: the former
transforms lambda
into λ
, and the latter enables style
warning for elisp packages
(add-hook 'emacs-lisp-mode-hook #'checkdoc-minor-mode) (add-hook 'emacs-lisp-mode-hook #'prettify-symbols-mode)
Bind a key to run all the tests and to spawn ielm:
(defun op/ert-all () "Run all ert tests." (interactive) (ert t)) (defun op/ielm-repl (arg) "Pop up a ielm buffer." (interactive "P") (let ((buf (get-buffer-create "*ielm*"))) (if arg (switch-to-buffer buf) (pop-to-buffer buf)) (ielm))) (let ((map emacs-lisp-mode-map)) (define-key map (kbd "C-c C-k") #'eval-buffer) (define-key map (kbd "C-c k") #'op/ert-all) (define-key map (kbd "C-c C-z") #'op/ielm-repl))
Eros is a nice little package that renders the output of
eval-last-sexp
in a small overlay right after the cursor, just
like CIDER!
(use-package eros :config (eros-mode 1))
Emacs-lisp doesn't have namespaces, so usually there's this
convention of prefixing every symbol of a package with the
package name. Nameless helps with this. It binds _
to insert
the name of the package, and it visually replace it with :
.
It's pretty cool.
(use-package nameless :hook (emacs-lisp-mode . nameless-mode) :custom ((nameless-private-prefix t) (nameless-affect-indentation-and-filling nil)) :bind (:map emacs-lisp-mode-map ("_" . nameless-insert-name-or-self-insert)))
package-lint
is kind of cool, if not because it helps you see
what's the required emacs version. The main entrypoint is the
package-lint-buffer
function, which will pop up a buffer with
the reported things.
(use-package package-lint)
1.9.28.7. Common LISP
I'm trying to use this convention for repls:
C-c C-z
opens a repl at the bottom of the windowC-u C-c C-z
opens the repl in the current buffer
(use-package sly :hook ((lisp-mode . prettify-symbols-mode) (lisp-mode . op/disable-tabs) (lisp-mode . sly-symbol-completion-mode)) :custom (inferior-lisp-program "sbcl") :bind (:map sly-mode-map ("C-c C-z" . op/sly-mrepl)) :config (defun op/sly-mrepl (arg) "Find or create the first useful REPL for the default connection in a side window." (interactive "P") (save-excursion (sly-mrepl nil)) (let ((buf (sly-mrepl--find-create (sly-current-connection)))) (if arg (switch-to-buffer buf) (pop-to-buffer buf)))) (use-package sly-mrepl :straight nil ;; it's part of sly! :bind (:map sly-mrepl-mode-map ("M-r" . comint-history-isearch-backward))))
1.9.28.8. Clojure
Load clojure-mode
from MELPA (I guess, or is it ELPA?)
(use-package clojure-mode :mode (("\\.clj" . clojure-mode) ("\\.cljs" . clojurescript-mode) ("\\.cljc" . clojurec-mode) ("\\.edn" . clojure-mode)) :hook ((clojure-mode . subword-mode) (clojurec-mode . subword-mode) (clojurescript-mode . subword-mode) (clojure-mode . op/disable-tabs) (clojurec-mode . op/disable-tabs) (clojurescript-mode . op/disable-tabs) (clojure-mode . abbrev-mode) (clojurec-mode . abbrev-mode) (clojurescript-mode . abbrev-mode)) :config (put-clojure-indent 'doto-cond '(1 nil nil (1))))
doto-cond
is a macro I wrote some time ago, I don't remember
where, but anyway.
CIDER is the Clojure Interactive Development Environment that
Rocks, aka the best thing for clojure. Just like with ielm and
sly, use my convention for C-c C-z
behaviour wrt prefix
argument, but tweak also the key so the repl behaves more like a
comint buffer.
(use-package cider :custom (cider-repl-display-help-banner nil) :bind (:map cider-repl-mode-map ;; more like comint ("C-c M-o" . cider-repl-clear-buffer) ("C-c C-l" . cider-repl-switch-to-other) :map cider-mode-map ("C-c C-z" . op/cider-repl)) :config (defun op/cider-repl (arg) "Switch to repl buffer in side window. With non-nil ARG use `display-buffer' ignoring the rules in `display-buffer-alist'." (interactive "P") (when-let (buf (cider-current-repl)) (call-interactively #'cider-repl-set-ns) (let ((display-buffer-alist (if arg () display-buffer-alist))) (pop-to-buffer buf '(display-buffer-reuse-window))))))
1.9.28.9. Scheme
Geiser works for any scheme IIRC, but needs a tweak to find
guile
in my system.
(use-package geiser :config (setq geiser-guile-binary "guile3.0"))
1.9.28.10. Elastic search mode
es-mode
let one write kibana-like queries and execute them from
Emacs.
(use-package es-mode :mode "\\.es\\'" :hook (es-mode . op/disable-tabs))
1.9.28.11. SQL
op/visit-new-migration-file
prompts for a name and creates an
associated migration file, named after $date-$name.sql
.
(defun op/visit-new-migration-file (name) "Visit a new SQL migration file named after NAME." (interactive "Mname: ") (let* ((name (replace-regexp-in-string " " "-" (string-trim name))) (f (format "%s-%s.sql" (format-time-string "%Y%m%d%H%M") name))) (find-file f)))
To please my muscle memory:
(defalias 'psql #'sql-postgres)
Sometimes I need to connect to a PostgreSQL database over a non-standard port, so here's a quick function to do that
(defun op/psql-params (port) "Easily connect to a psql on a non-standard PORT." (interactive "nPort: ") (let ((sql-port port)) (psql)))
I don't particularly like how the electric-indent
behaves in
SQL buffers, so try to tame it
(defun op/sql-sane-electric-indent-mode () "Fix function `electric-indent-mode' behaviour locally." (interactive) (setq-local electric-indent-inhibit nil)) (add-hook 'sql-mode-hook #'op/sql-sane-electric-indent-mode)
The lines in the interactive SQL buffer can get long, and truncation makes them look awful.
(add-hook 'sql-interactive-mode-hook #'toggle-truncate-lines)
Finally, define some handy keys to open a connection
(define-key global-map (kbd "C-z a s") #'psql) (define-key global-map (kbd "C-z a S") #'op/psql-params)
1.9.28.12. nxml
nxml-mode
is the major mode for editing XML buffers. I use it
to edit svg files too.
(setq nxml-slash-auto-complete-flag t)
(add-hook 'nxml-mode-hook #'smartparens-strict-mode)
1.9.28.13. web
web-mode
provides font-lock, indentation and stuff for various
"web-related" file types.
By enabling web-mode-enable-engine-detection
it became possible
to define web-mode-engines-alist
and having web-mode
selecting the engine from that alist.
(use-package web-mode :mode (("\\.erb\\'" . web-mode) ("\\.mustache\\'" . web-mode) ("\\.html\\'" . web-mode)) :custom ((web-mode-markup-indent-offset 2) (web-mode-css-indent-offset 2) (web-mode-code-indent-offset 2) (web-mode-style-padding 0) (web-mode-enable-engine-detection t)) :hook ((web-mode . op/disable-tabs)))
It's useful to use a .dir-locals.el
file to customize the
engine selection, but that unfortunately doesn't work
out-of-the-box. The following hack is needed:
(with-eval-after-load 'web-mode (defun op/web-mode-fix-dir-locals () (when (derived-mode-p major-mode 'web-mode) (web-mode-guess-engine-and-content-type))) (add-hook 'hack-local-variables-hook #'op/web-mode-fix-dir-locals))
1.9.28.14. CSS
I don't use web-mode
for CSS, emacs bulit in mode works pretty
well. Just disable hard tabs:
(use-package css-mode :hook (css-mode . op/disable-tabs))
1.9.28.15. javascript
Just load some useful modes and disable tabs
(use-package js :straight nil :hook ((js-mode . abbrev-mode) (js-mode . subword-mode) (js-mode . op/disable-tabs)))
1.9.28.16. C
Usually I follow the OpenBSD KNF style(9) guidelines when writing C.
(setq c-basic-offset 8 c-default-style "K&R") (defun op/c-indent () (interactive) (c-set-offset 'arglist-intro '+) (c-set-offset 'arglist-cont-nonempty '*)) (add-hook 'c-mode-hook #'op/c-indent)
Subword and abbrev mode are particularly useful. With abbrev I
can easily fix typos like #inculde
→ #include
, and subword is
useful for camelCase/PascalCase function name (fortunately
enough, they aren't widespread in C)
(dolist (hook '(c-mode-hook c++-mode-hook))
(add-hook hook #'abbrev-mode)
(add-hook hook #'subword-mode))
My smartparens configuration automatically adds semicolon when
appropriate (well, most of the times). While it's useful, typing
a line of code soon becomes a matter of typing the code and then
C-e RET
to go to the next line. Fortunately we can optimise
it:
(defun op/open-line-under () "Like `open-line', but under." (interactive) (move-end-of-line 1) (newline) (c-indent-line)) (with-eval-after-load 'cc-mode (define-key c-mode-map (kbd "M-RET") #'op/open-line-under))
Use some similar (but slightly different) smartparens key and
reserve a key for recompile
.
(with-eval-after-load 'cc-mode (let ((map c-mode-map)) (define-key map (kbd "<tab>") #'indent-for-tab-command) (define-key map (kbd "TAB") #'indent-for-tab-command) (define-key map (kbd "C-M-a") #'sp-beginning-of-sexp) (define-key map (kbd "C-M-e") #'sp-end-of-sexp) (define-key map (kbd "C-M-p") #'beginning-of-defun) (define-key map (kbd "C-M-n") #'end-of-defun) (define-key map (kbd "C-c M-c") #'recompile)))
I used to use irony
, but now I mostly use eglot if I really
need "advanced" support. Just for history sake, here's my old
configuration (this is not tangled)
(use-package irony :hook ((c++-mode . irony-mode) (c-mode . irony-mode) (obj-mode . irony-mode)))
Being able to interactively teach things to emacs is really
cool. Sometimes I need to add a header at the top of the
buffer. It's not something difficult, I push the point in the
mark ring, then jump to the start of the buffer and scroll until
I find the block of includes, add the one I want, sort them and
pop the mark to continue where I was. But we can do better:
op/c-add-include
is the answer. It prompts for a header
(without completion for the time being) and it inserts it in the
right place. With the prefix argument it's possible to require
the inclusion of a local header.
There's some space for improvements, but for the time being I'm happy. Things that I'd like to add in the future:
- I keep the
sys/
includes in a separate block, it'd be nice if this would respect that - If there aren't includes in the file this raises an error. It'd be nice if it was able to automatically add one after the copyright stuff at the start of the buffer.
(defun op/c-add-include (path &optional localp) "Include PATH at the start of the file. If LOCALP is non-nil, the include will be \"local\"." (interactive "Mheader to include: \nP") (save-excursion (let ((re (if localp "^#[ \t]*include[ \t]*\"" "^#[ \t]*include[ \t]*<")) (ignore-re "^#include \"compat.h\"") start) (goto-char (point-min)) (while (not (or (and (looking-at re) (not (looking-at ignore-re))) (eobp))) (forward-line)) (when (eobp) (error "Don't know where to insert the header")) (open-line 1) (insert "#include " (if localp "\"\"" "<>")) (backward-char) (insert path) (move-beginning-of-line 1) (setq start (point)) (forward-line) (while (and (looking-at re) (not (eobp))) (forward-line)) (sort-lines nil start (point))))) (with-eval-after-load 'cc-mode (define-key c-mode-map (kbd "C-c C-a") #'op/c-add-include))
1.9.28.17. Go
My go configuration is simple: just load go-mode
!
(use-package go-mode :mode "\\.go\\'" :hook ((go-mode . subword-mode)))
1.9.28.18. Perl
Just require perl-mode
and ensure we indent with hard tabs
(use-package perl-mode :straight nil :custom ((perl-indent-level 8)))
1.9.28.19. Python
Load python-mode
and disable hard tabs:
(use-package python :hook ((python-mode . op/disable-tabs)))
1.9.28.20. sh-mode
Simple stuff, set the tab width and fix the indentation
(use-package sh-script :straight nil :custom ((sh-basic-offset 8) (sh-indent-after-loop-construct 8) (sh-indent-after-continuation nil)))
1.9.28.20.1. TODO fix smartparens and sh' case
In a case statement, we have un-paired closed parethesis that
require C-q
to be typed because of sp-strict-mode
.
1.9.28.21. Lua
Nothing fancy, just load the package
(use-package lua-mode :mode "\\.lua\\'" :hook ((lua-mode . op/disable-tabs)) :custom ((lua-default-application "lua53")))
lua-lsp
is the LSP server for lua, let's hook it into eglot
(with-eval-after-load 'eglot (add-to-list 'eglot-server-programs '((lua-mode) . ("lua-lsp"))))
1.9.28.22. GDScript
GDScript is the scripting language of the Godot game engine. The
gdscript-mode
provides also format (via a python program) and
integration with Godot:
(use-package gdscript-mode :mode "\\.gd\\'" :custom (gdscript-gdformat-save-and-format t))
It needs an external program for the formatting:
pip3 install --user gdtoolkit
but see the repository on GitHub for more information!
1.9.28.23. YAML
Yet another simple block for Yet Another Markup Language.
Disable flyspell in yaml. It inherits from text-mode
but most
of the time grammar check doesn't yield anything useful.
(use-package yaml-mode :mode "\\.yml\\'" :hook ((yaml-mode . turn-off-flyspell)))
1.9.28.24. TOML
(use-package toml-mode :mode "\\.toml\\'")
1.9.28.25. Gemini (text/gemini)
Fetch gemini-mode
package. Also, I like to write text/gemini
with a nice proportial font and a "bar" as cursor, just like I do
with org-mode!
(use-package gemini-mode :hook ((gemini-mode . op/gemini-setup)) :config (defun op/gemini-setup () (setq-local cursor-type 'bar)))
(use-package ox-gemini)
1.9.28.26. Markdown
Install markdown-mode
and enable auto fill.
(use-package markdown-mode :mode "\\.md\\'" :hook ((markdown-mode . auto-fill-mode)))
1.10. Applications
Here, configuration for various non text editing related stuff.
(defun op/tigervnc->chiaki () "Connects to chiaki over tigervnc." (interactive) (async-shell-command "vncviewer.tigervnc 192.168.1.11")) (define-key global-map (kbd "C-z a c") #'op/tigervnc->chiaki)
1.10.1. bins
It's useful to send a buffer, or part of it, to a bin online and then send the corresponding link to someone. The clbin package does that, in a DWIM manner: send the current region (if any) or the whole buffer, and save the corresponding url in the kill-ring.
(use-package clbin :straight nil :bind ("C-z w" . clbin-dwim))
1.10.2. sam for the rescue!
I like the design of various plan9 stuff, even if I haven't used
the system. sam.el
is my ongoing (and slow) attempt at
emulating sam
(use-package sam :straight nil :load-path "~/w/sam/master/")
1.10.3. eshell
Eshell is the Emacs Shell. It's a strange combo, because it isn't
a full-blown elisp REPL like ielm, but neither a UNIX shell like
shell
. It seems fun to use though.
(use-package eshell :bind (("C-c e" . op/eshell)) :hook (eshell-mode . op/setup-eshell) :custom ((eshell-compl-dir-ignore "\\`\\(\\.\\.?\\|CVS\\|\\.svn\\|\\.git\\|\\.got\\)/\\'") (eshell-save-history-on-exit t) (eshell-prompt-regexp "[#$] ") (eshell-prompt-function (lambda () "$ ")) (eshell-history-size 1024)) :config <<eshell/aliases>> <<eshell/cl>> (defun op/eshell-bufname (dir) (concat "*eshell " (expand-file-name dir) "*")) <<eshell/after-cd>> (defun op/eshell (arg) "Run or jump to eshell in the current project. If called with a prefix argument ARG, always create a new eshell buffer." (interactive "P") (let* ((proj (project-current)) (dir (cond (proj (project-root proj)) (t default-directory))) (default-directory dir) (eshell-buffer-name (let ((name (op/eshell-bufname dir))) (if arg (generate-new-buffer name) name)))) (eshell))) <<op/append-to-buffer>> <<op/eshell-narrow-to-output>> <<op/setup-eshell>>)
I like to sync the $PWD
with the buffer name, so an eshell in my
home has as buffer name *eshell /home/op*
.
advice-add
I should use the
eshell-directory-change-hook
hook.
(defun op/eshell-after-cd (&rest _) (rename-buffer (op/eshell-bufname default-directory) t)) (advice-add #'eshell/cd :after #'op/eshell-after-cd)
To define custom commands in eshell (what would be functions or
scripts in other shells), say whatever
, one can define a
eshell/whatever
function. These are some aliases I find useful:
(defun eshell/emacs (&rest args) "Open a file in emacs (from the wiki)." (if (null args) (bury-buffer) (mapc #'find-file (mapcar #'expand-file-name (eshell-flatten-list (nreverse args)))))) (defalias 'eshell/less #'find-file) (defun eshell/dired () (dired (eshell/pwd)))
cl
is a better clear function, because the built in clear
is a
joke.
(defun eshell/cl () "Clear the eshell buffer." (let ((inhibit-read-only t)) (erase-buffer)))
Eshell can interact with other Emacs buffers, but the syntax is
quite verbose. op/append-to-buffer
(bound to C-c C-B
) helps
with this: it prompts for a buffer and inserts (but not execute)
the redirect. This is especially useful when working with the
OpenBSD ports tree: I can type cvs -q diff
and then C-c C-B
to
redirect the diff to the mail buffer!
(defun op/append-to-buffer (buf) (interactive "Bbuffer: ") (insert ">>> " "#<" buf ">"))
Being able to narrow to the output of the command at point seems very useful, so here's a quick implementation:
(defun op/eshell-narrow-to-output () "Narrow to the output of the command at point." (interactive) (save-excursion (let* ((start (progn (eshell-previous-prompt 1) (forward-line +1) (point))) (end (progn (eshell-next-prompt 1) (forward-line -1) (point)))) (narrow-to-region start end))))
Eshell unfortunately doesn't uses from comint
, so it lacks some
niceties that were added to it. One of these things is the
minibuffer completion for history navigation. I like to select an
history item using the minibuffer:
(defun op/eshell-select-from-history () (interactive) (let ((item (completing-read "Select from history: " (seq-uniq (ring-elements eshell-history-ring))))) (when item ;; from eshell-previous-matching-input (delete-region eshell-last-output-end (point)) (insert-and-inherit item))))
The weirdest thing about eshell is how it manages its own keys.
eshell-mode-map
, unlike other *-mode-map
variables, is
buffer-local, so a
;; just an example! (define-key eshell-mode-map (kbd "...") #'some-function)
won't work. One needs to define a function and call it during the
eshell-mode-hook
like this:
(defun op/setup-eshell () (define-key eshell-mode-map (kbd "C-c C-B") #'op/append-to-buffer) (define-key eshell-mode-map (kbd "C-c M-l") #'op/eshell-narrow-to-output) (message "before binding M-r") (define-key eshell-mode-map (kbd "M-r") #'op/eshell-select-from-history))
1.10.3.1. TODO make >>>
ignore stderr
1.10.4. vterm ― when eshell isn't enough
vterm is a full-blown terminal emulator for Emacs, powered by the same library that GNOME terminal uses (IIRC)
(use-package vterm :bind (:map vterm-mode-map ("C-<backspace>" . op/vterm-fix-c-backspace) ("C-c M-t" . vterm-copy-mode) :map vterm-copy-mode-map ("C-c M-t" . vterm-copy-mode)) :bind-keymap ("C-z v" . op/vterm-map) :custom ((vterm-buffer-name-string "*vterm %s*") (vterm-shell (concat shell-file-name " -l"))) :config (defun op/vterm-fix-c-backspace () (interactive) (vterm-send-key (kbd "C-w"))) (defvar *op/hostname* (with-temp-buffer (process-file "hostname" nil (current-buffer)) (string-trim (buffer-string) "" "\n")) "The hostname of this machine.") (defun op/project-vterm () "Spawn a vterm in the current project." (interactive) (let* ((project-current (project-current)) (default-directory (if project-current (project-root project-current) default-directory))) (vterm))) (define-prefix-command 'op/vterm-map) (define-key op/vterm-map (kbd "v") #'vterm) (define-key op/vterm-map (kbd "v") #'op/project-vterm))
1.10.5. ibuffer
Ibuffer is a package to list, filter and do stuff on the buffers.
(define-key global-map (kbd "C-x C-b") #'ibuffer)
1.10.6. help
Help buffers are, no pun intended, helpful. But they are so more with a bit o' visual lines:
(add-hook 'help-mode-hook #'visual-line-mode)
1.10.7. gdb
I like the "many buffer" view of gdb, so let's enable it
(setq gdb-many-windows t)
And to aid my muscle memory from the shell, define an egdb
alias
(defalias 'egdb #'gdb)
1.10.8. Literate calc-mode
This is pretty awesome. It provides a major mode where we can type expression and do math inline. Truly awesome.
(use-package literate-calc-mode)
1.10.9. keycast
Sometimes is nice. It adds the key and the command in the modeline, useful during records.
(use-package keycast :custom (keycast-insert-after 'mode-line-misc-info))
1.10.10. email
NB: mu4e
is installed from ports, not via straight.
doas pkg_add mu4e
1.10.10.1. TODO split the following into pieces
(use-package emacs :straight nil :bind ("<f12>" . mu4e) :custom ((message-citation-line-format "On %a, %b %d %Y, %f wrote\n") (message-default-charset 'utf8) (gnus-treat-display-smileys nil) (user-mail-address "op@omarpolo.com") (mu4e-maildir (expand-file-name "~/Maildir")) (mu4e-get-mail-command "mbsync -a") (mu4e-update-interval 180) (mu4e-compose-signature-auto-include nil) (mu4e-compose-format-flowed nil) (mu4e-compose-in-new-frame t) (mu4e-change-filenames-when-moving t) ;; don't break mbsync! (mu4e-attachment-dir "~/Downloads") (mu4e-compose-dont-reply-to-self t) (mu4e-confirm-quit nil) (mu4e-context-policy 'pick-first) (mu4e-compose-context-policy 'always-ask) (message-kill-buffer-on-exit t) (mu4e~view-beginning-of-url-regexp "https?\\://\\|mailto:\\|gemini://") ;; use localhost to send mails (message-send-mail-function #'smtpmail-send-it) (smtpmail-smtp-server "localhost") (smtpmail-default-smtp-server "localhost")) :hook (mu4e-view-mode . visual-line-mode) :config (require 'mu4e) ;; prefer the old style (setq mu4e-headers-thread-child-prefix '("├>" . "┣▶ ") mu4e-headers-thread-last-child-prefix '("└>" . "┗▶ ") mu4e-headers-thread-connection-prefix '("│" . "┃ ") mu4e-headers-thread-orphan-prefix '("┬>" . "┳▶ ") mu4e-headers-thread-single-orphan-prefix '("─>" . "━▶ ")) ;; fix the keys for the view buffer (define-key mu4e-view-mode-map (kbd "RET") #'push-button) (define-key mu4e-view-mode-map (kbd "SPC") #'scroll-up-command) (define-key mu4e-view-mode-map (kbd "S-SPC") #'scroll-down-command) ;; just like telescope :P (define-key mu4e-view-mode-map (kbd "M-SPC") #'scroll-down-command) (add-to-list 'mu4e-view-actions '("ViewInBrowser" . mu4e-action-view-in-browser) t) (defun op/mu4e-change-headers () (interactive) (setq mu4e-headers-fields `((:human-date . 10) (:flags . 4) (:mailing-list . 8) (:from . 20) (:to . 20) (:thread-subject)))) (add-hook 'mu4e-headers-mode-hook #'op/mu4e-change-headers) (defun op/mu4e-do-compose-stuff () (interactive) (set-fill-column 72) (auto-fill-mode) (flyspell-mode)) (add-hook 'mu4e-compose-mode-hook #'op/mu4e-do-compose-stuff) (defun op/mu4e-make-bookmarks (mdir) (list (make-mu4e-bookmark :name "Global Unread Messages" :query "flag:unread and not flag:trashed" :key ?u) (make-mu4e-bookmark :name "Local Unread Messages" :query (concat "maildir:" mdir "/* and flag:unread and not flag:trashed") :key ?l) (make-mu4e-bookmark :name "Today's Messages" :query (concat "maildir:" mdir "/* and date:today..now") :key ?t) (make-mu4e-bookmark :name "Big Messages" :query (concat "maildir:" mdir "/* and size:1M..500") :key ?b) (make-mu4e-bookmark :name "Sent" :query (concat "maildir:" mdir "/Sent") :key ?s) (make-mu4e-bookmark :name "Drafts" :query (concat "maildir:" mdir "/Drafts") :key ?d))) (defun op/mu4e-match-fn (mdir) (lambda (msg) (when msg (string-prefix-p mdir (mu4e-message-field msg :maildir))))) ;; loading the mu4e configuration without halting emacs if its not found ;; ;; the my-mu4e-config.el file contains: ;; (setq mu4e-contexts ;; (list ;; (make-mu4e-context ;; :name "foobar" ;; :match-func (my/mu4e-match-fn "/maildir-name") ;; :vars `((user-mail.address . "") ;; (user-full-name . "") ;; (mu4e-sent-folder . "") ;; (mu4e-drafts-folder . "") ;; (mu4e-trash-folder . "") ;; (mu4e-compose-format-flowed . t) ;; (mu4e-maildir-shortcuts . (("/foo/bar" . ?f) ;; ...)) ;; (mu4e-bookmarks . ,(my/mu4e-make-bookmars "/maildir-name")))))) ;; (condition-case nil (progn (message "before load") (load "my-mu4e-config") (message "after load")) (error (message "NOT loading mu4e configuration. It's missing!"))))
1.10.10.2. attach multiple files
mu4e uses mml-attach-file which, sadly, doesn't accept multiple files. Here's an attempt that uses dired to attach multiple file:
(defun op/composing-buffers () "Return a list of composing email buffers." (cl-loop for buf being the buffers when (with-current-buffer buf (eq major-mode 'mu4e-compose-mode)) collect buf)) ;; XXX: broken when multiple mu4e-compose-mode buffers are present! (defun op/mml-attach-multiple-files () "Attach the file marked from a dired buffer. Uses `mml-attach-file' to attach the single files." (interactive) (let ((files (dired-get-marked-files)) (bufs (op/composing-buffers))) (with-current-buffer (if (cdr bufs) ; if more than one (completing-read "Select email: " bufs) (car bufs)) (dolist (file files) (if (file-regular-p file) (mml-attach-file file (mm-default-file-encoding file) nil "inline") (message "Skipping non-regular file %s" file)))))) (define-key dired-mode-map (kbd "C-c C-a") #'op/mml-attach-multiple-files)
1.10.11. news
I'm trying to use GNUS to read usenet. I made an account over at eternal-september.org, added
machine news.eternal-september.org login mynick force yes password ****
in ~/.authinfo
. Then, to tell GNUS to connect to the server:
(setq gnus-select-method '(nntp "news.eternal-september.org"))
This bit could also be added to ~/.gnus
eventually.
At this point M-x gnus RET
and I'm ready to read the news!
Gnus has some strange keys:
A A
- to list all the groups known to the server
u
- to toggle the subscription to the group at point
M M u u
- (yes, really) to mark an entry as unread
etc…
1.10.12. pass
I've recently switched to the pass
password manager. It stores
every password in a gpg-encrypted file inside a git repository,
it's quite neat. My personal naming scheme is something along the
lines of
<category/service>/<optional sub-specifier>/<account-name>
(use-package pass :custom ((pass-show-keybindings nil)) :bind (("C-z P" . pass)))
1.10.13. vmd
I wrote a small package to interact with vmd(8)
on OpenBSD.
It's kinda nice, if I may say so.
(use-package vmd :straight nil :load-path "~/.emacs.d/lisp/vmd/" :custom (vmd-console-function #'op/vmd-vterm) :bind ("C-z a v" . vmd) :config (defun op/vmd-vterm (name cmd) (let ((vterm-shell (mapconcat #'shell-quote-argument cmd " ")) (vterm-buffer-name (concat "*" name "*"))) (vterm))))
1.10.14. sndio
I wrote also a package to interact with sndio(8)
. Yep, I like
these small package.
(use-package sndio :straight nil :load-path "~/.emacs.d/lisp/sndio.el/" :bind (("C-z a A" . sndio) ("C-z a a" . sndio-win-open)))
1.10.15. EMMS
Emacs Multi Media System is cool. It only needs a decent UI, which I provide via hydra.
A good companion for EMMS is versuri: a package that fetches lyrics from various websites and saves them locally.
(use-package versuri :config (defun op/versuri-select () (interactive) (when-let (match (call-interactively #'versuri-search)) (apply #'versuri-display match))))
There's a bit of hackery to make it see and play opus
files,
it's probably not needed anymore but who knows.
Also, I should split into pieces
(use-package emms :commands (emms) :bind ("C-z e" . hydra-emms/body) :config (setq emms-source-file-default-directory "~/music/" emms-mode-line-format "「%s」" emms-browser-covers 'emms-browser-cache-thumbnail-async) (require 'emms-setup) (emms-all) (emms-default-players) (emms-playing-time-disable-display) (add-to-list 'emms-player-base-format-list "opus") ;; re-compute the regxp for mpv (emms-player-set emms-player-mpv 'regex (apply #'emms-player-simple-regexp emms-player-base-format-list)) ;; save on quit and recover on startup (require 'emms-history) (emms-history-load) ;; use libtag to extract tracks info. ;; ;; XXX: this needs to be compiled from sources ;; (~/.emacs.d/straight/repos/emms/) and cp emms-print-metadata ;; ~/bin. (require 'emms-info) (require 'emms-info-libtag) (setq emms-info-functions '(emms-info-libtag)) (setq emms-info-libtag-known-extensions (regexp-opt '("opus" "mp3" "mp4" "m4a" "ogg" "flac" "spx" "wma"))) (defun my/tick-symbol (x) "Return a tick if X is true-ish." (if x "x" " ")) (defun my/emms-player-status () "Return the state of the EMMS player: `not-active', `playing', `paused' or `dunno'. Modeled after `emms-player-pause'." (cond ((not emms-player-playing-p) ;; here we should return 'not-active. The fact is that ;; when i change song, there is a short amount of time ;; where we are ``not active'', and the hydra is rendered ;; always during that short amount of time. So we cheat a ;; little. 'playing) (emms-player-paused-p (let ((resume (emms-player-get emms-player-playing-p 'resume)) (pause (emms-player-get emms-player-playing-p 'pause))) (cond (resume 'paused) (pause 'playing) (t 'dunno)))) (t (let ((pause (emms-player-get emms-player-playing-p 'pause))) (if pause 'playing 'dunno))))) (defun my/emms-toggle-time-display () "Toggle the display of time information in the modeline" (interactive) (if emms-playing-time-display-p (emms-playing-time-disable-display) (emms-playing-time-enable-display))) (defun my/emms-select-song () "Select and play a song from the current EMMS playlist." (interactive) (with-current-emms-playlist (emms-playlist-mode-center-current) (let* ((current-line-number (line-number-at-pos)) (lines (cl-loop with min-line-number = (line-number-at-pos (point-min)) with buffer-text-lines = (split-string (buffer-string) "\n") with lines = nil for l in buffer-text-lines for n = min-line-number then (1+ n) do (push (cons l n) lines) finally return (nreverse lines))) (selected-line (completing-read "Song: " lines))) (when selected-line (let ((line (cdr (assoc selected-line lines)))) (goto-line line) (emms-playlist-mode-play-smart) (emms-playlist-mode-center-current)))))) (defun op/emms-current-lyrics () "Find the lyrics for the current song." (interactive) (let* ((track (cdr (emms-playlist-current-selected-track))) (artist (cdr (assoc 'info-artist (cdr (emms-playlist-current-selected-track))))) (title (cdr (assoc 'info-title (cdr (emms-playlist-current-selected-track)))))) (versuri-display artist title))) (defhydra hydra-emms (:hint nil) " %(my/emms-player-status) %(emms-track-description (emms-playlist-current-selected-track)) ^Volume^ ^Controls^ ^Playback^ ^Misc^ ^^^^^^^^---------------------------------------------------------------- _+_: inc _n_: next _r_: repeat one [% s(my/tick-symbol emms-repeat-track)] _t_oggle modeline _-_: dec _p_: prev _R_: repeat all [% s(my/tick-symbol emms-repeat-playlist)] _T_oggle only time _v_: vol _<_: seek bw _#_: shuffle _s_elect ^ ^ _>_: seek fw _%_: sort _g_oto EMMS buffer ^ ^ _SPC_: play/pause _l_yrics ^ ^ _DEL_: restart _L_yrics select " ("+" emms-volume-raise) ("-" emms-volume-lower) ("v" sndio-win-open :exit t) ("n" emms-next) ("p" emms-previous) ("<" emms-seek-backward) (">" emms-seek-forward) ("SPC" emms-pause) ("DEL" (emms-player-seek-to 0)) ("<backspace>" (emms-player-seek-to 0)) ("r" emms-toggle-repeat-track) ("R" emms-toggle-repeat-playlist) ("#" emms-shuffle) ("%" emms-sort) ("t" (progn (my/emms-toggle-time-display) (emms-mode-line-toggle))) ("T" my/emms-toggle-time-display) ("s" my/emms-select-song) ("g" (progn (emms) (with-current-emms-playlist (emms-playlist-mode-center-current)))) ("l" op/emms-current-lyrics :exit t) ("L" op/versuri-select :exit t) ("q" nil :exit t)))
1.10.16. eww
The infamous Emacs Web Wrowser. Nothing fancy, just don't store cookie please!
(use-package eww :straight nil :custom ((url-cookie-trusted-urls nil) (url-cookie-untrusted-urls '(".*"))))
1.10.17. exwm
not tangled!
(use-package exwm :hook ((exwm-update-class . op/exwm-update-class) (exwm-update-title . op/exwm-update-title)) :custom ((exwm-input-simulation-keys '( ;; movement ([?\C-b] . [left]) ([?\M-b] . [C-left]) ([?\C-f] . [right]) ([?\M-f] . [C-right]) ([?\C-p] . [up]) ([?\C-n] . [down]) ([?\C-a] . [home]) ([?\C-e] . [end]) ([?\M-v] . [prior]) ([?\C-v] . [next]) ([?\C-d] . [delete]) ([?\C-k] . [S-end delete]) ;; cut/paste. ([?\C-w] . [?\C-x]) ([?\M-w] . [?\C-c]) ([?\C-y] . [?\C-v]) ;; search ([?\C-s] . [?\C-f])))) :bind (("s-r" . exwm-reset) ("s-w" . exwm-workspace-switch) ("M-&" . op/run-command) :map exwm-mode-map ("C-q" . exwm-input-send-next-key)) :config (setenv "EDITOR" "emacsclient") (setenv "VISUAL" "emacsclient") (cl-loop for k in '(?& ?\{ ?\[ ?\( ?= ?+ ?\) ?\] ?\} ?!) for i from 1 do (define-key global-map (kbd (format "s-%c" k)) (lambda () (interactive) (exwm-workspace-switch-create i)))) (defun op/exwm-update-class () (unless (or (string-prefix-p "sun-awt-X11-" exwm-instance-name) (string= "gimp" exwm-instance-name)) (exwm-workspace-rename-buffer exwm-class-name))) (defun op/exwm-update-title () (when (or (not exwm-instance-name) (string-prefix-p "sun-awt-X11-" exwm-instance-name) (string= "gimp" exwm-instance-name)) (exwm-workspace-rename-buffer exwm-title))) (defun op/run-command (cmd) (interactive (list (read-shell-command "$ "))) (start-process-shell-command cmd nil cmd)))
1.10.18. elpher
Elpher is a Gemini/Gopher browser for Emacs.
(use-package elpher :custom ((elpher-ipv4-always nil) (elpher-default-url-type "gemini")) :commands (elpher elpher-go elpher-jump))
1.10.19. pq
pq
is a postgres library module for elisp. Sound crazy, hu?
(use-package pq :straight nil :load-path "/home/op/build/emacs-libpq/")
1.10.20. nov.el
nov
is a package to read epub from Emacs. It's really cool, and
integrates with both bookmarks and org-mode.
(use-package nov :mode ("\\.epub\\'" . nov-mode) :hook (nov-mode . op/novel-setup) :config (defun op/novel-setup () (interactive) (variable-pitch-mode +1) (olivetti-mode +1) (setq-local cursor-type 'bar)))
1.10.21. Toxe
Toxe is my try at writing a tox client in elisp. One day will be functional, I promise!
(use-package toxe :straight nil :load-path "~/w/toxe/")
1.10.22. rcirc
rcirc is the first (and only) Emacs IRC client I tried, and the only IRC client I use. I really like it out-of-the-box!
(use-package rcirc :straight nil :bind (("C-z i i" . rcirc)) :custom ((rcirc-buffer-maximum-lines 1000) (rcirc-log-flag t) (rcirc-omit-responses '("JOIN" "PART" "QUIT" "NICK" "AWAY")) (rcirc-fill-column 72) (rcirc-keywords '("godot" "poedit" "mu4e"))) :config (rcirc-track-minor-mode) (add-hook 'rcirc-mode-hook (lambda () (flyspell-mode 1) (rcirc-omit-mode))) (setq rcirc-default-nick "op2" rcirc-default-user-name "op" rcirc-default-full-name "op" rcirc-server-alist '(("irc.libera.chat" :channels ("#clojure" "#emacs" "#gemini-it" "#matrix" "#openbsd" "#openbsd-gaming" "#gameoftrees" "#postgresql" "#postgresql-it") :port 6697 :encryption tls)) rcirc-authinfo '(("libera" certfp "/home/op/.emacs.d/irc/key.pem" "/home/op/.emacs.d/irc/cert.pem"))))
1.10.23. circe
I briefly tried circe before going back to rcirc and adding support for certfp to it. Just for reference, here's the configuration I used for a day.
(use-package circe :bind (("C-z i i" . circe)) :config (tracking-mode +1) (setq tracking-position 'end tracking-most-recent-first t) (setq circe-reduce-lurker-spam t circe-format-say "{nick:-16s} {body}" circe-format-self-say "{nick} {body}" lui-time-stamp-position 'right-margin lui-time-stamp-format "%H:%M" lui-fill-type nil) ;; it's using a certificate... :D (setq circe-network-options '(("Libera" :host "irc.libera.chat" :port 6697 :tls t :tls-keylist (("/home/op/.emacs.d/irc/key.pem" "/home/op/.emacs.d/irc/cert.pem")) :sasl-external t :nick "op2" :user "op2" :realname "op" :channels ("#clojure" "#emacs" "#gemini-it" "#matrix" "#openbsd" "#openbsd-gaming" "#gameoftrees" "#postgresql" "#postgresql-it")))) (defun op/circe-set-margin () (setq fringes-outside-margins t right-margin-width 5 word-wrap t ;; yep, really... wrap-prefix " ") (setf (cdr (assoc 'continuation fringe-indicator-alist)) nil)) (add-hook 'lui-mode-hook #'op/circe-set-margin) ;; provides some command that automatically uses /msg ServOp behind ;; the scene, like /getop, /dropop, /mode, /bans, /kick (require 'circe-chanop) (require 'lui-logging) (enable-lui-logging-globally) (enable-lui-track))
1.10.24. telega
Telega is the best telegram client. period.
I'm using a non-standard recipe because the standard one doesn't
include the contrib/*
stuff (in particular I'm interested in the
org integration).
(use-package telega :straight (:type git :flavor melpa :files (:defaults "etc" "server" "Makefile" "telega-pkg.el" "contrib/ol-telega.el" "contrib/telega-url-shorten.el") :branch "master" :host github :repo "zevlg/telega.el") :custom ((telega-chat-input-markups '(nil "markdown1" "markdown2")) (telega-use-images t) (telega-emoji-font-family "Noto Color Emoji")) :hook ((telega-root-mode . telega-notifications-mode) ;; (telega-chat-mode . op/telega-enable-company) (telega-load-hook . global-telega-url-shorten-mode)) :bind-keymap ("C-c t" . telega-prefix-map) :config ;; if put in the :custom will fail, and now I have other things to ;; do. (setq telega-completing-read-function #'completing-read) (comment (defun op/telega-enable-company () (interactive) (company-mode +1) (set (make-local-variable 'company-backends) (append '(telega-company-emoji telega-company-username telega-company-hashtag) (when (telega-chat-bot-p telega-chatbuf--chat) '(telega-company-botcmd)))))) ;; for telega-url-shorten (use-package all-the-icons))
1.10.25. elfeed
Elfeed is a RSS reader for Emacs that rocks!
(use-package elfeed :bind (("C-x w" . elfeed) :map elfeed-show-mode-map ("q" . delete-window) ("S-SPC" . scroll-down-command) ("M-SPC" . scroll-down-command)) :custom (elfeed-feeds '("https://undeadly.org/cgi?action=rss&full=yes&items=10" "http://www.tedunangst.com/flak/rss" "https://www.dragonflydigest.com/feed" "https://www.mirbsd.org/news.rss" "https://www.mirbsd.org/announce.rss" "https://bentsukun.ch/index.xml" "https://drewdevault.com/feed.xml" "https://www.cambus.net/atom.xml" "https://dataswamp.org/~solene/rss.xml" "https://briancallahan.net/blog/feed.xml" "https://www.poolp.org/index.xml" "https://jcs.org/rss" "https://sanctum.geek.nz/arabesque/feed/" "https://tech.toryanderson.com/" "https://alexschroeder.ch/wiki?action=journal;search=-tag:rpg -tag:rsp;lang=en;title=English Diary without RPG Pages" "http://boston.conman.org/bostondiaries.rss" "https://emacsninja.com/feed.atom" "https://bsdly.blogspot.com/feeds/posts/default" "https://crawshaw.io/atom.xml" "https://nullprogram.com/feed/" "http://pragmaticemacs.com/feed/" "https://emacsnotes.wordpress.com/feed/" "https://metaredux.com/feed.xml" "https://emacsredux.com/atom.xml" "https://endlessparentheses.com/atom.xml" "https://www.masteringemacs.org/feed" "https://cestlaz.github.io/rss.xml" "https://utcc.utoronto.ca/~cks/space/blog/?atom" "https://irreal.org/blog/?feed=rss2" "https://jao.io/blog/rss.xml" "https://planet.lisp.org/rss20.xml" "https://insideclojure.org/feed.xml" "https://tech.toryanderson.com/index.xml" "https://vermaden.wordpress.com/feed/" "https://www.arp242.net/feed.xml" "https://tymoon.eu/api/reader/atom" "https://venam.nixers.net/blog/feed.xml" "https://www.omarpolo.com/rss.xml" "https://owarisubs.lacumpa.biz/feed/" "https://asenshi.moe/feed/" "https://godotengine.org/rss.xml" "https://github.com/go-gitea/gitea/releases.atom" "https://github.com/yshui/picom/releases.atom" "https://github.com/vslavik/poedit/releases.atom" "https://github.com/TokTok/c-toxcore/releases.atom" "https://github.com/alexander-akhmetov/python-telegram/releases.atom" "https://github.com/paul-nameless/tg/releases.atom" "https://github.com/YACReader/yacreader/releases.atom" "https://github.com/luarocks/luarocks/releases.atom" "https://github.com/okbob/pspg/releases.atom" "https://www.crimsonmagic.me/feed/" "https://fullybookedtls.wordpress.com/feed/")) :config (setq elfeed-show-entry-switch #'pop-to-buffer))
1.10.25.1. TODO find a way to integrate elfeed-org with this document
it would be sick!
1.10.26. firefox integration
There is a package, edit-server
, that let me edit textareas from
Emacs. I'm still trying to decide if it's worth or not.
(use-package edit-server :init (if after-init-time (edit-server-start) (add-hook 'after-init-hook #'edit-server-start)) :custom (edit-server-new-frame-alist '((name . "Edit with Emacs") (minibuffer . t))))
1.11. "smol" packages
These are stuff that I keep in emacs-user-directory/lisp
, but
that isn't useful enough to submit a package for it.
1.11.1. Scratchpads
Creates a new *scratchpad*
buffer on demand. When called with a
prefix argument, choose the mode!
;;; scratchpads.el --- create scratchpads -*- lexical-binding: t; -*- ;; Copyright (C) 2021 Omar Polo ;; Author: Omar Polo <op@omarpolo.com> ;; Keywords: convenience ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; Quickly create temp scratch buffer ;;; Code: (require 'cl-lib) (defun scratchpads--list-major-modes () "List all the major modes. Inspired from ieure/scratch-el. Naïve probably." (cl-loop for sym the symbols of obarray for name = (symbol-name sym) when (and (functionp sym) (not (member sym minor-mode-alist)) (string-match "-mode$" name) (not (string-match "--" name))) collect name)) (defun scratchpads--select-mode () "Select an appropriate major mode." (if current-prefix-arg (intern (concat (completing-read "Major Mode: " (scratchpads--list-major-modes) nil t nil nil))) major-mode)) ;;;###autoload (defun scratchpads-new-scratchpad (mode) "Create a new *scratch* buffer for the MODE." (interactive (list (scratchpads--select-mode))) (let ((buf (generate-new-buffer "*scratch*"))) (pop-to-buffer buf) (funcall mode))) (provide 'scratchpads) ;;; scratchpads.el ends here
1.11.2. clbin
Do you know what NIH syndrome is? It's the definition of this package!
;;; clbin.el --- post stuff to clbin -*- lexical-binding: t; -*- ;; Copyright (C) 2021 Omar Polo ;; Author: Omar Polo <op@omarpolo.com> ;; Keywords: comm ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; clbin.el posts something to clbin and saves the corresponding link ;; to the kill ring. ;;; Code: (defun clbin--do (start end) "Send the region from START to END to clbin, add the link to the `kill-ring'." (let ((temp-buffer (generate-new-buffer " *clbin*" t))) (unwind-protect (let ((res (call-process-region start end "curl" nil temp-buffer nil "-Fclbin=<-" "https://clbin.com"))) (cond ((zerop res) (with-current-buffer temp-buffer (goto-char (point-max)) (forward-line -1) (let ((start (line-beginning-position)) (end (line-end-position))) (kill-ring-save start end) (message "%s" (buffer-substring start end))))) (t (error "Subprocess (curl) failed")))) (and (buffer-name temp-buffer) (kill-buffer temp-buffer))))) ;;;###autoload (defun clbin-dwim () "Send to clbin the current (narrowed) buffer or the active region." (interactive) (if mark-active (clbin--do (point) (mark)) (clbin--do (point-min) (point-max)))) (provide 'clbin) ;;; clbin.el ends here
1.11.3. my-abbrev
Where I store the abbreviation I need.
;;; my-abbrev --- Abbrev stuff -*- lexical-binding: t; -*- ;;; Commentary: ;; This adds various abbrevs for various modes. Abbrevs are useful to ;; avoid typos, for instance. To prevent the expansion, type ``word ;; C-q SPC'' instead of ``word SPC''. ;;; Code: (clear-abbrev-table global-abbrev-table) (define-abbrev-table 'global-abbrev-table '(("prots@" "ports@") ("supprots" "supports") ("het" "the") ("teh" "the") ("wehn" "when") ("perchè" "perché") ("perche" "perché") ("nonchè" "nonché") ("nonche" "nonché") ("quetse" "queste") ("sovlgimento" "svolgimento") ("sovlgere" "svolgere") ("sbagilo" "sbaglio") ("caffe" "caffè"))) (when (boundp 'text-mode-abbrev-table) (clear-abbrev-table text-mode-abbrev-table)) (define-abbrev-table 'text-mode-abbrev-table '(("hw" "hardware") ("sw" "software"))) (when (boundp 'clojure-mode-abbrev-table) (clear-abbrev-table clojure-mode-abbrev-table)) (define-abbrev-table 'clojure-mode-abbrev-table '(("erq" "req"))) (when (boundp 'c-mode-abbrev-table) (clear-abbrev-table c-mode-abbrev-table)) (define-abbrev-table 'c-mode-abbrev-table '(("inculde" "include") ("inlcude" "include"))) ;; turn on abbrev mode globally (setq-default abbrev-mode t) (provide 'my-abbrev) ;;; my-abbrev.el ends here
1.11.4. my-modeline
This is my customized modeline. It hides informations that I don't find particularly interesting. It looks like this
Figure 1: screenshot of the modeline
;; -*- lexical-binding: t; -*- (defvar op/mode-line-format-bk mode-line-format "Backup of the default `mode-line-format'.") (setq-default mode-line-format '("%e" mode-line-front-space mode-line-mule-info mode-line-client mode-line-modified mode-line-remote mode-line-frame-identification mode-line-buffer-identification " " mode-line-position (vc-mode vc-mode) " " ;; mode-line-modes mode-line-misc-info mode-line-end-spaces)) (provide 'my-modeline) ;;; my-modeline.el ends here
1.11.5. minimal-light-theme
minimal-light-theme
is my personal fork of anler/minimal-theme.
Since I change it pretty often, I don't see how it could be useful
to others.
;;; minimal-light-theme.el --- A light/dark minimalistic Emacs 27 theme. -*- lexical-binding: t; -*- ;; Copyright (C) 2020 Omar Polo ;; Copyright (C) 2014 Anler Hp ;; Author: Anler Hp <anler86 [at] gmail.com> ;; Keywords: color, theme, minimal ;; X-URL: http://github.com/ikame/minimal-theme ;; URL: http://github.com/ikame/minimal-theme ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see <http://www.gnu.org/licenses/>. ;;; Commentary: ;; ;; A minimalistic color theme to avoid distraction with ;; colors. Based on monochrome theme. ;;; Code: (deftheme minimal-light "minimal light theme.") (let* ((class '((class color) (min-colors 89))) (foreground "#586e75") (background "#ffffff") ;; "#fdf6e3" (cursor "#333") (border "grey90") (minibuffer cursor) (region "grey85") (comment-delimiter "grey55") (comment "grey30") (constant foreground) (string "grey40") (modeline-foreground foreground) (modeline-background "#e1dbca") (modeline-foreground-inactive comment) (modeline-background-inactive background) (modeline-border-color "white") (isearch-background modeline-background) (hl-background "grey94") (hl-face-background nil) (failure "red") (org-background "grey94")) (custom-theme-set-faces 'minimal-light ;; basic stuff `(default ((,class (:background ,background :foreground ,foreground)))) `(fringe ((,class (:inherit default)))) `(cursor ((,class (:background ,cursor :inverse-video t)))) `(vertical-border ((,class (:foreground ,border)))) ;; minibuffer `(minibuffer-prompt ((,class (:foreground ,minibuffer :weight bold)))) ;; region `(region ((,class (:background ,region)))) `(secondary-selection ((,class (:background ,region)))) ;; faces `(fl:builtin-face ((,class (:foreground ,foreground :weight bold)))) `(fl:constant-face ((,class (:foreground ,foreground :weight bold)))) `(fl:keyword-face ((,class (:foreground ,foreground :weight bold)))) `(fl:type-face ((,class (:foreground ,foreground :slant italic)))) `(fl:function-name-face ((,class (:foreground ,foreground :weight bold)))) `(fl:variable-name-face ((,class (:foreground ,foreground)))) `(fl:comment-delimiter-face ((,class (:foreground ,comment-delimiter)))) `(fl:comment-face ((,class (:foreground ,comment :slant italic)))) `(fl:doc-face ((,class (:inherit (fl:comment-face))))) `(fl:string-face ((,class (:foreground ,foreground :foreground ,string)))) ;; faces used by isearch `(isearch ((,class (:foreground ,foreground :background ,isearch-background :weight normal)))) `(isearch-fail ((,class (:foreground ,failure :bold t)))) `(lazy-highlight ((,class (:foreground ,foreground :background ,region)))) ;; flymake-error `(flymake-error ((,class :underline (:style line :color "Red1")))) ;; ido-mode `(ido-subdir ((,class (:foreground ,foreground :weight bold)))) `(ido-only-match ((,class (:foreground ,foreground :weight bold)))) ;; show-paren `(show-paren-match ((,class (:inherit highlight :underline (:color ,foreground :style line))))) `(show-paren-mismatch ((,class (:foreground ,failure :weight bold)))) `(show-paren-match-expression ((,class (:inherit default :background "#eee")))) ;; highlight-sexp `(hl-sexp-face ((,class (:inherit show-paren-match-expression)))) ;; tab-bar `(tab-bar ((,class :background ,modeline-background))) `(tab-bar-tab ((,class :background ,modeline-background-inactive :box (:line-width 4 :color ,modeline-background-inactive)))) `(tab-bar-tab-inactive ((,class :background ,modeline-background :box (:line-width 4 :color ,modeline-background)))) ;; help. Don't print funny boxes around keybindings `(help-key-binding ((,class))) ;; modeline `(mode-line ((,class (:inverse-video unspecified :overline ,border :underline nil :foreground ,modeline-foreground :background ,modeline-background :overline ,border :box (:line-width 3 :color ,modeline-background))))) `(mode-line-buffer-id ((,class (:weight bold)))) `(mode-line-inactive ((,class (:inverse-video unspecified :overline ,border :underline nil :foreground ,modeline-foreground-inactive :background ,modeline-background-inactive :box (:line-width 3 :color ,background))))) ;; hl-line-mode `(hl-line ((,class (:background ,hl-background)))) `(hl-line-face ((,class (:background ,hl-face-background)))) ;; highlight-stages-mode `(highlight-stages-negative-level-face ((,class (:foreground ,failure)))) `(highlight-stages-level-1-face ((,class (:background ,org-background)))) `(highlight-stages-level-2-face ((,class (:background ,region)))) `(highlight-stages-level-3-face ((,class (:background ,region)))) `(highlight-stages-higher-level-face ((,class (:background ,region)))) ;; org-mode `(org-level-1 ((,class (:foreground ,foreground :height 1.6)))) `(org-level-2 ((,class (:foreground ,foreground :height 1.5)))) `(org-level-3 ((,class (:foreground ,foreground :height 1.4)))) `(org-level-4 ((,class (:foreground ,foreground :height 1.3)))) `(org-level-5 ((,class (:foreground ,foreground :height 1.2)))) `(org-level-6 ((,class (:foreground ,foreground :height 1.1)))) `(org-level-7 ((,class (:foreground ,foreground)))) `(org-level-8 ((,class (:foreground ,foreground)))) `(org-ellipsis ((,class (:inherit org-ellipsis :underline nil)))) `(org-table ((,class (:inherit fixed-pitch)))) `(org-meta-line ((,class (:inherit (fl:comment-face fixed-pitch))))) `(org-property-value ((,class (:inherit fixed-pitch))) t) `(org-verbatim ((,class (:inherit (shadow fixed-pitch))))) `(org-quote ((,class (:slant italic)))) `(org-document-title ((,class (:foreground ,foreground)))) `(org-link ((,class (:foreground ,foreground :underline t)))) `(org-tag ((,class (:background ,org-background :foreground ,foreground)))) `(org-warning ((,class (:background ,region :foreground ,foreground :weight bold)))) `(org-todo ((,class (:weight bold)))) `(org-done ((,class (:weight bold)))) `(org-headline-done ((,class (:foreground ,foreground)))) `(org-table ((,class (:background ,org-background)))) `(org-code ((,class (:background ,org-background)))) `(org-date ((,class (:background ,org-background :underline t)))) `(org-block ((,class (:background ,org-background)))) `(org-block-background ((,class (:background ,org-background :foreground ,foreground)))) `(org-block-begin-line ((,class (:background ,org-background :foreground ,comment-delimiter :weight bold)))) `(org-block-end-line ((,class (:background ,org-background :foreground ,comment-delimiter :weight bold)))) ;; outline `(outline-1 ((,class (:inherit org-level-1)))) `(outline-2 ((,class (:inherit org-level-2)))) `(outline-3 ((,class (:inherit org-level-3)))) `(outline-4 ((,class (:inherit org-level-4)))) `(outline-5 ((,class (:inherit org-level-5)))) `(outline-6 ((,class (:inherit org-level-6)))) `(outline-7 ((,class (:inherit org-level-7)))) `(outline-8 ((,class (:inherit org-level-8)))) ;; js2-mode `(js2-external-variable ((,class (:inherit base-faces :weight bold)))) `(js2-function-param ((,class (:inherit base-faces)))) `(js2-instance-member ((,class (:inherit base-faces)))) `(js2-jsdoc-html-tag-delimiter ((,class (:inherit base-faces)))) `(js2-jsdoc-html-tag-name ((,class (:inherit base-faces)))) `(js2-jsdoc-tag ((,class (:inherit base-faces)))) `(js2-jsdoc-type ((,class (:inherit base-faces :weight bold)))) `(js2-jsdoc-value ((,class (:inherit base-faces)))) `(js2-magic-paren ((,class (:underline t)))) `(js2-private-function-call ((,class (:inherit base-faces)))) `(js2-private-member ((,class (:inherit base-faces)))) ;; sh-mode `(sh-heredoc ((,class (:inherit base-faces :slant italic)))) ;; telega `(telega-msg-heading ((,class (:inherit base-faces :underline ,comment-delimiter :foreground ,comment)))) `(telega-msg-user-title ((,class (:inherit telega-msg-heading)))) `(telega-msg-inline-reply ((,class (:inherit telega-msg-heading :slant italic)))) ;; objed `(objed-hl ((,class (:background ,region)))) ;; circe `(circe-prompt-face ((,class (:inherit default)))))) ;;;###autoload (when (and (boundp 'custom-theme-load-path) load-file-name) (add-to-list 'custom-theme-load-path (file-name-as-directory (file-name-directory load-file-name)))) (provide-theme 'minimal-light) ;;; minimal-light-theme.el ends here
1.11.6. even-more-cua
Even more cua is something I wrote for someone who wanted to try Emacs but needed "CUA" keys. It extends a bit cua-mode, and it's a bit opinionated. I don't have a use for it anymore, but anyway.
;; -*- lexical-binding: t; -*- (cua-mode +1) (setq mouse-drag-and-drop-region t) (define-key global-map (kbd "C-s") 'save-buffer) (define-key global-map (kbd "C-f") 'isearch-forward) (define-key global-map (kbd "C-q") 'save-buffers-kill-terminal) (define-key global-map (kbd "S-<mouse-1>") (lambda (arg e) (interactive "P\ne") (unless (region-active-p) (set-mark-command arg)) (mouse-set-point e))) (defun emc--move-text-impl (arg) "Move text up or down by ARG lines. If ARG is negative, the text will be moved up. ``text'' means the current line or the current region if its active. The region is automatically extended to the beginning of the first line and to the end of the last line." (atomic-change-group (cond ((and mark-active transient-mark-mode) (if (> (point) (mark)) (exchange-point-and-mark)) ;; extend the region to the beginning of the first line/end of ;; the last (when (not (bolp)) (move-beginning-of-line nil)) (exchange-point-and-mark) (when (not (bolp)) (next-line) (move-beginning-of-line nil)) (exchange-point-and-mark) (let ((column (current-column)) (text (delete-and-extract-region (point) (mark)))) (forward-line arg) (move-to-column column t) (set-mark (point)) (insert text) (exchange-point-and-mark) (setq deactivate-mark nil))) (t (let ((column (current-column))) (beginning-of-line) (when (or (> arg 0) (not (bobp))) (forward-line) (when (or (< arg 0) (not (eobp))) (transpose-lines arg)) (forward-line -1)) (move-to-column column t)))))) (define-key global-map (kbd "C-S-<up>") (lambda (arg) (interactive "*p") (emc--move-text-impl (- arg)))) (define-key global-map (kbd "C-S-<down>") (lambda (arg) (interactive "*p") (emc--move-text-impl arg))) (provide 'even-more-cua) ;;; even-more-cua.el ends here