💾 Archived View for gemini.omarpolo.com › dots › emacs.gmi captured on 2021-12-17 at 13:26:06. Gemini links have been rewritten to link to archived content
⬅️ Previous capture (2021-12-03)
-=-=-=-=-=-=-
Table of Contents
_________________
1. Emacs stuff
.. 1. early-init.el
.. 2. Preamble
.. 3. straight
.. 4. org
........ 1. TODO wasn't valign included into ELPA?
..... 1. org-roam
..... 2. Presentations in org-mode
..... 3. Org publish
.. 5. Misc
..... 1. Font
..... 2. tab-bar
..... 3. bookmarks
..... 4. save the place
..... 5. history
..... 6. Uniquify
..... 7. Hydra
..... 8. desktop.el
..... 9. recentf
..... 10. Gemini for `thingatpoint'
..... 11. browse-url
..... 12. variable pitch mode (aka non monospace)
..... 13. form-feed
.. 6. Minibuffer
..... 1. Marginalia
..... 2. Orderless
..... 3. Consult
..... 4. Affe
..... 5. Vertico
..... 6. Selectrum
..... 7. embark
.. 7. Completions
.. 8. Window management
..... 1. The window package
..... 2. Placement with shackle
..... 3. History
..... 4. Switch window
..... 5. Layouts
..... 6. Side windows
..... 7. Ace window
.. 9. Text editing
..... 1. Misc
..... 2. Auto-saving
..... 3. imenu
..... 4. Filling
..... 5. Transpose
........ 1. TODO [cycle-region] is worth a try
..... 6. Narrow to what I mean
..... 7. White spaces
..... 8. Version Control
........ 1. Backups
........ 2. Log
........ 3. Got
..... 9. Auto insert mode
..... 10. DIRED
..... 11. Project
........ 1. TODO add some mechanism to ignore files
..... 12. Scratchpads
..... 13. Occur & loccur
..... 14. hideshow
..... 15. Smartparens
........ 1. TODO the configuration is quite long, can it be made modular?
..... 16. Flymake
..... 17. Flyspell and friends
........ 1. guess language
..... 18. Typo(graphical stuff)
..... 19. hippie expand
..... 20. isearch
..... 21. etags
..... 22. view mode
..... 23. pdf-tools
..... 24. avy
..... 25. iedit
..... 26. editorconfig
..... 27. Compilation
..... 28. Languages
........ 1. jump to matching paren
........ 2. eglot
........ 3. prog-mode
........ 4. text-mode
........ 5. diff
........ 6. elisp
........ 7. Common LISP
........ 8. Clojure
........ 9. Scheme
........ 10. Elastic search mode
........ 11. SQL
........ 12. nxml
........ 13. web
........ 14. CSS
........ 15. javascript
........ 16. C
........ 17. Go
........ 18. Perl
........ 19. Python
........ 20. sh-mode
........... 1. TODO fix smartparens and sh' `case'
........ 21. Lua
........ 22. GDScript
........ 23. YAML
........ 24. TOML
........ 25. Gemini (text/gemini)
........ 26. Markdown
.. 10. Applications
..... 1. bins
..... 2. sam for the rescue!
..... 3. eshell
........ 1. TODO make `>>>' ignore `stderr'
..... 4. vterm ― when eshell isn't enough
..... 5. ibuffer
..... 6. help
..... 7. gdb
..... 8. Literate calc-mode
..... 9. keycast
..... 10. email
........ 1. TODO split the following into pieces
........ 2. attach multiple files
..... 11. news
..... 12. pass
..... 13. vmd
..... 14. sndio
..... 15. EMMS
..... 16. eww
..... 17. exwm
..... 18. elpher
..... 19. pq
..... 20. nov.el
..... 21. Toxe
..... 22. rcirc
..... 23. circe
..... 24. telega
..... 25. elfeed
........ 1. TODO find a way to integrate elfeed-org with this document
..... 26. firefox integration
.. 11. "smol" packages
..... 1. Scratchpads
..... 2. clbin
..... 3. my-abbrev
..... 4. my-modeline
..... 5. minimal-light-theme
..... 6. even-more-cua
Here be dragons.
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)))
;; -*- 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)
<2021-06-12 Sat> `$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)
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.
<2021-09-11 Sat> straight keeps loading `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)
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)))
;; 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)))
(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))))
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))
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)
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.
<2021-07-29 Thu> 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)
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)
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)))
`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))
`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))
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)))
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))
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'.
<2021-06-16 Wed> I've disabled `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)))
(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)
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://"))
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)))
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)
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))
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)
Enhances the minibuffer completions with additional informations
(use-package marginalia :custom (marginalia-annotators '(marginalia-annotators-heavy marginalia-annotators-light nil)) :init (marginalia-mode))
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)))))))
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 "")))))
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)))
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
<2021-06-08 Tue> vertico is *too damn slow* here: `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].
<2021-11-04 Thu> giving it another try
(use-package vertico :config (vertico-mode))
<2021-11-04 Thu> 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)
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)))
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))
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.
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))))))
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))
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)))
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)
`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)))
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.
(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)))
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)))
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.
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)
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)
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)
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)
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)))
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)))
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)))
[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"))
`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>"))
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\\`"))
(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))
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)
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)))
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)
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))))
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)))
Flyspell is Flymake, but for natural languages! /s
(add-hook 'text-mode-hook #'flyspell-mode)
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))
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)
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))
Some very small tweaks for isearch
(setq isearch-lazy-count t search-whitespace-regexp ".*?" isearch-allow-scroll 'unlimited)
Reload tags without asking
(setq tags-revert-without-query 1)
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)))
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?
<2021-06-23 Wed> see [this post] to hints on how to integrate it with AucTeX.
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)))
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)))))))
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))
`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...
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))))))
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} > $@
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)
Enable abbrev-mode in text buffers:
(add-hook 'text-mode-hook #'abbrev-mode)
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))
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)
I'm trying to use this convention for repls:
(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))))
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))))))
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"))
`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))
`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)
`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)
`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))
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))
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)))
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:
(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))
My go configuration is simple: just load `go-mode'!
(use-package go-mode :mode "\\.go\\'" :hook ((go-mode . subword-mode)))
Just require `perl-mode' and ensure we indent with hard tabs
(use-package perl-mode :straight nil :custom ((perl-indent-level 8)))
Load `python-mode' and disable hard tabs:
(use-package python :hook ((python-mode . op/disable-tabs)))
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)))
In a case statement, we have un-paired closed parethesis that require `C-q' to be typed because of `sp-strict-mode'.
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"))))
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!
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)))
(use-package toml-mode :mode "\\.toml\\'")
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)
Install `markdown-mode' and enable auto fill.
(use-package markdown-mode :mode "\\.md\\'" :hook ((markdown-mode . auto-fill-mode)))
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)
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))
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/")
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*'.
<2021-06-16 Wed> Instead of `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))
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))
Ibuffer is a package to list, filter and do stuff on the buffers.
(define-key global-map (kbd "C-x C-b") #'ibuffer)
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)
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)
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)
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))
doas pkg_add mu4e
(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!"))))
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)
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:
etc...
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)))
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))))
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)))
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)))
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 '(".*"))))
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)))
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))
`pq' is a postgres library module for elisp. Sound crazy, hu?
(use-package pq :straight nil :load-path "/home/op/build/emacs-libpq/")
`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)))
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/")
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"))))
<2021-11-11 Thu> 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))
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))
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))
it would be sick!
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))))
These are stuff that I keep in `emacs-user-directory/lisp', but that isn't useful enough to submit a package for it.
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
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
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
This is my customized modeline. It hides informations that I don't find particularly interesting. It looks like this
;; -*- 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
`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 `(font-lock-builtin-face ((,class (:foreground ,foreground :weight bold)))) `(font-lock-constant-face ((,class (:foreground ,foreground :weight bold)))) `(font-lock-keyword-face ((,class (:foreground ,foreground :weight bold)))) `(font-lock-type-face ((,class (:foreground ,foreground :slant italic)))) `(font-lock-function-name-face ((,class (:foreground ,foreground :weight bold)))) `(font-lock-variable-name-face ((,class (:foreground ,foreground)))) `(font-lock-comment-delimiter-face ((,class (:foreground ,comment-delimiter)))) `(font-lock-comment-face ((,class (:foreground ,comment :slant italic)))) `(font-lock-doc-face ((,class (:inherit (font-lock-comment-face))))) `(font-lock-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 (font-lock-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
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