* Preface This is an experiment to find out whether [[https://en.wikipedia.org/wiki/Literate_programming][literate configurations]] in a single monolithic [[http://orgmode.org/][org-mode]] file actually have a perceivable benefit. I'm using this configuration on a system running [[https://www.archlinux.org/][Arch Linux]] and Emacs 28.1, that's why this configuration will be using the latest and greatest features and /not/ check for the system it's running on. While I do use =emacsclient= for most of my editing needs, I occasionally open =emacs= instances for things like IRC or testing purposes. To debug it, I've added a properties drawer to the following subtree that exports the code blocks to =.emacs= with =M-x org-babel-tangle=. Why =.emacs=? This file is kickstarted by [[./init.el][init.el]], so the other init file location was chosen. There's some more files of interest: - [[./unpublished/][Unpublished stuff]] - [[./theme/my-solarized-theme.el][My former theme]] - [[./TODO.org][List of things to hack on]] - [[./INSTALL][Installation notes]] and [[./install.el][script]] Please don't use this Emacs configuration as is. I suggest you to study it instead and inspect the many variables involved with the built-in help system by hitting =F1 v=. Here's a list of other literate Emacs configurations I've drawn inspiration from: - http://writequit.org/org/ - http://doc.rix.si/org/fsem.html - https://postmomentum.ch/steckemacs.html - http://pages.sachachua.com/.emacs.d/Sacha.html - https://github.com/larstvei/dot-emacs/blob/master/init.org * Init :PROPERTIES: :header-args:emacs-lisp: :tangle ../.emacs :END: ** User Interface Emacs comes with a set of pretty wonky defaults, since the UI is what we'll see first, we'll deal with it swiftly. Part of its configuration can be set up in =~/.Xresources= and has the effect to be used before any frame is displayed for the price of less flexibility. For experimentation, reload such configuration with =xrdb ~/.Xresources=. Keep in mind that this hack might not do anything for you on a sufficiently fast machine that loads themes faster than you can notice. *** Superfluous UI elements I prefer not dealing with menu bars, tool bars and scroll bars and deactivate them in =~/.Xresources=: #+BEGIN_SRC conf Emacs.menuBar: off Emacs.toolBar: off Emacs.verticalScrollBars: off #+END_SRC *** Stop cursor blinking This used to give me nightmares, especially with the 24.4 addition that made the cursor blink ten times, then stops blinking until moving it again. #+BEGIN_SRC conf Emacs.cursorBlink: off #+END_SRC *** Less jarring background change The default background color chosen is white, however I'm using a dark theme. Changing the background color after frame creation results in flashing, therefore I modify the background color to equal the one of the theme I'm going to load later. #+BEGIN_SRC conf Emacs.background: #002b36 #+END_SRC The only other UI element left that stays visible throughout the entire init time would be the mode line. It took me a bit of puzzling to figure out the right format for making it look the same as in my theme, but I eventually figured out from reading the last paragraph of the =(emacs) Table of Resources= info node that it does read in a list from a string by studying the sources of =faces.el= and =xfaces.c=. While this sounds kind of obvious, it means one can debug errors for more complex values like the one for the box by simply invoking ~read-from-string~ on them. #+BEGIN_SRC conf Emacs.mode-line.attributeForeground: #93a1a1 Emacs.mode-line.attributeBackground: #002b36 Emacs.mode-line.attributeBox: (:line-width 1 :color "#073642") #+END_SRC *** Load theme I wrote a custom theme and want it to apply on each created frame: #+BEGIN_SRC emacs-lisp (add-to-list 'custom-theme-load-path "~/code/elisp/mine/punpun-theme") (add-to-list 'load-path "~/code/elisp/mine/punpun-theme") (defun my-load-theme (&optional frame) (with-selected-frame (or frame (selected-frame)) (load-theme 'punpun-light t))) (my-load-theme) (add-hook 'after-make-frame-functions 'my-load-theme) #+END_SRC Let's disable questions about theme loading while we're at it. #+BEGIN_SRC emacs-lisp (setq custom-safe-themes t) #+END_SRC Tooltips can be themed as well. #+BEGIN_SRC emacs-lisp (setq x-gtk-use-system-tooltips nil) #+END_SRC *** Improve the mode line The mode line is essentially a huge nested list of mode line items you can customize to achieve your prefered look. The simplest way of getting a more useful mode line is using the [[https://github.com/Bruce-Connor/smart-mode-line][smart-mode-line]] package which comes with more useful and color-coded items and a few interesting features like shortening file names and minor modes. #+BEGIN_SRC emacs-lisp (setq sml/theme 'respectful sml/mode-width 'full sml/name-width '(0 . 20) sml/replacer-regexp-list '(("^~/notes/" ":N:") ("^~/\\.emacs\\.d/" ":ED:"))) #+END_SRC However I prefer explicitly whitelisting the flycheck/flymake/flyspell family of minor modes. #+BEGIN_SRC emacs-lisp (setq rm-whitelist "Fly.*") #+END_SRC *** Disable advertisements The first obvious thing one notices upon launching an uncustomized Emacs is a rather fancy splash screen that informs you about the usage and advertises for [[https://www.gnu.org/][the GNU project]]. I did eventually grow annoyed by it. #+BEGIN_SRC emacs-lisp (setq inhibit-startup-screen t) #+END_SRC A less obvious one is the advertisement message displayed after successful startup in the echo area. The culprit behind it is ~display-startup-echo-area-message~ and goes great lengths to make sure it's seen by first checking whether the ~inhibit-startup-echo-area-message~ has been set by the =customize= system to your user name, then scanning your init file with a regular expression for it. Considering I dislike using the =customize= system, don't have a conventional init file and find this pretty silly, I disable this behaviour entirely by redefining the function to display a bit more encouraging message instead. #+BEGIN_SRC emacs-lisp (defun display-startup-echo-area-message () (message "Let the hacking begin!")) #+END_SRC *** Adjust keystroke echo timeout This is a built-in feature I didn't expect to be useful. If you type part of keybind, Emacs will display this part in the echo area after a timeout. One second is a bit too long though for my taste. #+BEGIN_SRC emacs-lisp (setq echo-keystrokes 0.5) #+END_SRC *** Customize font-specific faces I can't put these into every theme I use, so it's time to change the special =user= theme: #+BEGIN_SRC emacs-lisp (custom-theme-set-faces 'user '(default ((t :font "DejaVu Sans Mono:pixelsize=14"))) '(variable-pitch ((t :family "Cambria" :height 1.3)))) #+END_SRC ** Emacs annoyances Every file stating "This file is part of GNU Emacs." is more often than not a source of code that may be crufty, nausea-inducing or just having weird defaults that I need to correct. *** Memory Management 800 KiB heap size makes for major pauses during init, so we'll temporarily increase it, then reset it after init is done to minimize GC pause times. #+BEGIN_SRC emacs-lisp (setq gc-cons-threshold 10000000) #+END_SRC *** Fix ~line-number-mode~ ~line-number-mode~ displays the current line number in the mode line, however it stops doing that in buffers when encountering at least one overly long line and displays two question marks instead. This is pretty unhelpful, the only workaround I've been able to find was to increase ~line-number-display-limit-width~ to a substantially higher value. #+BEGIN_SRC emacs-lisp (setq line-number-display-limit-width 2000000) #+END_SRC See also [[http://emacs.stackexchange.com/questions/3824/what-piece-of-code-in-emacs-makes-line-number-mode-print-as-line-number-i][this question]] on [[http://emacs.stackexchange.com/][the Emacs SE]]. *** GnuTLS I have no idea why, but apparently you get nasty warnings by [[http://gnutls.org/][the GnuTLS library]] when using https with the default settings. Increasing the minimum prime bits size to something safer alleviates that. Another fun thing is that it allows legacy algorithms like 3DES by default, so let's disable them as well. #+BEGIN_SRC emacs-lisp (setq gnutls-min-prime-bits 2048) (setq gnutls-algorithm-priority "SECURE128") #+END_SRC *** Scratch Since the =*scratch*= buffer is pretty hard-wired into Emacs (see =buffer.c=), the least we could do is getting rid of its initial message. No, it's using its own mode instead of ~emacs-lisp-mode~ for the questionable benefit of having a function inserting evaluation values after a newline. #+BEGIN_SRC emacs-lisp (setq initial-scratch-message "") (setq initial-major-mode 'emacs-lisp-mode) #+END_SRC *** Initial buffer However I don't want to see the scratch buffer, let's display our notes file instead as daily reminder what's left to do. #+BEGIN_SRC emacs-lisp (setq remember-notes-bury-on-kill nil) (setq remember-notes-initial-major-mode 'org-mode) (setq initial-buffer-choice 'remember-notes) #+END_SRC There is a bit of mismatch between the keybindings of ~remember-notes-mode~ and ~org-mode~, so let's fix that: #+BEGIN_SRC emacs-lisp (with-eval-after-load 'remember (define-key remember-notes-mode-map (kbd "C-c C-c") nil)) #+END_SRC *** Find C functions There's a fair number of Emacs functions that aren't written in Emacs Lisp (see [[https://www.openhub.net/p/emacs][these statistics]]). To be able to locate them, it's necessary to grab a tarball of the sources at http://ftp.gnu.org/gnu/emacs/ and put it into your prefered location. #+BEGIN_SRC emacs-lisp (setq find-function-C-source-directory "~/code/emacsen/emacs-27.1/src") #+END_SRC *** Shorten Yes/No prompts Per default you're required to type out a full "yes" or "no" whenever the function ~yes-or-no-p~ is invoked. Fixing this used to require swapping it out for ~y-or-n-p~, but no longer does as of Emacs 28.1: #+BEGIN_SRC emacs-lisp (setq use-short-answers t) #+END_SRC *** Open URLs with =xdg-open= I've set up =xdg-open= to use my prefered browser for HTTP and HTTPS URLs. Emacs claims to detect whether my system can use it, however this fails because I don't have a popular DE up and running (I kid you not, look at ~browse-url-can-use-xdg~ and how it replicates that part from the =xdg-open= script). #+BEGIN_SRC emacs-lisp (setq browse-url-browser-function 'browse-url-xdg-open) #+END_SRC *** Zero out default splitting tresholds I have no idea how this actually works, but it seems to make Emacs prefer doing a horizontal split over a vertical split on wide screens. #+BEGIN_SRC emacs-lisp (setq split-height-threshold 0 split-width-threshold 0) #+END_SRC *** Unique buffer names This shouldn't be necessary since I'm already using =smart-mode-line=, however it's better to use a less confusing style than the default that puts brackets around the buffer names shared in Emacs. #+BEGIN_SRC emacs-lisp (setq uniquify-buffer-name-style 'forward) #+END_SRC *** Inhibit =custom= littering my init file For whatever reason the customization system will write into your init file which is especially annoying if you have it in version control like I do. It's reasonably simple to deactivate this behaviour by customizing customize into using a dedicated file, however you'll need to both delete the lines it wrote and load it later to make it aware it has already been loaded successfully. #+BEGIN_SRC emacs-lisp (setq custom-file "~/.config/emacs/etc/custom.el") #+END_SRC *** Display .nfo files with appropriate code page Since Emacs auto-detection of encodings is quite good, but not omniscient, we'll give it a nudge to display these files the way they're supposed to be. #+BEGIN_SRC emacs-lisp (add-to-list 'auto-coding-alist '("\\.nfo\\'" . ibm437)) #+END_SRC *** Fix scrolling Half-page scrolling is great at reducing bandwidth, but is very jarring when done automatically. The following settings will make Emacs scroll line by line, without scrolloff and try to keep point at the same visual place when scrolling by page. I used to have scrolloff enabled here with the ~scroll-margin~ variable, but it introduced pretty nasty scrolling behaviour for large files, so I no longer do. #+BEGIN_SRC emacs-lisp (setq scroll-conservatively 10000 scroll-preserve-screen-position t) #+END_SRC *** Indent with spaces by default Most programming languages I work with prefer spaces over tabs. Note how this is not a mode, but a buffer-local variable. #+BEGIN_SRC emacs-lisp (setq-default indent-tabs-mode nil) #+END_SRC *** Manage Backup, autosave and lock files Backup files are created on save in the same directory as the file and end in =~=. They can be numbered which makes most sense combined with a different save location and automatic pruning. #+BEGIN_SRC emacs-lisp (setq backup-directory-alist '(("." . "~/.config/emacs/backup"))) (setq version-control t) (setq delete-old-versions t) #+END_SRC Autosave files are created between saves after a sufficient timeout in the current directory for crash detection, they begin and end with =#=. Let's change their save location as well. #+BEGIN_SRC emacs-lisp (setq auto-save-list-file-prefix "~/.config/emacs/autosave/") (setq auto-save-file-name-transforms '((".*" "~/.config/emacs/autosave/" t))) #+END_SRC Lock files are put in the same directory to detect multiple users trying to edit the same file. Given that I'm on a single-user system, I can migrate them elsewhere as of Emacs 28.1. #+BEGIN_SRC emacs-lisp (setq lock-file-name-transforms '((".*" "~/.config/emacs/lock/" t))) #+END_SRC *** Allow for multiple Emacs daemons Although I'm pretty sure I won't make use of this, I prefer using local TCP connections over socket files. Another benefit of this setting is that it would allow me to make use of =emacsclient= to access a remote Emacs daemon. #+BEGIN_SRC emacs-lisp (setq server-use-tcp t) #+END_SRC *** Stop pasting at the mouse click point Middle-clicking is nice to paste, however it should not adjust point and paste at the then adjusted point. #+BEGIN_SRC emacs-lisp (setq mouse-yank-at-point t) #+END_SRC *** Display buffer name in frame titles The default is to display the invocation name and host. Changing that to use a different separator and the buffer name is trivial, however there's still an annoying space in front when using =M-:=. Regular expressions to the rescue! #+BEGIN_SRC emacs-lisp (setq frame-title-format '("" invocation-name ": " (:eval (replace-regexp-in-string "^ +" "" (buffer-name))))) #+END_SRC *** Disable parentheses blinking on entering a match This will be done by a different package anyways, therefore we don't need it. #+BEGIN_SRC emacs-lisp (setq blink-matching-paren nil) #+END_SRC *** Display fringe indicators and fix line movement in ~visual-line-mode~ It's pretty nice to have the option to display words in a buffer as if they were hardwrapped around the word boundaries, however it's confusing to not have any fringe indicators. #+BEGIN_SRC emacs-lisp (setq visual-line-fringe-indicators '(left-curly-arrow right-curly-arrow)) #+END_SRC I don't like the remappings done to operate on visual lines (for =C-a=, =C-e= and =C-k=), so I'm just undefining them. #+BEGIN_SRC emacs-lisp (setcdr visual-line-mode-map nil) #+END_SRC *** Enable every deactivated command The rationale for this default seems to be to avoid confusion for beginners, I personally find it kind of annoying that Emacs of all editors does this kind of thing and doesn't offer a straightforward option to disable it even. #+BEGIN_SRC emacs-lisp (setq disabled-command-function nil) #+END_SRC *** Save clipboard data of other programs in the kill ring when possible I hope the necessity of this will be gone once Wayland is a viable option for me. #+BEGIN_SRC emacs-lisp (setq save-interprogram-paste-before-kill t) #+END_SRC *** Make recentering behave more similiar to other programs Try it out yourself by hitting =C-l=, it will start with the top instead of the middle row. #+BEGIN_SRC emacs-lisp (setq recenter-positions '(top middle bottom)) #+END_SRC *** Make =kill -USR1= do something useful A lesser known fact is that sending the =USR2= signal to an Emacs process makes it proceed as soon as possible to a debug window. =USR1= is ignored however, so let's bind it to an alternative desirable function that can be used on an Emacs instance that has locked up. #+BEGIN_SRC emacs-lisp (defun my-quit-emacs-unconditionally () (interactive) (my-quit-emacs '(4))) (define-key special-event-map (kbd "") 'my-quit-emacs-unconditionally) #+END_SRC *** Don't use dialog boxes Clicking on an install button for instance makes Emacs spawn dialog boxes from that point on. #+BEGIN_SRC emacs-lisp (setq use-dialog-box nil) #+END_SRC *** Don't print integers in multiple formats I'm not sure why this is handled this way in the first place. Turns out that ~eval-expression-print-format~ is responsible and signals a non-integer by returning =nil=. #+BEGIN_SRC emacs-lisp (fset 'eval-expression-print-format 'ignore) #+END_SRC *** Inspect complicated data When using =F1 v= or friends to view complicated data (such as an Org parse buffer), Emacs can take a long time to finish. The =data-debug= library offers a replacement for =M-:= for that: #+BEGIN_SRC emacs-lisp (require 'data-debug) (global-set-key (kbd "C-:") 'data-debug-eval-expression) #+END_SRC *** Print backtraces in a lispier form I wrote a patch to fix the oversight of function calls in a backtrace not being a list. The following will only work in Emacs 26: #+BEGIN_SRC emacs-lisp (setq debugger-stack-frame-as-list t) #+END_SRC *** Display raw bytes as hex Another patch of mine that will have effect in Emacs 26. Name says it all. #+BEGIN_SRC emacs-lisp (setq display-raw-bytes-as-hex t) #+END_SRC *** Disable signal handler insanity Emacs tries saving your buffers if it receives a fatal signal (including module segfaults). This is batshit insane, I prefer a clean exit over silent corruption. The following setting is supposed to make it so: #+BEGIN_SRC emacs-lisp (setq attempt-stack-overflow-recovery nil) (setq attempt-orderly-shutdown-on-fatal-signal nil) #+END_SRC *** ~comment-dwim~ isn't DWIM enough All IDEs I've tried have a comment toggle command (typically mapped to =C-/=), however it only does operate on full lines instead of adding an in-line comment if no region is selected. While Emacs already has the ~comment-line~ command, it doesn't work correctly with Evil, so I wrote my own version of it: #+BEGIN_SRC emacs-lisp (defun my-comment-dwim () (interactive) (if (use-region-p) (comment-or-uncomment-region (region-beginning) (region-end)) (comment-or-uncomment-region (line-beginning-position) (line-end-position)))) (global-set-key (kbd "M-;") 'my-comment-dwim) #+END_SRC *** Unconditionally kill subprocesses at exit #+BEGIN_SRC emacs-lisp (setq confirm-kill-processes nil) #+END_SRC *** Sentence spacing RIP double-spacing sentences. #+BEGIN_SRC emacs-lisp (setq sentence-end-double-space nil) #+END_SRC ** Packages bundled with Emacs This includes stuff that is bundled with Emacs and can be obtained from a more recent source as well, such as =org-mode=. I'm mostly refering to smaller packages though. *** =recentf= ~recentf-mode~ allows you to access the list of recent files which can be used by =ido= and =helm=. Let's save its file somewhere else and change the size of its history while we're at it. #+BEGIN_SRC emacs-lisp (setq recentf-save-file "~/.config/emacs/etc/recentf" recentf-max-saved-items 50) #+END_SRC Editing with =emacsclient= records files I'd rather not have there: #+BEGIN_SRC emacs-lisp (setq recentf-exclude '("^/dev/shm/.*$")) #+END_SRC *** =savehist= The history of prompts like =M-:= can be saved, but let's change its save file and history length first. #+BEGIN_SRC emacs-lisp (setq savehist-file "~/.config/emacs/etc/savehist" history-length 150) #+END_SRC *** =save-place= I didn't expect to like this functionality, but it's pretty neat to start from the last place you were in a file the next time you visit it. Asides from putting the save file somewhere else, I have to enable this behaviour for every buffer since it's buffer-local. #+BEGIN_SRC emacs-lisp (setq save-place-file "~/.config/emacs/etc/saveplace") #+END_SRC *** =windmove= The =windmove= provides useful commands for moving window focus by direction, I prefer having wraparound instead of getting errors though. #+BEGIN_SRC emacs-lisp (setq windmove-wrap-around t) #+END_SRC *** =bookmark= Yet another file that I prefer being saved somewhere else. #+BEGIN_SRC emacs-lisp (setq bookmark-default-file "~/.config/emacs/etc/bookmarks") #+END_SRC *** =ediff= Anything else than =emacsclient= spawning frames is pretty much useless for me with =i3=. I assume the vertical split is not done because I've customized horizontal splits to be prefered. The name of the alternative splitting function is not a mistake, what Emacs calls "horizontal" in =window.el= is called vertical in anything else. #+BEGIN_SRC emacs-lisp (setq ediff-window-setup-function 'ediff-setup-windows-plain ediff-split-window-function 'split-window-horizontally) #+END_SRC *** =dired= For the few times I'm using [[http://pu.inf.uni-tuebingen.de/users/sperber/software/dired/][Dired]], I prefer it not spawning an endless amount of buffers. In fact, I'd prefer it using one buffer unless another one is explicitly created, but you can't have everything. #+BEGIN_SRC emacs-lisp (with-eval-after-load 'dired (define-key dired-mode-map (kbd "RET") 'dired-find-alternate-file)) #+END_SRC *** =tramp= If [[https://www.gnu.org/software/tramp/][TRAMP]] makes backup files, they should better be kept locally than remote. #+BEGIN_SRC emacs-lisp (setq tramp-backup-directory-alist backup-directory-alist) #+END_SRC As usual I want to fix up the file it's storing its history in. #+BEGIN_SRC emacs-lisp (with-eval-after-load 'tramp-cache (setq tramp-persistency-file-name "~/.config/emacs/etc/tramp")) #+END_SRC But to be honest, I prefer it not automatically interfering with everything. Unloading it entirely causes packages to break that assume it's enabled, therefore I'm going for its main entry point and dike it out. #+BEGIN_SRC emacs-lisp (defun my-disable-tramp-file-handlers () (setq file-name-handler-alist (--remove (string-match-p "^tramp" (symbol-name (cdr it))) file-name-handler-alist))) #+END_SRC It discovers SSH options by launching a process which is rather slow, so customize it to the discovered value to disable auto-detection. #+BEGIN_SRC emacs-lisp (setq tramp-ssh-controlmaster-options "") #+END_SRC *** FFAP A great idea, but it's rather annoying in practice. At times it can happen that it pings something looking like a domain name. #+BEGIN_SRC emacs-lisp (setq ffap-machine-p-known 'accept) #+END_SRC *** Calendar General functionality for calendars inside Emacs, split up in a lot of files. Customizing it will affect other packages, including [[https://github.com/kiwanami/emacs-calfw][calfw]] and =M-x calendar=. The following customizations add German holidays (since I happen to live in Germany, d'uh). #+BEGIN_SRC emacs-lisp (setq calendar-week-start-day 1) (setq holiday-general-holidays '((holiday-fixed 1 1 "Neujahr") (holiday-fixed 5 1 "1. Mai") (holiday-fixed 10 3 "Tag der Deutschen Einheit"))) (setq holiday-christian-holidays '((holiday-float 12 0 -4 "1. Advent" 24) (holiday-float 12 0 -3 "2. Advent" 24) (holiday-float 12 0 -2 "3. Advent" 24) (holiday-float 12 0 -1 "4. Advent" 24) (holiday-fixed 12 24 "Weihnachten") (holiday-fixed 12 25 "1. Weihnachtstag") (holiday-fixed 12 26 "2. Weihnachtstag") (holiday-fixed 1 6 "Heilige Drei Könige") (holiday-easter-etc -48 "Rosenmontag") (holiday-easter-etc -3 "Gründonnerstag") (holiday-easter-etc -2 "Karfreitag") (holiday-easter-etc 0 "Ostersonntag") (holiday-easter-etc +1 "Ostermontag") (holiday-easter-etc +39 "Christi Himmelfahrt") (holiday-easter-etc +49 "Pfingstsonntag") (holiday-easter-etc +50 "Pfingstmontag") (holiday-easter-etc +60 "Fronleichnam") (holiday-fixed 8 15 "Mariae Himmelfahrt") (holiday-fixed 11 1 "Allerheiligen") (holiday-float 11 0 1 "Totensonntag" 20))) (setq holiday-oriental-holidays nil holiday-bahai-holidays nil holiday-islamic-holidays nil holiday-hebrew-holidays nil) #+END_SRC *** =org-mode= First some UI and editing tweaks. #+BEGIN_SRC emacs-lisp (setq org-catch-invisible-edits 'error org-startup-indented t org-cycle-include-plain-lists 'integrate org-return-follows-link t org-M-RET-may-split-line nil org-src-fontify-natively t org-src-preserve-indentation t org-enforce-todo-dependencies t org-enforce-todo-checkbox-dependencies t org-link-frame-setup '((file . find-file))) #+END_SRC I like taking notes. #+BEGIN_SRC emacs-lisp (setq org-directory "~/notes/" org-default-notes-file "~/notes/notes.org" org-capture-templates '(("j" "Journal" entry (file+olp+datetree "~/notes/journal.org.gpg") "* %<%H:%M>\n\n%?" :empty-lines 1))) #+END_SRC An Org patch from an upcoming release that makes datetree journal entries less annoying: #+BEGIN_SRC emacs-lisp (with-eval-after-load 'org-datetree (defun org-datetree-insert-line (year &optional month day text) (delete-region (save-excursion (skip-chars-backward " \t\n") (point)) (point)) (org-insert-heading) (while (> (org-current-level) 1) (org-do-promote)) (when month (org-do-demote)) (when day (org-do-demote)) (if text (insert text) (insert (format "%d" year)) (when month (insert (if day (format-time-string "-%m-%d %A" (encode-time 0 0 0 day month year)) (format-time-string "-%m %B" (encode-time 0 0 0 1 month year)))))) (when (and day org-datetree-add-timestamp) (save-excursion (insert "\n") (org-indent-line) (org-insert-time-stamp (encode-time 0 0 0 day month year) nil (eq org-datetree-add-timestamp 'inactive)))) (beginning-of-line))) #+END_SRC The export functionality is very handy, but some of the stuff I like using is deactivated by default :< #+BEGIN_SRC emacs-lisp (setq org-export-backends '(ascii beamer html latex md)) #+END_SRC It's a bit tricky to color code listings and permit more flexible tables: #+BEGIN_SRC emacs-lisp (setq org-latex-listings 'minted org-latex-packages-alist '(("" "tabu") ("" "minted") ("english" "babel")) org-latex-pdf-process '("pdflatex -shell-escape -interaction nonstopmode -output-directory %o %f" "pdflatex -shell-escape -interaction nonstopmode -output-directory %o %f")) #+END_SRC Using the HTML exporter I've discovered that underscores are interpreted as subscripts. Considering I only use these in LaTeX formulae, I'll just deactivate that markup completely: #+BEGIN_SRC emacs-lisp (setq org-export-with-sub-superscripts '{}) #+END_SRC When clocking in changes, the default accuracy of five minutes is a bit too fine for me. I don't know why, but Org has a customizable that adjusts this *and* rounding when adding clocking entries... #+BEGIN_SRC emacs-lisp (setq org-time-stamp-rounding-minutes '(0 15)) #+END_SRC Org's exporter has a rather weird way of figuring out how to open an exported file, so we'll give it a nudge. Note that since Emacs handles PDFs with ~docview~ *and* its mailcap implementation prefers Lisp viewers over system viewers, it ends up using ~docview~ in both cases, so we'll just tell it not to by placing the PDF entry at the top while still defining that we'd rather want to use ~xdg-open~ instead of that piece of rubbish. #+BEGIN_SRC emacs-lisp (setq org-file-apps '(("pdf" . system) (auto-mode . emacs) (system . "xdg-open %s") (t . system))) #+END_SRC Org changed a default about clocking and drawers which has the annoying side effect of always putting clocks into a drawer and surrounding the existing clocks with one if necessary. This can take a significant amount of time if there are many clocking entries present. #+BEGIN_SRC emacs-lisp (setq org-clock-into-drawer nil) #+END_SRC Enable additional Babel languages: #+BEGIN_SRC emacs-lisp (with-eval-after-load 'ob (org-babel-do-load-languages 'org-babel-load-languages '((emacs-lisp . t) (python . t)))) #+END_SRC *** =comint= Here comes another particularly interesting Emacs package. It allows one to define major modes interacting with a REPL-style process. In other words, it gives you all kinds of shell and interpreter interaction with common keybindings, be it for SQL, your favourite programming language or your shell. Even Emacs itself can be used, try out =M-x ielm=. It's trivial to clear the entire =comint= buffer by temporarily binding ~comint-buffer-maximum-size~ to zero and calling ~comint-truncate-buffer~, however that's not what I really want. Usually it's just the output of the last expression that's been faulty and needs to be cleared by replacing it with a comment. The idea itself is taken from [[https://github.com/clojure-emacs/cider/blob/cb3509eb54d3c3369681d73f3218a1493b977e99/cider-repl.el#L640-L655][CIDER]]. #+BEGIN_SRC emacs-lisp (defun my-comint-last-output-beg () (save-excursion (comint-goto-process-mark) (while (not (or (eq (get-char-property (point) 'field) 'boundary) (= (point) (point-min)))) (goto-char (previous-char-property-change (point) (point-min)))) (if (= (point) (point-min)) (point) (1+ (point))))) (defun my-comint-last-output-end () (save-excursion (comint-goto-process-mark) (while (not (or (eq (get-char-property (point) 'font-lock-face) 'comint-highlight-prompt) (= (point) (point-min)))) (goto-char (previous-char-property-change (point) (point-min)))) (let ((overlay (car (overlays-at (point))))) (when (and overlay (eq (overlay-get overlay 'font-lock-face) 'comint-highlight-prompt)) (goto-char (overlay-start overlay)))) (1- (point)))) (defun my-comint-clear-last-output () (interactive) (let ((start (my-comint-last-output-beg)) (end (my-comint-last-output-end))) (let ((inhibit-read-only t)) (delete-region start end) (save-excursion (goto-char start) (insert (propertize "output cleared" 'font-lock-face 'font-lock-comment-face)))))) #+END_SRC Killed =comint= processes tend to leave an useless buffer around. Let's kill it after noticing such an event with a process sentinel. #+BEGIN_SRC emacs-lisp (defun my-shell-kill-buffer-sentinel (process event) (when (and (memq (process-status process) '(exit signal)) (buffer-live-p (process-buffer process))) (kill-buffer))) (defun my-kill-process-buffer-on-exit () (set-process-sentinel (get-buffer-process (current-buffer)) #'my-shell-kill-buffer-sentinel)) (dolist (hook '(ielm-mode-hook term-exec-hook comint-exec-hook)) (add-hook hook 'my-kill-process-buffer-on-exit)) #+END_SRC Recentering feels a bit unintuitive since it goes by the middle first. I only need top and bottom commands, for that I'll define my own command and bind it later. #+BEGIN_SRC emacs-lisp (defun my-recenter-top-bottom () (interactive) (goto-char (point-max)) (let ((recenter-positions '(top bottom))) (recenter-top-bottom))) #+END_SRC Another thing annoying me in comint buffers is that when text is read-only, both cursor movement and appending to kill ring still happen. This is less useful since if you keep holding the keys to delete words, you end up traversing the entire buffer instead of stopping at the read-only boundaries and pollute the kill ring. To remedy that I'll write my own word killing commands in the typical Emacs user fashion, however I'll not advise the built-ins since who knows what might possibly be relying on this default behaviour. #+BEGIN_SRC emacs-lisp (defun my-kill-word (arg) (interactive "p") (unless buffer-read-only (let ((beg (point)) (end (save-excursion (forward-word arg) (point))) (point (save-excursion (goto-char (if (> arg 0) (next-single-char-property-change (point) 'read-only) (previous-single-char-property-change (point) 'read-only))) (point)))) (unless (get-char-property (point) 'read-only) (if (if (> arg 0) (< point end) (> point end)) (kill-region beg point) (kill-region beg end)))))) (defun my-backward-kill-word (arg) (interactive "p") (my-kill-word (- arg))) #+END_SRC The new functionality introduced has to be bound to keys for convenient use. Note the remapping of commands. #+BEGIN_SRC emacs-lisp (with-eval-after-load 'comint (define-key comint-mode-map (kbd " ") 'my-kill-word) (define-key comint-mode-map (kbd " ") 'my-backward-kill-word) (define-key comint-mode-map (kbd "C-S-l") 'my-comint-clear-last-output) (define-key comint-mode-map (kbd "C-l") 'my-recenter-top-bottom)) #+END_SRC *** =shell= For unknown reasons I get my input echoed back to me. In other words, sending =ls= to =shell= echoes my input twice, then the output. =comint= has a setting that can filter these echoes. #+BEGIN_SRC emacs-lisp (defun my-shell-turn-echo-off () (setq comint-process-echoes t)) (add-hook 'shell-mode-hook 'my-shell-turn-echo-off) #+END_SRC *** =eshell= I want =C-d= to not unconditionally delete the character, but to quit on an empty prompt, too. #+BEGIN_SRC emacs-lisp (defun my-eshell-quit-or-delete-char (arg) (interactive "p") (if (and (eolp) (looking-back eshell-prompt-regexp)) (eshell-life-is-too-much) ;; http://emacshorrors.com/post/life-is-too-much (delete-forward-char arg))) (defun my-eshell-setup () (define-key eshell-mode-map (kbd "C-d") 'my-eshell-quit-or-delete-char)) (add-hook 'eshell-mode-hook 'my-eshell-setup) #+END_SRC For silly reasons I like having a rainbow-colored prompt. #+BEGIN_SRC emacs-lisp (add-hook 'eshell-load-hook 'nyan-prompt-enable) #+END_SRC *** CC-Mode In their ingenuity the Emacs developers decided to make the GNU style the default style for C code written with it. While this is a decision that helps making contribution to GNU projects still adhering to this style (including Emacs itself) a fair bit easier, I'd hate using it for anything else. I don't know my exact preferences yet, but for the time being the "user" style is good enough and can still be customized into something more sophisticated. #+BEGIN_SRC emacs-lisp (setq c-default-style '((java-mode . "java") (awk-mode . "awk") (c-mode . "user") (c++-mode . "user"))) #+END_SRC Electric indentation of things like opening braces on their own line is linked to ~electric-indent-mode~, but that can be worked around easily: #+BEGIN_SRC emacs-lisp (setq-default c-electric-flag t) #+END_SRC Debugging indentation issues is hard enough as it is, this setting echoes what construct you've just indented. #+BEGIN_SRC emacs-lisp ;; (setq c-echo-syntactic-information-p t) #+END_SRC Backspace can untabify tabs which is yucky. Hungry delete is overkill, so I'll just revert to normal behavior there: #+BEGIN_SRC emacs-lisp (setq c-backspace-function 'delete-backward-char) #+END_SRC Smartparens screws up the handling of single quote literals and escapes them for some reason. Let's work around this by disabling its delimiter escaping in cc-mode: #+BEGIN_SRC emacs-lisp (defun my-disable-sp-escape-quotes () (setq-local sp-escape-quotes-after-insert nil)) (add-hook 'c-mode-common-hook 'my-disable-sp-escape-quotes) #+END_SRC *** =eldoc-mode= The default idle delay is way too long. Also, avoid displaying overly long function signatures. #+BEGIN_SRC emacs-lisp (setq eldoc-idle-delay 0.1 eldoc-echo-area-use-multiline-p nil) #+END_SRC *** Emacs Lisp [[https://github.com/cask/cask][Cask]] files are just Emacs Lisp. #+BEGIN_SRC emacs-lisp (add-to-list 'auto-mode-alist '("Cask\\'" . emacs-lisp-mode)) #+END_SRC Additionally to the =F1= keybindings I'd like to have two extra keybinds for evaluation and a REPL. #+BEGIN_SRC emacs-lisp (defun my-eval-region-or-buffer () (interactive) (if (region-active-p) (eval-region (region-beginning) (region-end)) (eval-buffer))) (with-eval-after-load 'lisp-mode (define-key emacs-lisp-mode-map (kbd "C-c C-c") 'my-eval-region-or-buffer) (define-key emacs-lisp-mode-map (kbd "C-c C-z") 'ielm)) #+END_SRC =eldoc= is a nice helper to avoid looking up function signatures in function documentation. #+BEGIN_SRC emacs-lisp (add-hook 'emacs-lisp-mode-hook 'turn-on-eldoc-mode) (add-hook 'ielm-mode-hook 'turn-on-eldoc-mode) #+END_SRC I quite like CIDER-style display of evaluation results, so I'm glad there's a package providing this for Emacs Lisp code. #+BEGIN_SRC emacs-lisp (add-hook 'emacs-lisp-mode-hook 'eros-mode) #+END_SRC Experiment: My biggest gripe with writing Emacs Lisp is having to type out a package prefix every time. This hack will lessen the pain by only having to hit =C-.= for inserting the prefix at point. The prefix is guessed if not set by searching for a =(defgroup ...)= form, but can be set manually as well by using the prefix argument. #+BEGIN_SRC emacs-lisp (defvar-local my-current-package-prefix nil) (defun my-ensure-trailing-dash (string) (if (and (not (zerop (length string))) (not (= (aref string (1- (length string))) ?-))) (concat string "-") string)) (defun my-guess-current-package-prefix (arg) (save-excursion (goto-char (point-min)) (if (and (not arg) (re-search-forward "^(defgroup " nil t)) (let ((form (read (thing-at-point 'defun))) rest) (if (setq rest (memq :prefix form)) (setq my-current-package-prefix (cadr rest)) (setq my-current-package-prefix (format "%s-" (cadr form))))) (setq my-current-package-prefix (my-ensure-trailing-dash (read-string "Package prefix: " my-current-package-prefix)))))) (defun my-insert-current-package-prefix (arg) (interactive "P") (when (or (not my-current-package-prefix) arg) (my-guess-current-package-prefix arg)) (insert my-current-package-prefix)) (with-eval-after-load 'elisp-mode (define-key emacs-lisp-mode-map (kbd "C-.") 'my-insert-current-package-prefix)) #+END_SRC *** Scheme I like [[http://call-cc.org/][CHICKEN]]. #+BEGIN_SRC emacs-lisp (setq scheme-program-name "csi") (add-to-list 'interpreter-mode-alist '("chicken-scheme" . scheme-mode)) #+END_SRC To avoid typing =M-x run-scheme=, I define another useful keybinding. #+BEGIN_SRC emacs-lisp (with-eval-after-load 'scheme (define-key scheme-mode-map (kbd "C-c C-z") 'run-scheme)) #+END_SRC The binding is replaced though after launching the REPL, I should eventually fix this. Perhaps with my very own major mode. I wrote an integration for =chicken-doc=: #+BEGIN_SRC emacs-lisp (with-eval-after-load 'scheme (define-key scheme-mode-map (kbd "C-c C-d") 'chicken-doc-describe)) #+END_SRC Indentation hints fortunately seem to work for other languages than Emacs Lisp. #+BEGIN_SRC emacs-lisp (put 'match 'scheme-indent-function 1) (put 'match-let 'scheme-indent-function 1) (put 'match-let* 'scheme-indent-function 1) (put 'when 'scheme-indent-function 1) (put 'and-let* 'scheme-indent-function 1) (put 'if-let 'scheme-indent-function 1) (put 'let-location 'scheme-indent-function 1) (put 'select 'scheme-indent-function 1) (put 'bitmatch 'scheme-indent-function 1) (put 'bitpacket 'scheme-indent-function 1) (put 'with-transaction 'scheme-indent-function 1) (put 'foreign-lambda* 'scheme-indent-function 2) (put 'test-group 'scheme-indent-function 1) (put 'call-with-database 'scheme-indent-function 1) (put 'call-with-input-string 'scheme-indent-function 1) (put 'parameterize 'scheme-indent-function 1) (put 'hash-table-for-each 'scheme-indent-function 1) (put 'module 'scheme-indent-function (lambda (&rest args) 0)) #+END_SRC There's a few schemey file formats I'd like to automatically recognize: #+BEGIN_SRC emacs-lisp (add-to-list 'auto-mode-alist '("\\.sld\\'" . scheme-mode)) (add-to-list 'auto-mode-alist '("\\.sxml\\'" . scheme-mode)) (add-to-list 'auto-mode-alist '("\\.scss\\'" . lisp-data-mode)) (add-to-list 'auto-mode-alist '("\\.setup\\'" . scheme-mode)) (add-to-list 'auto-mode-alist '("\\.meta\\'" . lisp-data-mode)) (add-to-list 'auto-mode-alist '("\\.release-info\\'" . lisp-data-mode)) (add-to-list 'auto-mode-alist '("\\.egg\\'" . lisp-data-mode)) #+END_SRC CHICKEN has special syntax for embedded C code and heredocs that messes up syntax highlighting: #+BEGIN_SRC emacs-lisp (with-eval-after-load 'scheme (defun my-scheme-region-extend-function () (when (not (get-text-property (point) 'font-lock-multiline)) (let* ((heredoc nil) (new-beg (save-excursion (when (and (re-search-backward "#>\\|<#\\|#<[<#]\\(.*\\)$" nil t) (not (get-text-property (point) 'font-lock-multiline))) (let ((match (match-string 0)) (tag (match-string 1))) (cond ((equal match "#>") (point)) ((string-match-p "^#<[<#]" match) (setq heredoc tag) (point))))))) (new-end (save-excursion (if heredoc (when (and (re-search-forward (concat "^" (regexp-quote heredoc) "$") nil t) (not (get-text-property (point) 'font-lock-multiline))) (point)) (when (and (re-search-forward "#>\\|<#" nil t) (not (get-text-property (point) 'font-lock-multiline)) (equal (match-string 0) "<#")) (point)))))) (when (and new-beg new-end) (setq font-lock-beg new-beg) (setq font-lock-end new-end) (with-silent-modifications (put-text-property new-beg new-end 'font-lock-multiline t)) (cons new-beg new-end))))) (defun my-scheme-syntax-propertize-foreign (_ end) (save-match-data (when (search-forward "<#" end t) (with-silent-modifications (put-text-property (1- (point)) (point) 'syntax-table (string-to-syntax "> cn")))))) (defun my-scheme-syntax-propertize-heredoc (_ end) (save-match-data (let ((tag (match-string 2))) (when (and tag (re-search-forward (concat "^" (regexp-quote tag) "$") nil t)) (with-silent-modifications (put-text-property (1- (point)) (point) 'syntax-table (string-to-syntax "> cn"))))))) (defun scheme-syntax-propertize (beg end) (goto-char beg) (scheme-syntax-propertize-sexp-comment (point) end) (funcall (syntax-propertize-rules ("\\(#\\);" (1 (prog1 "< cn" (scheme-syntax-propertize-sexp-comment (point) end)))) ("\\(#\\)>" (1 (prog1 "< cn" (my-scheme-syntax-propertize-foreign (point) end)))) ("\\(#\\)<[<#]\\(.*\\)$" (1 (prog1 "< cn" (my-scheme-syntax-propertize-heredoc (point) end))))) (point) end))) (defun my-scheme-mode-setup () (setq font-lock-extend-region-functions (cons 'my-scheme-region-extend-function font-lock-extend-region-functions))) (add-hook 'scheme-mode-hook 'my-scheme-mode-setup) #+END_SRC Flymake integration for linting: #+BEGIN_SRC emacs-lisp (defun flymake-chicken-init () (add-hook 'flymake-diagnostic-functions #'flymake-chicken-backend nil t)) (add-hook 'scheme-mode-hook #'flymake-chicken-init) #+END_SRC *** Common Lisp I like [[http://www.sbcl.org/][SBCL]]. #+BEGIN_SRC emacs-lisp (setq inferior-lisp-program "/usr/bin/sbcl") #+END_SRC *** NXML Let's automatically complete closing tags. #+BEGIN_SRC emacs-lisp (setq nxml-slash-auto-complete-flag t) #+END_SRC *** CSS Indentation could be a bit more narrow. #+BEGIN_SRC emacs-lisp (setq css-indent-offset 2) #+END_SRC *** Python Emacs is not aware of version-dependent shebangs. #+BEGIN_SRC emacs-lisp (add-to-list 'interpreter-mode-alist '("python2" . python-mode)) (add-to-list 'interpreter-mode-alist '("python3" . python-mode)) #+END_SRC For some reason guessing the indentation offset is on by default although nearly all Python code I've worked with did use 4 spaces. I wouldn't even care weren't it for the message displayed after it's done. #+BEGIN_SRC emacs-lisp (setq python-indent-guess-indent-offset nil) #+END_SRC *** Etags I've created a =TAGS= file for finding the definitions to the C sources quickly. To avoid prompting for its name, one can customize the following: #+BEGIN_SRC emacs-lisp (setq tags-file-name "TAGS") #+END_SRC *** Info Make copying use the lispy syntax by default and with a normal syntax argument copy the HTML link. #+BEGIN_SRC emacs-lisp (defun my-info-copy-current-node-name (arg) "Copy the lispy form of the current node. With a prefix argument, copy the link to the online manual instead." (interactive "P") (let* ((manual (file-name-sans-extension (file-name-nondirectory Info-current-file))) (node Info-current-node) (link (if (not arg) (format "(info \"(%s) %s\")" manual node) ;; NOTE this will only work with emacs-related nodes... (format "https://www.gnu.org/software/emacs/manual/html_node/%s/%s.html" manual (if (string= node "Top") "index" (replace-regexp-in-string " " "-" node)))))) (kill-new link) (message link))) (with-eval-after-load 'info (define-key Info-mode-map (kbd "c") 'my-info-copy-current-node-name)) #+END_SRC *** nroff I'll just pretend that mdoc is the same as nroff: #+BEGIN_SRC emacs-lisp (add-to-list 'auto-mode-alist '("\\.mdoc\\'" . nroff-mode)) #+END_SRC *** sql-mode I'm using PostgreSQL most of the time: #+BEGIN_SRC emacs-lisp (setq sql-product 'postgres) #+END_SRC *** VC I rarely if ever do non-Git projects and if I do, I dislike the potentially very long VC indicator in the modeline. It looks as if VC is enabled by merely requiring the feature, so I'll have to disable it differently: #+BEGIN_SRC emacs-lisp (setq vc-handled-backends nil) #+END_SRC *** JS The whole world appears to be using two-space indentation: #+BEGIN_SRC emacs-lisp (setq js-indent-level 2) #+END_SRC Some people use weird extensions for Javascript files: #+BEGIN_SRC emacs-lisp (add-to-list 'auto-mode-alist '("\\.es6\\'" . js-mode)) #+END_SRC *** shell-script-mode I don't see why using zsh as my shell means I wouldn't want to write bash scripts. #+BEGIN_SRC emacs-lisp (setq sh-shell-file "/bin/bash") #+END_SRC PKGBUILDs are shell scripts, really: #+BEGIN_SRC emacs-lisp (add-to-list 'auto-mode-alist '("PKGBUILD\\'" . shell-script-mode)) #+END_SRC *** Edebug Evaluating expressions in Edebug gives you no completion whatsoever. https://emacs.stackexchange.com/questions/39353/completion-in-edebug-eval-expression shows how to do better. #+BEGIN_SRC emacs-lisp (defun my-edebug-eval-expression-completion (expr) "Replace interactive-spec of `edebug-eval-expression' with `read--expression' from `eval-expression' to get completion working." (interactive (list (read--expression "Eval: ")))) (advice-add 'edebug-eval-expression :before 'my-edebug-eval-expression-completion) #+END_SRC *** Sieve Neat to have, but it's annoying to do auth over and over again. #+BEGIN_SRC emacs-lisp (defun my-sieve-manage () (interactive) (let ((host "imap.mailbox.org") (user "mail@vasilij.de") (secret (lambda () (car (process-lines "pass" "mail/mailbox.org"))))) (password-cache-add `(auth-source :host ,host :port "sieve" :max 1 :create t) `((:host ,host :user ,user :secret ,secret))) (sieve-manage "imap.mailbox.org"))) #+END_SRC *** yow #+BEGIN_SRC emacs-lisp (autoload 'yow "yow") (autoload 'psychoanalyze-pinhead "yow") (setq yow-file "~/.config/emacs/etc/yow.lines") #+END_SRC *** rst-mode One convenience command for updating the date in my blog posts: #+BEGIN_SRC emacs-lisp (defun my-update-blog-timestamp () (interactive) (save-excursion (save-restriction (goto-char (point-min)) (when (char-equal (char-after) ?\() (narrow-to-region (point-min) (save-excursion (forward-sexp) (point))) (when (or (re-search-forward "updated \\. \"\\([^\"]+\\)\"" nil t) (re-search-forward "date \\. \"\\([^\"]+\\)\"" nil t)) (replace-match (format-time-string "%F %T %z") nil t nil 1)))))) (with-eval-after-load 'rst (define-key rst-mode-map (kbd "C-c u") 'my-update-blog-timestamp)) #+END_SRC *** calculator The display precision defaults to 3 for some reason: #+BEGIN_SRC emacs-lisp (setq calculator-number-digits 8) #+END_SRC I find scientific display of small numbers plain unreadable, so display of all digits with grouping it is. #+BEGIN_SRC emacs-lisp (setq calculator-displayer '(std ?f t)) #+END_SRC Division behaves like integer division for integers. I want float division by default (calc cleans up trailing zeroes anyway). #+BEGIN_SRC emacs-lisp (setq calculator-user-operators '(("/" / (/ (float X) Y) 2 5))) #+END_SRC *** Flymake In an ironic turn of events, flycheck development stalled and flymake got a long overdue rewrite, making it the better choice for linting source code. Therefore I'm slowly migrating my bits of flycheck configuration to flymake. For some reason, flymake doesn't choose any key bindings for its commands. #+BEGIN_SRC emacs-lisp (with-eval-after-load 'flymake (define-key flymake-mode-map (kbd "C-c ! n") 'flymake-goto-next-error) (define-key flymake-mode-map (kbd "C-c ! p") 'flymake-goto-prev-error)) #+END_SRC I don't want it to check before I save the file. #+BEGIN_SRC emacs-lisp (setq flymake-no-changes-timeout nil) #+END_SRC ** Packages outside Emacs Welcome to the blind spot of =emacs-devel=. Unlike the people on there, I'll not pretend external packages are something to speak of in hushed tones. *** [[https://github.com/nonsequitur/smex/][smex]] Nice improvement over vanilla =M-x= that gives you persistency and better matching. Let's give it more history and a different file. #+BEGIN_SRC emacs-lisp (setq smex-save-file (concat user-emacs-directory "etc/smex") smex-history-length 50) #+END_SRC *** CSV After installing [[http://elpa.gnu.org/packages/csv-mode.html][csv-mode]] from [[http://elpa.gnu.org/][GNU ELPA]], I found out it's using a =:set= form in its customization option for the separators, therefore I had to figure out what "internal" variables they were setting and customized them. #+BEGIN_SRC emacs-lisp (setq csv-separators '(";" " " ",") csv-separator-chars '(?\; ? ?,) csv--skip-regexp "^ ; ," csv-separator-regexp "[; ,]" csv-font-lock-keywords '(("[; ,]" (0 'csv-separator-face)))) #+END_SRC *** [[https://github.com/quelpa/quelpa][Quelpa]] A client-side [[http://melpa.org/][MELPA]]. Hugely useful for development, also useful to obtain packages that are not there or need to be built differently from what it offers. [[https://github.com/junegunn/vim-plug][vim-plug]] comes close, but the closest equivalent to it would be the [[https://www.archlinux.org/pacman/makepkg.8.html][makepkg]] utility. This customization is necessary to have updates of packages happen, even if they already exist. #+BEGIN_SRC emacs-lisp (setq quelpa-upgrade-p t) #+END_SRC *** [[https://github.com/wasamasa/shackle][shackle]] Declarative popup window rules. #+BEGIN_SRC emacs-lisp (setq shackle-rules '(((svg-2048-mode circe-query-mode clojure-mode) :same t) ("*Help*" :align t :select t) ("\\`\\*helm.*?\\*\\'" :regexp t :align t) ((compilation-mode "\\`\\*firestarter\\*\\'" "\\`magit-diff:") :regexp t :noselect t) ((inferior-scheme-mode inferior-lisp-mode "*shell*" "*eshell*") :popup t)) shackle-default-rule '(:select t) shackle-default-size 0.4 shackle-inhibit-window-quit-on-same-windows t) #+END_SRC *** [[https://github.com/wasamasa/eyebrowse][eyebrowse]] Less clumsy management of window configurations. Switch back and forth just like my i3wm configuration, wrap around, too. #+BEGIN_SRC emacs-lisp (setq eyebrowse-switch-back-and-forth t eyebrowse-wrap-around t) #+END_SRC *** [[https://github.com/company-mode/company-mode][company-mode]] The best auto-completion mode we have out there. The following sets up a good amount of UI tweaks and everything necessary for the global backends. #+BEGIN_SRC emacs-lisp (setq company-idle-delay 0.1 company-minimum-prefix-length 2 company-selection-wrap-around t company-show-numbers t company-require-match 'never company-dabbrev-downcase nil company-dabbrev-ignore-case t company-dabbrev-other-buffers nil company-backends '(company-nxml company-css company-capf company-dabbrev-code company-files company-dabbrev)) #+END_SRC Hitting =ESC= does exit Evil's insert state (which is where I'm usually in when typing completable text), but still keeps the popup open. A similiar problem applies to the candidate search, so here's a workaround for both: #+BEGIN_SRC emacs-lisp (with-eval-after-load 'company (define-key company-search-map (kbd "") 'company-search-abort)) #+END_SRC I've transitioned from [[https://github.com/auto-complete/auto-complete][auto-complete-mode]] so I'm missing its selection behaviour. Company is not quite there yet, but ~company-tng-configure-default~ helps. It needs to be called after this, but before Company is enabled, so I've deferred it to the end of this init file. *** [[https://github.com/magnars/dash.el][dash]] Very useful library, too bad I don't know how to properly use it yet. Since it's sprinkled all over in code I'd like to have extra syntax highlighting for it. #+BEGIN_SRC emacs-lisp (with-eval-after-load 'dash (dash-enable-font-lock)) #+END_SRC *** [[https://github.com/emacs-helm/helm][helm]] A polarizing package to say the least. The good part of it is that it actually tries enabling abstractions over complex selection UI. The bad part is that it's overly complex, hard to debug and prone to bizarre behaviour. I've handed in ten bugs for it already and don't expect those to be the last. With that being said I find it essential to quickly find your way through Emacs, I just wish it were less idiosyncratic and with developer documentation. **** Navigation The default navigation isn't as fast as it could be. Automatically switching directories is a must for me. Note the hack with ~helm-ff--auto-update-state~, it's supposedly internal, but only set after using ~helm-find-files~ which essentially means that everything using the file selector won't get the auto-switching goodies unless a file has been found before. With this hack however it will. The other hack goes beyond the ~helm-ff-ido-style-backspace~ customization and unconditionally enables backspace going up one level in both kinds of file selectors. #+BEGIN_SRC emacs-lisp (setq helm-ff-ido-style-backspace 'always helm-ff-auto-update-initial-value t helm-ff--auto-update-state t) (with-eval-after-load 'helm-files (define-key helm-read-file-map (kbd "") 'helm-find-files-up-one-level) (define-key helm-find-files-map (kbd "") 'helm-find-files-up-one-level)) #+END_SRC There are more idiosyncracies to be resolved with file selection. I don't want to see boring files and not get prompted for creating a new file either. The creation of a new directory however is kept as is. #+BEGIN_SRC emacs-lisp (setq helm-ff-newfile-prompt-p nil helm-ff-skip-boring-files t) #+END_SRC **** Search [[http://www.gnu.org/software/grep/][grep]] is very fast, but not the best tool for code search, especially not within compressed files. That's why I'll go for [[https://github.com/ggreer/the_silver_searcher/][ag]] instead, its =-z= option enables the usage of the very great [[http://libarchive.org/][libarchive]]. Here's two commands for pretty common queries, one going through the official Emacs Lisp sources, the other through the C parts. Note that while the second argument of ~helm-grep-ag-1~ is intended to be used for the file type switches, other switches such as =-z= can be passed just fine: #+BEGIN_SRC emacs-lisp (defun my-grep-emacs-elisp () (interactive) (helm-grep-ag-1 (format "/usr/share/emacs/%s/lisp" emacs-version) '("-z"))) (defun my-grep-emacs-C () (interactive) (helm-grep-ag-1 find-function-C-source-directory '("--cc"))) #+END_SRC Integration for [[https://comby.dev][comby]]'s search: #+BEGIN_SRC emacs-lisp (defun my-comby-command (query dir ext &optional ext-override) (concat (format "comby %s '' %s -d %s -o" (shell-quote-argument query) (shell-quote-argument ext) (shell-quote-argument (expand-file-name dir))) (if ext-override (format " -m %s" (shell-quote-argument ext-override)) ""))) (defun my-comby-search-C (query dir) (compilation-start (my-comby-command query dir ".c") 'grep-mode)) (defun my-comby-search-elisp (query dir) (compilation-start (my-comby-command query dir ".el" ".lisp") 'grep-mode)) (defun my-comby-emacs-C (query) (interactive "sQuery: ") (my-comby-search-C "~/code/emacsen/emacs-git/src")) (defun my-comby-emacs-lisp (query) (interactive "sQuery: ") (my-comby-search-elisp query "~/code/emacsen/emacs-git/lisp")) (defun my-comby-elpa (query) (interactive "sQuery: ") (my-comby-search-elisp "~/.config/emacs/elpa")) #+END_SRC **** ~completing-read~ behaviour I dislike =helm= taking over tab-completion in my IRC client. #+BEGIN_SRC emacs-lisp (setq helm-mode-no-completion-in-region-in-modes '(circe-channel-mode circe-query-mode circe-server-mode)) #+END_SRC **** Other Highlighting of token matches is a tad slow, let's speed it up. #+BEGIN_SRC emacs-lisp (setq helm-mp-highlight-delay 0.3) #+END_SRC I like having my dotfiles repo as default when using =helm-cmd-t= on a directory that's not under version-control. #+BEGIN_SRC emacs-lisp (setq helm-cmd-t-default-repo "~/code/misc/dotfiles") #+END_SRC I don't know why, but helm tries doing window management. Please stop: #+BEGIN_SRC emacs-lisp (setq helm-display-function 'pop-to-buffer) #+END_SRC This may or may not avoid a memory leak: #+BEGIN_SRC emacs-lisp (setq helm-ff-keep-cached-candidates nil) (with-eval-after-load 'helm-files (helm-ff-cache-mode -1)) #+END_SRC **** Custom commands #+BEGIN_SRC emacs-lisp (with-eval-after-load 'helm (defun my-helm-rdictcc () (interactive) (helm :sources 'my-helm-rdictcc-source :buffer "*helm rdictcc*")) (defvar my-helm-rdictcc-source (helm-build-async-source "rdictcc" :candidates-process 'my-helm-rdictcc-process :candidate-number-limit 99 :filtered-candidate-transformer 'my-helm-rdictcc-transformer :requires-pattern 3)) (defun my-helm-rdictcc-process () (let* ((pattern (car (split-string helm-pattern))) (proc (start-process "rdictcc" helm-buffer "rdictcc" "-c" pattern))) (set-process-sentinel proc (lambda (process event) (helm-process-deferred-sentinel-hook process event default-directory))) proc)) (defun my-helm-rdictcc-transformer (candidates _source) (let* ((extra-patterns (cdr (split-string helm-pattern))) (extra-patterns (and extra-patterns (regexp-opt extra-patterns))) result) (dolist (candidate candidates) (when (string-match-p "=\\{20\\}\\[ [AB] => [AB] \\]=\\{20\\}" candidate) (add-face-text-property 0 (length candidate) 'font-lock-comment-face nil candidate)) (if extra-patterns (when (string-match-p extra-patterns candidate) (push candidate result)) (push candidate result))) (nreverse result)))) #+END_SRC *** [[https://github.com/flycheck/flycheck][flycheck]] There's a few languages I like having linting for, see [[Hooks]]. Additionally to that there's few things to tweak. For one I prever the =tex-lacheck= linter over the default =tex-chktex= linter and don't want to use the =emacs-lisp-checkdoc= one at all, another thing is that I don't want linting to start on an idle timer, but rather on opening the buffer and saving it to disk. #+BEGIN_SRC emacs-lisp (setq flycheck-disabled-checkers '(tex-chktex emacs-lisp-checkdoc) flycheck-check-syntax-automatically '(mode-enabled save)) #+END_SRC For whatever reason the =emacs-lisp= checker stopped unconditionally initializing packages before doing the check, the following avoids errors for dependencies in packages I write: #+BEGIN_SRC emacs-lisp (setq flycheck-emacs-lisp-initialize-packages t) #+END_SRC Automatic linting for Emacs Lisp files is nice, but doesn't make any sense for non-packages. For simplicity's sake, I define everything in a few specific directories to be a package: #+BEGIN_SRC emacs-lisp (defun my-enable-flycheck-for-elisp-packages () (when (and buffer-file-name (string-match-p "/home/wasa/code/elisp/" buffer-file-name)) (flycheck-mode))) (add-hook 'emacs-lisp-mode-hook 'my-enable-flycheck-for-elisp-packages) #+END_SRC *** [[https://github.com/abo-abo/hydra][Hydra]] Not sure how to describe it. A library for defining key-centric interfaces? You use it to execute commands with single-key presses first and foremost, I have only come to define repetition-free ones. **** Define utility functions #+BEGIN_SRC emacs-lisp (defun my-zsh () (interactive) (ansi-term "zsh")) (defun my-info-emacs-lisp-intro () (interactive) (info "eintr")) (defun my-info-emacs-lisp-manual () (interactive) (info "elisp")) (defun my-info-cl () (interactive) (info "cl")) (defun my-info-cl-loop () (interactive) (info "(cl) Loop facility")) (defun my-capture-journal () (interactive) (org-capture nil "j")) (defun my-open-journal () (interactive) (find-file "~/notes/journal.org.gpg")) (defun my-open-tracking () (interactive) (find-file "~/notes/tracking.org")) (autoload 'cfw:open-org-calendar "calfw-org" "Open Org calendar" t) #+END_SRC **** Define setup function This is used in ~after-init-hook~. #+BEGIN_SRC emacs-lisp (defun my-setup-hydra () (global-set-key (kbd "") (defhydra hydra-help (:color blue) "Help" ("a" helm-apropos "Apropos") ("c" describe-char "Describe Char") ("f" find-function "Find Function") ("F" describe-function "Describe Function") ("k" describe-key "Describe Key") ("K" find-function-on-key "Find Key") ("m" describe-mode "Describe Modes") ("t" list-timers "List timers") ("v" find-variable "Find Variable") ("V" describe-variable "Describe Variable"))) (global-set-key (kbd "") (defhydra hydra-packages (:color blue) "Packages" ("c" helm-colors "Colors") ("f" find-library "Find Library") ("g" customize-group "Customize Group") ("p" list-packages "Package List") ("q" quelpa "Quelpa"))) (global-set-key (kbd "") (defhydra hydra-search (:color blue) "Search" ("e" my-grep-emacs-elisp "Grep Emacs Elisp") ("E" my-grep-emacs-C "Grep Emacs C") ("g" helm-do-grep "Grep") ("i" helm-imenu "Imenu") ("o" helm-occur "Occur") ("r" my-helm-rdictcc "rdictcc"))) (global-set-key (kbd "") (defhydra hydra-find (:color blue) "Find" ("f" helm-find "Find") ("l" helm-locate "Locate") ("t" helm-cmd-t "Cmd-T"))) (global-set-key (kbd "") (defhydra hydra-eval (:color blue) "Eval" ("c" calc "Calc") ("e" eshell "Eshell") ("g" magit-status "Magit") ("i" ielm "IELM") ("s" shell "Shell") ("t" my-zsh "Term"))) (global-set-key (kbd "") (defhydra hydra-doc (:color blue) "Doc" ("e" info-emacs-manual "Emacs manual") ("i" info "Info") ("l" my-info-emacs-lisp-manual "Emacs Lisp manual"))) (global-set-key (kbd "") (defhydra hydra-insert (:color blue) "insert" ("u" insert-char "Unicode input"))) (global-set-key (kbd "") (defhydra hydra-misc (:color blue) "Misc" ("g" helm-google-suggest "Google Suggest") ("p" helm-list-emacs-process "Emacs Process List") ("t" helm-top "Top"))) (global-set-key (kbd "") (defhydra hydra-distractions (:color blue) "Distractions" ("i" my-irc "IRC") ("s" my-sieve-manage "Sieve"))) (global-set-key (kbd "") (defhydra hydra-capture (:color blue) "Org Capture" ("j" my-capture-journal "Journal"))) (global-set-key (kbd "") (defhydra hydra-lookup (:color blue) "Org Lookup" ("c" cfw:open-org-calendar "Calendar") ("j" my-open-journal "Journal") ("t" my-open-tracking "Tracking")))) #+END_SRC *** [[https://github.com/clojure-emacs/cider][CIDER]] Clojure Interactive Development Environment that Rocks. Please don't display a splash screen in my REPL: #+BEGIN_SRC emacs-lisp (setq cider-repl-display-help-banner nil) #+END_SRC I like =eldoc= for function signatures in both code and REPL buffers. #+BEGIN_SRC emacs-lisp (add-hook 'cider-repl-mode-hook 'eldoc-mode) (add-hook 'clojure-mode-hook 'eldoc-mode) #+END_SRC The REPL binds a key for clearing the last evaluated output, but I prefer nuking all output (which is unbound for some reason): #+BEGIN_SRC emacs-lisp (with-eval-after-load 'cider-repl (define-key cider-repl-mode-map (kbd "C-c C-k") 'cider-repl-clear-buffer)) #+END_SRC [[https://github.com/candid82/joker][Joker]] is a light-weight Clojure implementation that can be used as linter for Clojure and ClojureScript files. #+BEGIN_SRC emacs-lisp (defun my-flycheck-joker-setup () (require 'flycheck-joker)) (add-hook 'clojure-mode-hook 'my-flycheck-joker-setup) (add-hook 'clojurescript-mode-hook 'my-flycheck-joker-setup) #+END_SRC *** [[http://web-mode.org/][web-mode]] Deals with all kinds of templates and other files with multiple modes one encounters in web development. Other than templates, I prefer using it for HTML these days. #+BEGIN_SRC emacs-lisp (add-to-list 'auto-mode-alist '("\\.html?\\'" . web-mode)) (add-to-list 'auto-mode-alist '("\\.tm?pl\\'" . web-mode)) (add-to-list 'auto-mode-alist '("\\.erb\\'" . web-mode)) (setq web-mode-markup-indent-offset 2) (setq web-mode-css-indent-offset 2) (setq web-mode-code-indent-offset 2) #+END_SRC *** =company-tern= [[http://ternjs.net/][Tern]] is kind of cool. Don't forget installing it via =npm=, then adding a =.tern-project= file to your project root. #+BEGIN_SRC emacs-lisp (add-hook 'js-mode-hook 'tern-mode) (add-to-list 'company-backends 'company-tern) #+END_SRC *** [[https://github.com/zenspider/enhanced-ruby-mode][enh-ruby-mode]] I'm using this mode for everything the stock =ruby-mode= would be used for because it provides better syntax highlighting and indentation by using an external process. #+BEGIN_SRC emacs-lisp (add-to-list 'auto-mode-alist '("\\.rb\\'" . enh-ruby-mode)) (add-to-list 'auto-mode-alist '("Gemfile\\'" . enh-ruby-mode)) (add-to-list 'auto-mode-alist '("Rakefile\\'" . enh-ruby-mode)) (add-to-list 'auto-mode-alist '("\\.rake\\'" . enh-ruby-mode)) #+END_SRC For deep indentation, allow bouncing towards a less deep level. #+BEGIN_SRC emacs-lisp (setq enh-ruby-bounce-deep-indent t) #+END_SRC *** [[https://www.gnu.org/software/auctex/][AUCTEX]] Improves the standard editing facilities for all things [[http://tug.org/begin.html][TeX]] and [[http://latex-project.org/][LaTeX]]. **** Usage tweaks It's 2015 and I prefer a TeX engine that can deal with Unicode and use any font I like. #+BEGIN_SRC emacs-lisp (setq-default TeX-engine 'luatex) #+END_SRC This isn't enough though to allow overriding the used command. I used to put =LaTeX-command= into a file-local variable, but that's not picked up if =TeX-engine= has been customized... #+BEGIN_SRC emacs-lisp (setq LuaTeX-command "luatex" LuaLaTeX-command "lualatex --jobname=%s" TeX-engine-alist '((luatex "LuaTeX" LuaTeX-command LuaLaTeX-command LuaTeX-command))) #+END_SRC Set up viewers and a few other things. #+BEGIN_SRC emacs-lisp (setq TeX-quote-after-quote t TeX-auto-save t TeX-parse-self t TeX-view-program-list '(("llpp" "llpp %o")) TeX-view-program-selection '(((output-dvi style-pstricks) "dvips and gv") (output-dvi "xdvi") (output-pdf "llpp") (output-html "xdg-open"))) #+END_SRC There's more verbatim environments than =verbatim=: #+BEGIN_SRC emacs-lisp (setq LaTeX-verbatim-environments '("minted" "verbatim" "verbatim*")) #+END_SRC Enable PDF mode, enable folding and add a few convenience keybinds (like =C-c C-a= to run every command until the document can be viewed). #+BEGIN_SRC emacs-lisp (defun my-extend-hs-modes-alist () (add-to-list 'hs-special-modes-alist `(latex-mode ,(latex/section-regexp) nil "%" (lambda (arg) (latex/next-section 1) (skip-chars-backward " \t\n")) nil))) (autoload 'latex/section-regexp "latex-extra" "LaTeX section regexp" t) (defun my-latex-setup () (TeX-PDF-mode) (latex/setup-keybinds) (my-extend-hs-modes-alist)) (add-hook 'LaTeX-mode-hook 'my-latex-setup) #+END_SRC **** ~completing-read~ behaviour ~helm-mode~ enables more convenient ~completing-read~, however it's a bit silly that [[https://github.com/emacs-helm/helm/issues/37][candidates for common AUCTEX functions aren't required matches]]. #+BEGIN_SRC emacs-lisp (with-eval-after-load 'tex (defun TeX-command-master (&optional override-confirm) "Run command on the current document. If a prefix argument OVERRIDE-CONFIRM is given, confirmation will depend on it being positive instead of the entry in `TeX-command-list'." (interactive "P") (TeX-command (my-TeX-command-query (TeX-master-file)) 'TeX-master-file override-confirm)) (defun TeX-command-query (name) "Query the user for what TeX command to use." (let* ((default (cond ((if (string-equal name TeX-region) (TeX-check-files (concat name "." (TeX-output-extension)) (list name) TeX-file-extensions) (TeX-save-document (TeX-master-file))) TeX-command-default) ((and (memq major-mode '(doctex-mode latex-mode)) ;; Want to know if bib file is newer than .bbl ;; We don't care whether the bib files are open in emacs (TeX-check-files (concat name ".bbl") (mapcar 'car (LaTeX-bibliography-list)) (append BibTeX-file-extensions TeX-Biber-file-extensions))) ;; We should check for bst files here as well. (if LaTeX-using-Biber TeX-command-Biber TeX-command-BibTeX)) ((TeX-process-get-variable name 'TeX-command-next TeX-command-Show)) (TeX-command-Show))) (completion-ignore-case t) (answer (or TeX-command-force (completing-read (concat "Command: (default " default ") ") (TeX-mode-specific-command-list major-mode) nil t default 'TeX-command-history)))) ;; If the answer is "latex" it will not be expanded to "LaTeX" (setq answer (car-safe (TeX-assoc answer TeX-command-list))) (if (and answer (not (string-equal answer ""))) answer default)))) (with-eval-after-load 'latex (defun LaTeX-section-heading () "Hook to prompt for LaTeX section name. Insert this hook into `LaTeX-section-hook' to allow the user to change the name of the sectioning command inserted with `\\[LaTeX-section]'." (let ((string (completing-read (concat "Level: (default " name ") ") LaTeX-section-list nil nil name))) ; Update name (if (not (zerop (length string))) (setq name string)) ; Update level (setq level (LaTeX-section-level name)))) (defun LaTeX-environment (arg) "Make LaTeX environment (\\begin{...}-\\end{...} pair). With optional ARG, modify current environment. It may be customized with the following variables: `LaTeX-default-environment' Your favorite environment. `LaTeX-default-style' Your favorite document class. `LaTeX-default-options' Your favorite document class options. `LaTeX-float' Where you want figures and tables to float. `LaTeX-table-label' Your prefix to labels in tables. `LaTeX-figure-label' Your prefix to labels in figures. `LaTeX-default-format' Format for array and tabular. `LaTeX-default-width' Width for minipage and tabular*. `LaTeX-default-position' Position for array and tabular." (interactive "*P") (let ((environment (completing-read (concat "Environment type: (default " (if (TeX-near-bobp) "document" LaTeX-default-environment) ") ") (LaTeX-environment-list) nil t nil 'LaTeX-environment-history LaTeX-default-environment))) ;; Get default (cond ((and (zerop (length environment)) (TeX-near-bobp)) (setq environment "document")) ((zerop (length environment)) (setq environment LaTeX-default-environment)) (t (setq LaTeX-default-environment environment))) (let ((entry (assoc environment (LaTeX-environment-list)))) (if (null entry) (LaTeX-add-environments (list environment))) (if arg (LaTeX-modify-environment environment) (LaTeX-environment-menu environment)))))) #+END_SRC *** [[https://github.com/Fuco1/smartparens/][smartparens]] Promises to go beyond [[http://mumble.net/~campbell/emacs/paredit.el][paredit]] (which is structured editing for Lisp code) by supporting other languages than Lisp-likes with arbitrary kinds of pairs. I only use its autopairing feature, pair highlighting and a bit of auto-indent though. **** Disable some default pairs The following wall of code disables pairs for Lisp- and TeX-like modes that make absolutely no sense. #+BEGIN_SRC emacs-lisp (with-eval-after-load 'smartparens (sp-local-pair 'minibuffer-inactive-mode "'" nil :actions nil) (sp-local-pair 'minibuffer-inactive-mode "`" nil :actions nil) (sp-local-pair 'emacs-lisp-mode "'" nil :actions nil) (sp-local-pair 'emacs-lisp-mode "`" nil :actions nil) (sp-local-pair 'lisp-interaction-mode "'" nil :actions nil) (sp-local-pair 'lisp-interaction-mode "`" nil :actions nil) (sp-local-pair 'scheme-mode "'" nil :actions nil) (sp-local-pair 'scheme-mode "`" nil :actions nil) (sp-local-pair 'inferior-scheme-mode "'" nil :actions nil) (sp-local-pair 'inferior-scheme-mode "`" nil :actions nil) (sp-local-pair 'LaTeX-mode "\"" nil :actions nil) (sp-local-pair 'LaTeX-mode "'" nil :actions nil) (sp-local-pair 'LaTeX-mode "`" nil :actions nil) (sp-local-pair 'latex-mode "\"" nil :actions nil) (sp-local-pair 'latex-mode "'" nil :actions nil) (sp-local-pair 'latex-mode "`" nil :actions nil) (sp-local-pair 'TeX-mode "\"" nil :actions nil) (sp-local-pair 'TeX-mode "'" nil :actions nil) (sp-local-pair 'TeX-mode "`" nil :actions nil) (sp-local-pair 'tex-mode "\"" nil :actions nil) (sp-local-pair 'tex-mode "'" nil :actions nil) (sp-local-pair 'tex-mode "`" nil :actions nil)) #+END_SRC **** Add IDE-like auto-insertion for braces Working on college assignments in both C and Java made me wish for an interesting feature I've seen in IDEs: Automatic insertion of a correctly indented newline before the closing brace which allows you to enter its content right away. The following is stolen from [[https://github.com/Fuco1/smartparens/wiki/Permissions#pre-and-post-action-hooks][its wiki]]. #+BEGIN_SRC emacs-lisp (defun my-create-newline-and-enter-sexp (&rest _ignored) "Open a new brace or bracket expression, with relevant newlines and indent." (newline) (indent-according-to-mode) (forward-line -1) (indent-according-to-mode)) (with-eval-after-load 'smartparens (sp-local-pair 'c-mode "{" nil :post-handlers '((my-create-newline-and-enter-sexp "RET"))) (sp-local-pair 'js-mode "{" nil :post-handlers '((my-create-newline-and-enter-sexp "RET"))) (sp-local-pair 'rjsx-mode "{" nil :post-handlers '((my-create-newline-and-enter-sexp "RET"))) (sp-local-pair 'java-mode "{" nil :post-handlers '((my-create-newline-and-enter-sexp "RET")))) #+END_SRC **** Other First of all, no long pair mismatch messages please, they're reserved for debugging purposes. #+BEGIN_SRC emacs-lisp (setq sp-message-width nil) #+END_SRC Because I'm using =evil=, funny things are happening with my cursor, like it not going beyond the end of the line in normal state. To emulate a bit more Vim-like paren highlighting, pairs should be shown from inside, too. #+BEGIN_SRC emacs-lisp (setq sp-show-pair-from-inside t) #+END_SRC Automatic quote escaping feels like a mistake to me (and to its author as well ._.). #+BEGIN_SRC emacs-lisp (setq sp-autoescape-string-quote nil) #+END_SRC This curiously named variable controls whether the overlay spanning the pair's content disappears on backwards motions, something entirely different than its name suggests. #+BEGIN_SRC emacs-lisp (setq sp-cancel-autoskip-on-backward-movement nil) #+END_SRC *** [[https://bitbucket.org/lyro/evil/wiki/Home][Evil]] Here comes the set of sane text editing keybindings I can't live without. Both implementation and execution are excellent and reuse as much from Emacs as possible, resulting in very high compatibility and feature coverage. The only thing I can complain about is that its sources are pretty much incomprehensible to me. Despite that weakness I've managed writing my own additions to improve integration a good bit more according to my own tastes. **** Initial state First of all, there are plenty of special modes where neither insert state nor motion state suffice. I've instead decided to do away with motion state and going for Emacs state whenever it makes sense. #+BEGIN_SRC emacs-lisp (setq evil-default-state 'emacs evil-emacs-state-modes nil evil-insert-state-modes nil evil-motion-state-modes nil evil-normal-state-modes '(text-mode prog-mode fundamental-mode css-mode conf-mode TeX-mode LaTeX-mode diff-mode)) #+END_SRC ~org-capture-mode~ is a minor mode, that's why I need to use its hook instead. Same goes for ~view-mode~. #+BEGIN_SRC emacs-lisp (add-hook 'org-capture-mode-hook 'evil-insert-state) (add-hook 'with-editor-mode-hook 'evil-insert-state) (add-hook 'view-mode-hook 'evil-emacs-state) #+END_SRC Allow quitting =M-x magit-blame= with =q= by toggling Evil's current state. #+BEGIN_SRC emacs-lisp (defun my-evil-toggle () (interactive) (cond ((memq evil-state '(insert normal)) (evil-emacs-state)) ((eq evil-state 'emacs) (evil-exit-emacs-state)))) (add-hook 'magit-blame-mode-hook 'my-evil-toggle) #+END_SRC **** More Emacs-like feel These make movement, undo and search feel a bit less weird. #+BEGIN_SRC emacs-lisp (setq evil-cross-lines t evil-move-beyond-eol t evil-want-fine-undo t evil-symbol-word-search t) #+END_SRC However, I want =C-w= to still be the window map prefix in Emacs state (instead of the standard ~kill-region~ command). As the customization setting for that is applied in =evil-maps.el= which is loaded by =evil.el=, I need to load it before enabling ~evil-mode~. #+BEGIN_SRC emacs-lisp (with-eval-after-load 'evil-vars (setq evil-want-C-w-in-emacs-state t)) #+END_SRC #+BEGIN_SRC emacs-lisp (with-eval-after-load 'evil-common (evil-declare-motion 'recenter-top-bottom)) #+END_SRC **** Keymap hacking I want =Y= to yank to the end of line. #+BEGIN_SRC emacs-lisp (setq evil-want-Y-yank-to-eol t) #+END_SRC Some minor modes come with keymaps reminiscent of special major modes, these get overridden by Evil. These can be fixed by using ~evil-normalize-keymaps~, at least for ~edebug-mode~. #+BEGIN_SRC emacs-lisp (add-hook 'edebug-mode-hook 'evil-normalize-keymaps) #+END_SRC ~macrostep-mode~ requires a bit more effort, see [[https://bitbucket.org/lyro/evil/issue/511/let-certain-minor-modes-key-bindings][evil#511]] for the code involved and further explanation. #+BEGIN_SRC emacs-lisp (defun my-macrostep-setup () (evil-make-overriding-map macrostep-keymap 'normal) (evil-normalize-keymaps)) (add-hook 'macrostep-mode-hook 'my-macrostep-setup) #+END_SRC Same goes for ~cider-debug~: #+BEGIN_SRC emacs-lisp (defun my-cider-debug-setup () (evil-make-overriding-map cider--debug-mode-map 'normal) (evil-normalize-keymaps)) (add-hook 'cider--debug-mode-hook 'my-cider-debug-setup) #+END_SRC Let's poke some holes into its keymaps. Anything not bound will be passed through to Emacs other keymaps. Because =SPC=, =RET= and =TAB= are bound to rather silly commands in Vim I'm unbinding them to allow for much more useful Emacs commands (such as context-aware indentation, following links, scrolling a page down, etc.). #+BEGIN_SRC emacs-lisp (with-eval-after-load 'evil-maps (define-key evil-motion-state-map (kbd "SPC") nil) (define-key evil-motion-state-map (kbd "RET") nil) (define-key evil-motion-state-map (kbd "TAB") nil)) #+END_SRC Same story with =C-.= and =M-.=, the latter is usually bound to lookup of symbol at point. The former is unbound because I'm fat-fingering often. #+BEGIN_SRC emacs-lisp (with-eval-after-load 'evil-maps (define-key evil-normal-state-map (kbd "C-.") nil) (define-key evil-normal-state-map (kbd "M-.") nil)) #+END_SRC The hole poking continues, this time for the ex completion keymap. Everything with a modifier (except for the toggle key for Emacs state and other useful keys) has to go. #+BEGIN_SRC emacs-lisp (with-eval-after-load 'evil-maps (setcdr evil-ex-completion-map (--reject (and (memq 'control (event-modifiers (car-safe it))) ;; abort prompt (/= (car-safe it) ?\C-c) (/= (car-safe it) ?\C-g) ;; exit (/= (car-safe it) ?\C-m) ;; previous/next input (/= (car-safe it) ?\C-p) (/= (car-safe it) ?\C-n)) (cdr evil-ex-completion-map)))) #+END_SRC There is a relatively new customizable for Evil's insert state keymap that disables setting up anything vimmish in it: #+BEGIN_SRC emacs-lisp (setq evil-disable-insert-state-bindings t) #+END_SRC **** =C-w= =C-w= works in Emacs state, but not in insert state. Let's fix that. #+BEGIN_SRC emacs-lisp (with-eval-after-load 'evil-maps (define-key evil-insert-state-map (kbd "C-w") 'evil-window-map)) #+END_SRC **** =C-i= =C-i= is used in Vim as counterpart to =C-o= for going back and forth in the jump list. It also happens to be interpreted as =TAB=, simply because terminals are a nightmare. Fortunately GUI Emacs can be told to not resolve =C-i= to indentation by defining a function in ~key-translation-map~ that returns the desired key. That way I'm sending a custom == when Evil is active, in normal state and =C-i= (as opposed to the =TAB= key) has been pressed, otherwise =TAB= is passed through. #+BEGIN_SRC emacs-lisp (defun my-translate-C-i (_prompt) (if (and (= (length (this-single-command-raw-keys)) 1) (eql (aref (this-single-command-raw-keys) 0) ?\C-i) (bound-and-true-p evil-mode) (eq evil-state 'normal)) (kbd "") (kbd "TAB"))) (define-key key-translation-map (kbd "TAB") 'my-translate-C-i) (with-eval-after-load 'evil-maps (define-key evil-motion-state-map (kbd "") 'evil-jump-forward)) #+END_SRC **** =C-u= =C-u= is bound to a scroll up command in Vim, in Emacs however it's used for the prefix argument. This feels pretty weird to me after having bothered learning =C-u= as command for killing a whole line in everything using the readline library. I consider =M-u= as a good replacement considering it's bound to the rather useless ~upcase-word~ command by default which I most definitely will not miss. #+BEGIN_SRC emacs-lisp (define-key global-map (kbd "C-u") 'kill-whole-line) (define-key global-map (kbd "M-u") 'universal-argument) (define-key universal-argument-map (kbd "C-u") nil) (define-key universal-argument-map (kbd "M-u") 'universal-argument-more) (with-eval-after-load 'evil-maps (define-key evil-motion-state-map (kbd "C-u") 'evil-scroll-up)) #+END_SRC **** Extra keybindings Emacs 24.4 introduced ~electric-indent-mode~ as default which happens to be a global mode. I'm not particularly fond of it (and anything starting with =electric-=), that's why I disable it later after initialization is done and instead bind ~newline-and-indent~ in insert state. #+BEGIN_SRC emacs-lisp (with-eval-after-load 'evil-maps (define-key evil-insert-state-map (kbd "RET") 'newline-and-indent)) #+END_SRC Let's get rid of =;= for the questionable benefit of having a modifier less to hit for entering ex state. #+BEGIN_SRC emacs-lisp (with-eval-after-load 'evil-maps (define-key evil-motion-state-map (kbd ";") 'evil-ex) (define-key evil-visual-state-map (kbd ";") 'evil-ex)) #+END_SRC =U= is a much more fit key for redoing than =C-r=. #+BEGIN_SRC emacs-lisp (with-eval-after-load 'evil-maps (define-key evil-normal-state-map (kbd "U") 'redo)) #+END_SRC The [[https://github.com/janpath/evil-numbers][evil-numbers]] package is pretty nice, but I don't want to use the standard Vim keybinds (=C-a= and =C-x=) for its commands. Instead I'm going for the much more mnemonic =+= and =-=. #+BEGIN_SRC emacs-lisp (with-eval-after-load 'evil-maps (define-key evil-normal-state-map (kbd "-") 'evil-numbers/dec-at-pt) (define-key evil-normal-state-map (kbd "+") 'evil-numbers/inc-at-pt) (define-key evil-visual-state-map (kbd "-") 'evil-numbers/dec-at-pt) (define-key evil-visual-state-map (kbd "+") 'evil-numbers/inc-at-pt) (define-key evil-visual-state-map (kbd "g -") 'evil-numbers/dec-at-pt-incremental) (define-key evil-visual-state-map (kbd "g +") 'evil-numbers/inc-at-pt-incremental)) #+END_SRC The =z= map is full of keybindings I can never remember for dealing with code folding. First of all, get rid of them. #+BEGIN_SRC emacs-lisp (with-eval-after-load 'evil-maps (setcdr evil-normal-state-map (--reject (eq (car-safe it) ?z) (cdr evil-normal-state-map))) (setcdr evil-motion-state-map (--reject (eq (car-safe it) ?z) (cdr evil-motion-state-map)))) #+END_SRC Next, define a few toggling commands and bind them. #+BEGIN_SRC emacs-lisp (defvar my-hs-hide nil "Current state of hideshow for toggling all.") (with-eval-after-load 'evil-common (evil-define-command my-evil-toggle-folds () "Open or close all folds." (setq my-hs-hide (not my-hs-hide)) (if my-hs-hide (hs-hide-all) (hs-show-all)))) (defun my-toggle-mode-line-minor-modes () (interactive) (if rm-blacklist (setq rm-blacklist nil) (setq rm-blacklist ".*")) (force-mode-line-update t)) (defun my-narrow-to-region-with-mode (beg end mode) (interactive (list (region-beginning) (region-end) (completing-read "Major mode: " (mapcar 'cdr auto-mode-alist) nil t))) (unless (region-active-p) (error "No region for narrowing selected")) (narrow-to-region beg end) (deactivate-mark) (funcall (intern mode))) (defun my-revert-buffer () (interactive) (revert-buffer nil t)) (defun my-theme-toggle () (interactive) (cond ((memq 'punpun-light custom-enabled-themes) (disable-theme 'punpun-light) (load-theme 'punpun-dark t)) ((memq 'punpun-dark custom-enabled-themes) (disable-theme 'punpun-dark) (load-theme 'punpun-light t)))) (with-eval-after-load 'evil-maps (define-key evil-normal-state-map (kbd "z r") 'my-revert-buffer) (define-key evil-normal-state-map (kbd "z b") 'magit-blame) (define-key evil-normal-state-map (kbd "z s") 'describe-char) (define-key evil-normal-state-map (kbd "z e") 'toggle-debug-on-error) (define-key evil-normal-state-map (kbd "z q") 'toggle-debug-on-quit) (define-key evil-normal-state-map (kbd "z t") 'my-theme-toggle) (define-key evil-normal-state-map (kbd "z m") 'my-toggle-mode-line-minor-modes) (define-key evil-normal-state-map (kbd "z n") 'my-narrow-to-region-with-mode) (define-key evil-normal-state-map (kbd "z TAB") 'evil-toggle-fold) (define-key evil-normal-state-map (kbd "z ") 'my-evil-toggle-folds)) #+END_SRC Define my most-used helpers (stolen from [[https://github.com/tpope/vim-unimpaired][unimpaired.vim]]) next. #+BEGIN_SRC emacs-lisp (defun my-evil-unimpaired-insert-newline-above (count) "Insert an empty line below point" (interactive "p") (save-excursion (dotimes (i count) (evil-insert-newline-above)))) (defun my-evil-unimpaired-insert-newline-below (count) "Insert an empty line below point" (interactive "p") (save-excursion (dotimes (i count) (evil-insert-newline-below)))) (with-eval-after-load 'evil-maps (define-key evil-normal-state-map (kbd "[ SPC") 'my-evil-unimpaired-insert-newline-above) (define-key evil-normal-state-map (kbd "] SPC") 'my-evil-unimpaired-insert-newline-below)) #+END_SRC Add a few convenience bindings to the window map on =C-w=. #+BEGIN_SRC emacs-lisp (defun my-work-on-scratch () (interactive) (switch-to-buffer (get-buffer-create "*scratch*"))) (with-eval-after-load 'evil-maps (define-key evil-window-map (kbd "n") 'my-work-on-scratch) (define-key evil-window-map (kbd "u") 'winner-undo) (define-key evil-window-map (kbd "b") 'helm-mini) (define-key evil-window-map (kbd "d") 'kill-buffer) (define-key evil-window-map (kbd "D") 'kill-buffer-and-window) (define-key evil-window-map (kbd "C-d") 'kill-buffer-and-window)) #+END_SRC Then some "leader" bindings. #+BEGIN_SRC emacs-lisp (defun my-switch-to-last-buffer () (interactive) (switch-to-buffer (other-buffer))) (defun my-find-file-with-root-privileges (filename) (interactive "F") (let ((pw (concat (password-read "Enter password: ") "\n")) (sudo-process (start-process "Sudo" "*sudo*" "sudo" "-Se" filename))) (process-send-string sudo-process pw))) (with-eval-after-load 'evil-maps (define-key evil-normal-state-map (kbd ", ,") 'my-switch-to-last-buffer) (define-key evil-normal-state-map (kbd ", .") 'helm-mini) (define-key evil-normal-state-map (kbd ", /") 'helm-find-files) (define-key evil-normal-state-map (kbd ", ?") 'my-find-file-with-root-privileges)) #+END_SRC As =calc= keeps bewildering me, but =calculator= doesn't, I'll bind the latter for one-off calculations: #+BEGIN_SRC emacs-lisp (with-eval-after-load 'evil-maps (define-key evil-normal-state-map (kbd "=") 'calculator)) #+END_SRC **** Load supplementary modes Finally, there's a few minor modes depending on Evil being loaded before they are. #+BEGIN_SRC emacs-lisp (defun my-after-evil () (global-evil-surround-mode) (eyebrowse-mode) (eyebrowse-setup-opinionated-keys) (require 'evil-cleverparens-text-objects)) (add-hook 'evil-mode-hook 'my-after-evil) #+END_SRC **** evil-cleverparens I'm not a fan of it rebinding either the small or big word movements. There's unfortunately no option to do neither, so we'll have to undo all those bindings. #+BEGIN_SRC emacs-lisp (with-eval-after-load 'evil-cleverparens (evil-cp--populate-mode-bindings-for-state evil-cp-regular-movement-keys 'normal nil)) #+END_SRC Its insertion replacement inserting spaces isn't terribly helpful. #+BEGIN_SRC emacs-lisp (setq evil-cleverparens-use-regular-insert t) #+END_SRC Why did they change =M-w= from copying the region to pasting something? I like the other bindings though, so I'll leave them as is. #+BEGIN_SRC emacs-lisp (with-eval-after-load 'evil-cleverparens (evil-define-key 'normal evil-cleverparens-mode-map (kbd "M-w") nil)) #+END_SRC It's useful to have slurping/barfing in insert mode so that you can quickly update things while typing. #+BEGIN_SRC emacs-lisp (with-eval-after-load 'evil-maps (define-key evil-insert-state-map (kbd "M-<") 'evil-cp-<) (define-key evil-insert-state-map (kbd "M->") 'evil-cp->)) #+END_SRC This resolves a bug in the change command eating a space when applied on a word: #+BEGIN_SRC emacs-lisp (with-eval-after-load 'evil-vars (push 'evil-cp-change evil-change-commands)) #+END_SRC **** undo-redo As of Emacs 28.1, there's a built-in redo, so I'm enabling that instead of undo-tree. #+BEGIN_SRC emacs-lisp (setq evil-undo-system 'undo-redo) #+END_SRC *** [[https://github.com/capitaomorte/yasnippet][yasnippet]] Snippets are quite useful for boilerplatey languages. Like, Java. Although, if you take it far enough, even something as =org-mode= qualifies considering I can never remember the proper syntax for code blocks. The following sets up a single directory for snippets. #+BEGIN_SRC emacs-lisp (setq yas-snippet-dirs '("~/.config/emacs/snippets")) (with-eval-after-load 'yasnippet (yas-reload-all)) #+END_SRC This package does way too chatty logging by default: #+BEGIN_SRC emacs-lisp (setq yas-verbosity 2) #+END_SRC *** [[https://github.com/jorgenschaefer/circe][Circe]] =rcirc= is too small, [[http://savannah.gnu.org/projects/erc/][ERC]] is too large. So I chose Circe as my IRC client living inside Emacs. As for why IRC in Emacs in the first place, I wanted to leave irssi behind and didn't really like Weechat. So, why not try something extensible? #+BEGIN_SRC emacs-lisp (defun my-face-at-point-p (face) (let ((face-or-faces (get-text-property (point) 'face))) (cond ((consp face-or-faces) (memq face face-or-faces)) ((symbolp face-or-faces) (eq face face-or-faces))))) (defun my-circe-inhibit-nick-highlight-function () (or (eq major-mode 'circe-server-mode) (my-face-at-point-p 'circe-server-face) (my-face-at-point-p 'circe-my-message-face))) (setq circe-inhibit-nick-highlight-function 'my-circe-inhibit-nick-highlight-function) (setq circe-track-faces-priorities '(circe-highlight-nick-face)) #+END_SRC **** Authentication and identification First of all, let's define who I am and change the quit/part message to something less advertising. #+BEGIN_SRC emacs-lisp (setq circe-default-nick "wasamasa" circe-default-user "wasamasa" circe-default-realname "wasamasa" circe-default-part-message "Bye" circe-default-quit-message "Bye") #+END_SRC I'm using [[http://wiki.znc.in/ZNC][ZNC]] to connect to [[http://freenode.net/][Freenode]]. Passwords for the [[https://en.wikipedia.org/wiki/Internet_Relay_Chat_services#NickServ][Nickserv service]] are kept in pass. #+BEGIN_SRC emacs-lisp (defun my-pass-password* (name) (car (process-lines "pass" name))) (fset 'my-pass-password (my-memoize 'my-pass-password*)) (defun my-retrieve-irc-password (_) (cond ((string= circe-server-network "ZNC/freenode") (my-pass-password "irc/znc")) (t (error "Unknown network")))) (setq circe-network-options '(("ZNC/freenode" :host "brause.cc" :port 30832 :user "wasamasa/freenode" :pass my-retrieve-irc-password :use-tls t :reduce-lurker-spam t) ("Freenode" :nick "not_wasamasa"))) #+END_SRC **** Basic usability tweaks I cannot imagine why I wouldn't want to use in-line tab-completion with cycling just as it exists in other IRC clients. #+BEGIN_SRC emacs-lisp (setq circe-use-cycle-completion t) #+END_SRC Highlighting the last read position in buffer is invaluable: #+BEGIN_SRC emacs-lisp (setq lui-track-behavior 'before-switch-to-buffer) #+END_SRC Let's customize a few format strings. #+BEGIN_SRC emacs-lisp (setq circe-format-self-say "<{nick}> {body}" circe-format-server-topic "*** Topic Change by {userhost}: {topic-diff}" circe-server-buffer-name "{network}" circe-prompt-string (propertize ">>> " 'face 'circe-prompt-face)) #+END_SRC Other entities using my nickname are not ghosted by default, that's why I enable it, but only after authenticating in some way. #+BEGIN_SRC emacs-lisp (setq circe-nickserv-ghost-style 'after-auth) #+END_SRC ZNC handles autojoins for me, but Circe does not recognize these. So, instead I'll just ignore all buffers that are opened implicitly. #+BEGIN_SRC emacs-lisp (setq circe-new-buffer-behavior 'ignore) #+END_SRC General highlight settings. #+BEGIN_SRC emacs-lisp (setq circe-track-faces-priorities '(circe-highlight-nick-face lui-highlight-face)) (setq circe-extra-nicks '("wasa" "bagelmasa") lui-formatting-list '(("\\(?:^\\|[[:space:]]\\)\\(\\*[^*[:space:]]+?\\*\\)\\(?:$\\|[[:space:]]\\)" 1 lui-strong-face) ("\\(?:^\\|[[:space:]]\\)\\(_[^_[:space:]]+?_\\)\\(?:$\\|[[:space:]]\\)" 1 underline)) circe-highlight-nick-in-server-messages-p nil circe-highlight-nick-type 'all) #+END_SRC When using the =circe-color-nicks= contrib module, please color nicknames /inside/ messages as well. #+BEGIN_SRC emacs-lisp (setq circe-color-nicks-everywhere t) #+END_SRC Additionally to that, make use of colors more compatible with my theme. #+BEGIN_SRC emacs-lisp (setq circe-color-nicks-pool-type '("#ffaf00" "#d75f00" "#d70000" "#00af00" "#5f00ff" "#0087ff" "#ff005f" "#8700d7")) #+END_SRC Sometimes I get spurious tracking buffers in the modeline, this seems to happen if the frame loses focus. Here's a workaround: #+BEGIN_SRC emacs-lisp (autoload 'tracking-remove-visible-buffers "tracking") (add-hook 'focus-in-hook 'tracking-remove-visible-buffers) #+END_SRC **** Add extra keybinds Let's add a few extra keybindings common in all buffers Circe spawns. I want word killing to behave the same as for =comint=, =C-l= to redraw and reposition and =C-u= to kill the whole line since there's a more appropriate command than the default one bound to =C-u=. #+BEGIN_SRC emacs-lisp (defun my-window-C-l () (interactive) (goto-char (point-max)) (recenter-top-bottom -1)) (with-eval-after-load 'lui (define-key lui-mode-map (kbd " ") 'my-kill-word) (define-key lui-mode-map (kbd " ") 'my-backward-kill-word) (define-key lui-mode-map (kbd "C-l") 'my-window-C-l) (define-key lui-mode-map (kbd "C-u") 'lui-kill-to-beginning-of-line)) #+END_SRC Copy-pasting from other sources (like, browsers) can leave more than one line of text in the input area. Directly sending it would be annoying as this would result in either multiple messages or autopaste detection. To avoid resorting to joining lines manually, I've written a command doing the opposite of =M-q=, but named it similiarly as the intent is the same (making the given text conform to a more suitable form). #+BEGIN_SRC emacs-lisp (defun my-trim-surrounding-lui-whitespace () (save-excursion (let ((bol (line-beginning-position))) (goto-char bol) (skip-syntax-forward "-") (when (/= (point) bol) (delete-region bol (point))) (let ((eol (point-max))) (goto-char eol) (skip-syntax-backward "-" bol) (when (/= (point) eol) (delete-region (point) eol)))))) (defun my-fill-lui-input () (interactive) (fill-delete-newlines lui-input-marker (point-max) nil nil nil) (my-trim-surrounding-lui-whitespace) (goto-char (point-max))) (with-eval-after-load 'lui (define-key lui-mode-map (kbd "M-q") 'my-fill-lui-input)) #+END_SRC Sometimes I like knowing just how many people are online. #+BEGIN_SRC emacs-lisp (defun my-circe-count-nicks () (interactive) (when (eq major-mode 'circe-channel-mode) (message "%i entities are online on %s." (length (circe-channel-nicks)) (buffer-name)))) (with-eval-after-load 'circe (define-key circe-channel-mode-map (kbd "C-c n") 'my-circe-count-nicks)) #+END_SRC There's some occasional pastebin spamming going on at =#chicken=, for that I wrote me a small helper to report it instantly: #+BEGIN_SRC emacs-lisp (defvar my-vandusen-paste-re (rx bol (? space) ; margin property hides here "" (? (+ any)) "\"" (group (+ any)) "\" pasted \"" (group (+ any)) "\" " (group (+ any)) eol)) (defun my-report-spam () (interactive) (save-excursion (re-search-backward my-vandusen-paste-re nil t)) (let ((url (match-string-no-properties 3))) (when url (let ((id (cadr (split-string url "paste\\?id=")))) (insert "vandusen: spam " id))))) (with-eval-after-load 'circe (define-key circe-channel-mode-map (kbd "C-c s") 'my-report-spam)) #+END_SRC I keep forgetting how to send people a memo: #+BEGIN_SRC emacs-lisp (defun my-circe-send-memo (target message) (interactive "sTarget: \nsMessage: ") (cond ((equal circe-chat-target "#chicken") (insert (format "vandusen: tell %s: %s" target message))) ((member circe-chat-target '("#emacs" "#scheme")) (insert (format "rudybot: later tell %s %s" target message))) (t (user-error "Unsupported channel")))) (with-eval-after-load 'circe (define-key circe-channel-mode-map (kbd "C-c m") 'my-circe-send-memo)) #+END_SRC I've registered a few nicks, but need to use them once in a while for them not to expire. The following command makes this task a bit easier: #+BEGIN_SRC emacs-lisp (defun my-next-nick () (interactive) (when (not circe-server-buffer) (user-error "Used outside of a Circe buffer")) (let* ((nicks '("wasamasa" "wasa" "{{{" "}}}")) (current-nick (circe-nick)) (index (--find-index (equal it current-nick) nicks)) (new-nick (nth (mod (1+ index) (length nicks)) nicks))) (insert (format "/nick %s" new-nick)))) (with-eval-after-load 'circe (define-key circe-channel-mode-map (kbd "C-c >") 'my-next-nick)) #+END_SRC I like irssi's key binding to switch to the next active buffer: #+BEGIN_SRC emacs-lisp (with-eval-after-load 'circe (define-key circe-mode-map (kbd "M-a") 'tracking-next-buffer)) #+END_SRC **** Hacks The standard nickname switching function is a bit silly. I own a bunch of nicknames and will use the =wasa= one for switching. #+BEGIN_SRC emacs-lisp (defun my-circe-nick-next (oldnick) (cond ((string= oldnick "wasamasa") "wasa") ((string= oldnick "wasa" "wasamasa")))) (setq circe-nick-next-function 'my-circe-nick-next) #+END_SRC There isn't a highlighting function /yet/ that could do something useful like setting a X urgency hint (taken from [[http://www.emacswiki.org/emacs/JabberEl][the wiki]]), asides from that I want a bit more of control to treat highlights in private queries different from channel highlights. The following code yanks out the default one and replaces it with something slightly better. #+BEGIN_SRC emacs-lisp (defun my-x-urgency-hint (&rest _) (let* ((wm-hints (append (x-window-property "WM_HINTS" nil "WM_HINTS" nil nil t) nil)) (flags (car wm-hints))) (setcar wm-hints (logior flags #x00000100)) (x-change-window-property "WM_HINTS" wm-hints nil "WM_HINTS" 32 t))) (defvar my-circe-hl-function 'my-x-urgency-hint) (defvar my-bot-nicks '("fsbot" "phrik")) (defun my-circe-message-regex-handler (nick userhost command &rest args) (let* ((irc-message (cadr args)) (nick-regexp (regexp-opt (append (list (circe-server-nick)) circe-extra-nicks) 'symbols)) (highlight-match (string-match-p nick-regexp irc-message))) (when (and irc-message (not (member nick my-bot-nicks)) (not (circe--ignored-p nick userhost irc-message)) (not (string-match-p "LAGMON" irc-message)) (or highlight-match (string= (car args) (circe-server-nick)))) (message "HL: %s!%s %s %S" nick userhost command args) (funcall my-circe-hl-function nick userhost command args))) 'noop) (with-eval-after-load 'circe (circe-set-display-handler "PRIVMSG" 'my-circe-message-regex-handler)) #+END_SRC Highlight quoted text in a green color for fun and profit (or to be honest, to discern [[https://www.4chan.org/][4chan]] people from the rest). #+BEGIN_SRC emacs-lisp (defface my-circe-greentext-face '((t (:foreground "spring green"))) "Face for greentext detected in circe.") (defun my-circe-color-greentext () (when (memq major-mode '(circe-channel-mode circe-query-mode)) (let ((body-beg (text-property-any (point-min) (point-max) 'lui-format-argument 'body)) (greentext-regex "\\([^[:space:]]+?: \\)?\\(>[[:word:][:space:]]\\)")) (when body-beg (goto-char body-beg) (when (looking-at greentext-regex) (add-text-properties (match-beginning 2) (point-max) '(face my-circe-greentext-face))))))) (add-hook 'lui-pre-output-hook 'my-circe-color-greentext) #+END_SRC I dislike =custom=, but want persistent fools and ignore. So, advice it is! #+BEGIN_SRC emacs-lisp (defvar my-circe-fool-file "~/.config/emacs/etc/fools") (defvar my-circe-fool-list nil) (defvar my-circe-ignore-file "~/.config/emacs/etc/ignore") (defvar my-circe-ignore-list nil) (defun my-serialize (path data) (with-temp-file path (let (print-length print-level) (insert (prin1-to-string data)) (pp-buffer)))) (defun my-deserialize (path) (when (file-exists-p path) (with-temp-buffer (insert-file-contents-literally path) (goto-char (point-min)) (read (current-buffer))))) (defun my-circe-command-FOOL (line) (let* ((regex (and (string-match "\\S-+" line) (match-string 0 line))) (reason (read-string "Why? "))) (push (list regex reason) my-circe-fool-list)) (my-serialize my-circe-fool-file my-circe-fool-list)) (defun my-circe-command-UNFOOL (line) (let ((regex (and (string-match "\\S-+" line) (match-string 0 line)))) (setq my-circe-fool-list (--remove (equal (car it) regex) my-circe-fool-list))) (my-serialize my-circe-fool-file my-circe-fool-list)) (defun my-circe-command-IGNORE (line) (let* ((regex (and (string-match "\\S-+" line) (match-string 0 line))) (reason (read-string "Why? "))) (push (list regex reason) my-circe-ignore-list)) (my-serialize my-circe-ignore-file my-circe-ignore-list)) (defun my-circe-command-UNIGNORE (line) (let ((regex (and (string-match "\\S-+" line) (match-string 0 line)))) (setq my-circe-ignore-list (--remove (equal (car it) regex) my-circe-ignore-list))) (my-serialize my-circe-ignore-file my-circe-ignore-list)) (advice-add 'circe-command-FOOL :after 'my-circe-command-FOOL) (advice-add 'circe-command-UNFOOL :after 'my-circe-command-UNFOOL) (advice-add 'circe-command-IGNORE :after 'my-circe-command-IGNORE) (advice-add 'circe-command-UNIGNORE :after 'my-circe-command-UNIGNORE) (with-eval-after-load 'circe (setq my-circe-fool-list (my-deserialize my-circe-fool-file)) (setq my-circe-ignore-list (my-deserialize my-circe-ignore-file)) (setq circe-fool-list (mapcar 'car my-circe-fool-list)) (setq circe-ignore-list (mapcar 'car my-circe-ignore-list))) #+END_SRC Let's test having fluid-width windows, now that both Circe and Emacs 24.4 seem to be less wonky about it. Adapted from [[https://github.com/jorgenschaefer/circe/wiki/Configuration#fluid-width-windows][the wiki]], extended to avoid the rather annoying behaviour of the cursor jumping into the fringe when reaching the full text width. #+BEGIN_SRC emacs-lisp (setq lui-time-stamp-position 'right-margin lui-fill-type nil) (defun my-no-fill-lui-setup () (setq fringes-outside-margins t right-margin-width 10 visual-fill-column-extra-text-width '(0 . -10) fill-column 80 wrap-prefix " ") (visual-line-mode) (setf (cdr (assoc 'continuation fringe-indicator-alist)) nil) (make-local-variable 'overflow-newline-into-fringe) (setq overflow-newline-into-fringe nil)) (add-hook 'circe-chat-mode-hook 'my-no-fill-lui-setup) #+END_SRC Port of the pangotext irssi script: #+BEGIN_SRC emacs-lisp (defvar my-pango-color '(("white" . 0) ("black" . 1) ("blue" . 2) ("green" . 3) ("lightred" . 4) ("red" . 5) ("purple" . 6) ("orange" . 7) ("yellow" . 8) ("lightgreen" . 9) ("cyan" . 10) ("lightcyan" . 11) ("lightblue" . 12) ("lightpurple" . 13) ("gray" . 14) ("lightgray" . 15))) (defvar my-pango-color-order ["white" "lightgray" "lightcyan" "lightblue" "lightgreen" "lightpurple" "yellow" "lightred" "orange" "red" "purple" "cyan" "blue" "green" "gray" "black"]) (defvar my-pango-color-ordermap nil) (dotimes (i (length my-pango-color-order)) (push (cons (aref my-pango-color-order i) i) my-pango-color-ordermap)) (defvar my-pango-tag-registry '(("rb" . my-pango-rainbow) ("rainbow" . my-pango-rainbow) ("checker" . my-pango-checker) ("checkers" . my-pango-checker) ("gradient" . my-pango-gradient) ("gradiant" . my-pango-gradient) ("grad" . my-pango-gradient) ("ul" . my-pango-underline) ("underline" . my-pango-underline) ("b" . my-pango-bold) ("bold" . my-pango-bold) ("inv" . my-pango-inverse) ("inverse" . my-pango-inverse))) (defun my-pango-palettize (text palette) (with-temp-buffer (let ((count 0)) (dotimes (i (length text)) (let ((char (aref text i))) (when (not (= (char-syntax char) ?\s)) (insert ?\003 (format "%02d" (aref palette (% count (length palette))))) (setq count (1+ count))) (when (= char ?,) (insert ?,)) (insert char)))) (insert ?\003) (buffer-string))) (defun my-pango-rainbow (text &optional _attribs) (let ((palette [2 3 4 5 6 7 8 9 10 11 12 13])) (my-pango-palettize text palette))) (defun my-pango-gradient (text &optional attribs) (let* ((start (or (cdr (assoc "start" attribs)) "white")) (end (or (cdr (assoc "end" attribs)) "lightpurple")) (start (cdr (assoc start my-pango-color-ordermap))) (end (cdr (assoc end my-pango-color-ordermap))) (min (min start end)) (max (max start end)) (i min) palette) (while (<= i max) (push (cdr (assoc (aref my-pango-color-order (% i (length my-pango-color-order))) my-pango-color)) palette) (setq i (1+ i))) (my-pango-palettize text (vconcat (nreverse palette))))) (defun my-pango-checker (text &optional _attribs) (with-temp-buffer (let ((count 0) (palette ["01,04" "04,01"])) (dotimes (i (length text)) (let ((char (aref text i))) (when (not (= (char-syntax char) ?\s)) (insert ?\003 (aref palette (% count (length palette)))) (setq count (1+ count))) (when (= char ?,) (insert ?,)) (insert char)))) (insert ?\003) (buffer-string))) (defun my-pango-bold (text &optional _attribs) (format "\002%s\002" text)) (defun my-pango-underline (text &optional _attribs) (format "\037%s\037" text)) (defun my-pango-inverse (text &optional _attribs) (format "\026%s\026" text)) (defvar my-pango-tag-rx (rx "<" (* space) (group (+ (not (any ">" space)))) (* space) (group (* (not (any ">")))) ">" (group (+? any)) "<" (? "/") (backref 1) ">")) (defvar my-pango-tag-attr-rx (rx (group (+ (not space))) (* space) "=" (* space) (group (+ (not space))))) (defun my-pango-render (text) (while (string-match my-pango-tag-rx text) (let* ((action (match-string 1 text)) (extra (match-string 2 text)) (msg (match-string 3 text)) (op (cdr (assoc action my-pango-tag-registry))) attribs) (save-match-data (while (string-match my-pango-tag-attr-rx extra) (push (cons (match-string 1 extra) (match-string 2 extra)) attribs) (setq extra (replace-match "" t t extra)))) (when (not op) (user-error "unknown action: %s" action)) (setq text (replace-match (funcall op msg attribs) t t text)))) text) (defun circe-command-PANGO (line) "Send LINE using Pango markup." (interactive "sLine: ") (if (not circe-chat-target) (circe-display-server-message "No target for current buffer") (let* ((colored (my-pango-render line)) (preview (with-temp-buffer (insert colored) (lui-irc-colors) (buffer-string)))) (when (yes-or-no-p (format "Send message? %s" preview)) (circe-command-SAY colored)) (message "")))) #+END_SRC **** Entry point And finally, the function for entering IRC. #+BEGIN_SRC emacs-lisp (defun my-irc () "Connect to all my IRC servers after enabling contrib modules." (interactive) (circe-lagmon-mode) (enable-circe-color-nicks) (enable-lui-autopaste) (enable-lui-track) (require 'circe-chanop) (circe "ZNC/freenode")) #+END_SRC *** =hl-todo= Minor mode for coloring =TODO=, =NOTE=, =FIXME= and many more keywords of that sort prevalent in comments and strings. #+BEGIN_SRC emacs-lisp (setq hl-todo-keyword-faces '(("TODO" . hl-todo) ("NOTE" . hl-todo) ("HACK" . hl-todo) ("FIXME" . hl-todo) ("KLUDGE" . hl-todo))) #+END_SRC *** =macrostep= Expand macros interactively. I'll go with the recommended keybinding for it. #+BEGIN_SRC emacs-lisp (define-key emacs-lisp-mode-map (kbd "C-x e") 'macrostep-expand) #+END_SRC *** =visual-fill-column= There used to be ~longlines-mode~ which did display a file soft-wrapped without breaking words and using the value of ~fill-column~. However it got deprecated for ~visual-line-mode~ which does the same except it doesn't take ~fill-column~ into account. The =visual-fill-column= package fixes that, I want to enable its mode automatically when enabling ~visual-line-mode~. #+BEGIN_SRC emacs-lisp (add-hook 'visual-line-mode-hook 'visual-fill-column-mode) #+END_SRC *** =magit= The very best. For some reason they've started nagging me to opt-in for default behaviour to be used without confirmation. #+BEGIN_SRC emacs-lisp (setq magit-revert-buffers 'silent magit-push-always-verify nil) #+END_SRC Enable inline highlighting of differences for all hunks: #+BEGIN_SRC emacs-lisp (setq magit-diff-refine-hunk 'all) #+END_SRC Insert a helpful reminder about commit message rules if I happen to hack on Emacs: #+BEGIN_SRC emacs-lisp (defun my-git-commit-emacs-setup () (let ((repo-path (expand-file-name "~/code/emacsen/emacs-git"))) (when (and (string-match-p (format "^%s" repo-path) default-directory) (looking-at "^$")) ; don't do this when there's a merge message (insert "Press C in the diff buffer and end each changelog line with a period") (goto-char (point-min))))) (add-hook 'git-commit-setup-hook 'my-git-commit-emacs-setup) #+END_SRC Experimental: #+BEGIN_SRC emacs-lisp (setq magit-display-buffer-function 'display-buffer magit-display-file-buffer-function 'display-buffer) #+END_SRC *** nov.el #+BEGIN_SRC emacs-lisp (add-to-list 'auto-mode-alist '("\\.epub\\'" . nov-mode)) #+END_SRC *** alda-mode Make =:= and indentation less magic, then use this mode for [[https://depp.brause.cc/waka/][waka]]. #+BEGIN_SRC emacs-lisp (with-eval-after-load 'alda-mode (define-key alda-mode-map (kbd ":") 'self-insert-command)) (defun my-alda-mode-setup () (setq indent-line-function 'indent-relative)) (add-hook 'alda-mode-hook 'my-alda-mode-setup) (add-to-list 'auto-mode-alist '("\\.waka\\'" . alda-mode)) #+END_SRC *** rainbow-delimiters-mode I like highlighting the outermost parentheses, but keep the inner ones in a lighter color. This is done by the theme and a customization option: #+BEGIN_SRC emacs-lisp (setq rainbow-delimiters-outermost-only-face-count 1) #+END_SRC *** Elpher Finally a usable Gopher client. I'd like it to open gopher:// URLs for me, yet keep using =xdg-open= for everything else. #+BEGIN_SRC emacs-lisp (defun my-browse-url-elpher (url &rest args) (require 'elpher) (elpher-go url)) ;; emacs 28.1 (setq browse-url-handlers '(("^gopher://" . my-browse-url-elpher) ("." . browse-url-xdg-open))) #+END_SRC Another odd choice is how it customizes Evil to use motion state for its buffers. #+BEGIN_SRC emacs-lisp (with-eval-after-load 'elpher (evil-set-initial-state 'elpher-mode 'emacs)) #+END_SRC *** explain-pause-mode This might help with debugging UX issues. It's a bit too noisy for my taste though, so let's make it more quiet and increase the threshold: #+BEGIN_SRC emacs-lisp (setq explain-pause-alert-via-message nil) (setq explain-pause-blocking-too-long-ms 100) #+END_SRC ** Keybinds We already have =F1= for help, so let's turn =C-h= and =M-h= more readline-like. #+BEGIN_SRC emacs-lisp (global-set-key (kbd "C-h") 'delete-backward-char) (global-set-key (kbd "M-h") 'backward-kill-word) #+END_SRC Deactivate all other uses of insert than =Shift-Insert=. #+BEGIN_SRC emacs-lisp (global-set-key (kbd "") nil) (global-set-key (kbd "") nil) #+END_SRC FWIW, yanking is what Emacs calls /pasting/ text. Unlike Vim where it stands for /copying/ text. Anyways, I want to rectify the curious default of making =S-insert= paste from the same place as =C-y=. #+BEGIN_SRC emacs-lisp (defun my-yank-primary () (interactive) (let ((primary (or (x-get-selection-value) (x-get-selection)))) (unless primary (error "No selection is available")) (push-mark (point)) (insert-for-yank primary))) (global-set-key (kbd "") 'my-yank-primary) #+END_SRC Install a keybind that saves all buffers with asking (use a prefix argument to inhibit the questions), then kills Emacs (including the daemon) on =M-=. #+BEGIN_SRC emacs-lisp (defun my-quit-emacs (arg) (interactive "P") (save-some-buffers (when (consp arg) t) t) (kill-emacs)) (global-set-key (kbd "M-") 'my-quit-emacs) #+END_SRC Make =M-x= more useful, put its original functionality on =C-c M-x= instead. #+BEGIN_SRC emacs-lisp (global-set-key (kbd "M-x") 'helm-smex) (global-set-key (kbd "C-c M-x") 'execute-extended-command) #+END_SRC Helm stuff #+BEGIN_SRC emacs-lisp (global-set-key (kbd "C-x C-f") 'helm-find-files) (global-set-key (kbd "C-x b") 'helm-buffers-list) (global-set-key (kbd "") 'helm-resume) #+END_SRC =C-c C-+= and =C-c C--= are pretty useful, but only resize the current buffer. Here's a hack using ~set-frame-font~ and altering the font size only: #+BEGIN_SRC emacs-lisp (defun my-alter-frame-font-size (fn) (let* ((current-font-name (frame-parameter nil 'font)) (decomposed-font-name (x-decompose-font-name current-font-name)) (font-size (string-to-number (aref decomposed-font-name 5)))) (aset decomposed-font-name 5 (number-to-string (funcall fn font-size))) (set-frame-font (x-compose-font-name decomposed-font-name)))) (defun my-inc-frame-font-size () (interactive) (my-alter-frame-font-size '1+)) (defun my-dec-frame-font-size () (interactive) (my-alter-frame-font-size '1-)) (global-set-key (kbd "C-+") 'my-inc-frame-font-size) (global-set-key (kbd "C-=") 'my-inc-frame-font-size) (global-set-key (kbd "C--") 'my-dec-frame-font-size) #+END_SRC ** Hooks First of all, let's define a helper function that does the boilerplate for us. #+BEGIN_SRC emacs-lisp (defun my-add-function-to-hooks (function hooks) (dolist (hook hooks) (add-hook hook function))) #+END_SRC *** Basic setup I'll start with a list of hooks for everything that's not a special mode or in other words, related to programming and text editing. This will inevitably contain modes that have not been properly derived, might be worth reporting those. #+BEGIN_SRC emacs-lisp (defun my-non-special-modes-setup () (setq indicate-empty-lines t) (setq indicate-buffer-boundaries '((top . left) (bottom . left))) (setq show-trailing-whitespace t) (modify-syntax-entry ?_ "w") (goto-address-mode) (smartparens-mode) (show-smartparens-mode) (yas-minor-mode)) (my-add-function-to-hooks 'my-non-special-modes-setup '(text-mode-hook prog-mode-hook css-mode-hook diff-mode-hook)) #+END_SRC Same deal with programming-related hooks and text-related hooks. #+BEGIN_SRC emacs-lisp (my-add-function-to-hooks 'auto-fill-mode '(text-mode-hook css-mode-hook)) #+END_SRC #+BEGIN_SRC emacs-lisp (defun my-prog-modes-setup () (make-local-variable 'comment-auto-fill-only-comments) (setq comment-auto-fill-only-comments t) (auto-fill-mode) (column-enforce-mode)) (my-add-function-to-hooks 'my-prog-modes-setup '(prog-mode-hook)) #+END_SRC If a file contains a shebang, mark it as executable after saving. #+BEGIN_SRC emacs-lisp (add-hook 'after-save-hook 'executable-make-buffer-file-executable-if-script-p) #+END_SRC *** Mode-specific setup To fine tune completion behavior, I'll prefer ~company-mode~ over ~global-company-mode~. I should add more hooks for REPLs, too. #+BEGIN_SRC emacs-lisp (my-add-function-to-hooks 'company-mode '(prog-mode-hook css-mode-hook nxml-mode-hook sgml-mode-hook css-mode-hook ielm-mode-hook)) #+END_SRC Enable linting for a few select modes by default. The rest is programming languages I don't happen to use, languages I don't have a linter installed for or that would be too annoying to always check. Like, the default =emacs-lisp= (and =emacs-lisp-checkdoc=, too) linter which always assumes I'm writing an Emacs package. #+BEGIN_SRC emacs-lisp (my-add-function-to-hooks 'flycheck-mode '(python-mode-hook ruby-mode-hook enh-ruby-mode-hook LaTeX-mode-hook clojure-mode-hook clojurescript-mode-hook)) (my-add-function-to-hooks 'flymake-mode '(scheme-mode-hook)) #+END_SRC Lisp-specific setup goes here: #+BEGIN_SRC emacs-lisp (defun my-lisp-modes-setup () (rainbow-delimiters-mode) (evil-cleverparens-mode)) (my-add-function-to-hooks 'my-lisp-modes-setup '(emacs-lisp-mode-hook ielm-mode-hook scheme-mode-hook inferior-scheme-mode-hook lisp-mode-hook lisp-interaction-mode-hook clojure-mode-hook nrepl-interaction-mode-hook)) #+END_SRC Colored color codes however only make sense for CSS, HTML and XML. #+BEGIN_SRC emacs-lisp (my-add-function-to-hooks 'rainbow-mode '(css-mode-hook sgml-mode-hook nxml-mode-hook web-mode-hook)) #+END_SRC =smartparens= ought to be enabled in the minibuffer, but only for =M-:=. #+BEGIN_SRC emacs-lisp (defun my-smartparens-minibuffer-setup () (when (eq this-command 'eval-expression) (smartparens-mode) (show-smartparens-mode))) (add-hook 'minibuffer-setup-hook 'my-smartparens-minibuffer-setup) #+END_SRC I don't want .l or .lsp files to be considered CL files, so I'll use =lisp-data-mode= for them. #+BEGIN_SRC emacs-lisp (add-to-list 'auto-mode-alist (cons (rx "." (or "l" "lsp") eos) 'lisp-data-mode)) #+END_SRC Likewise, any .sexp file should be considered Lisp data. #+BEGIN_SRC emacs-lisp (add-to-list 'auto-mode-alist (cons (rx ".sexp" eos) 'lisp-data-mode)) #+END_SRC ** Other software I'm using qutebrowser which provides a =C-e= keybind for editing the contents of the current text box inside my editor. As it creates a file at =/tmp/qutebrowser-editor-.txt=, I'm changing some of the text-mode defaults for nicer editing: #+BEGIN_SRC emacs-lisp (defun my-qutebrowser-edit () (markdown-mode) (auto-fill-mode -1) (setq require-final-newline nil)) (add-to-list 'auto-mode-alist '("\\`/tmp/qutebrowser-editor-" . my-qutebrowser-edit)) #+END_SRC MAL requires me to use modes for lesser known languages, so their config goes there: #+BEGIN_SRC emacs-lisp (defvar inhibit-first-line-modes-regexps nil) ; HACK (add-to-list 'load-path "/usr/local/share/emacs/site-lisp/") (autoload 'smalltalk-mode "smalltalk-mode" "Smalltalk mode" t) (add-to-list 'auto-mode-alist '("\\.st\\'" . smalltalk-mode)) (push 'smalltalk-mode evil-normal-state-modes) (add-hook 'smalltalk-mode-hook 'my-non-special-modes-setup) (add-hook 'smalltalk-mode-hook 'my-prog-modes-setup) (with-eval-after-load 'smalltalk-mode (define-key smalltalk-mode-map "!" nil) (define-key smalltalk-mode-map ":" nil)) (add-to-list 'auto-mode-alist '("\\.cfml?\\'" . js-mode)) (add-to-list 'auto-mode-alist '("\\.cfc\\'" . js-mode)) (defun my-sclang-mode () (require 'sclang) (sclang-mode)) (autoload 'sclang-mode "sclang-mode" "SCLang mode" t) (add-to-list 'auto-mode-alist '("\\.sc\\'" . my-sclang-mode)) (push 'sclang-mode evil-normal-state-modes) (setq sclang-indent-level 4) #+END_SRC I use keychain to manage passphrases for SSH, the following integrates it into Emacs: #+BEGIN_SRC emacs-lisp (defun my-keychain-setup () (let* ((output (shell-command-to-string "keychain --query --quiet id_rsa id_ed25519")) (lines (split-string output "\n" t))) (dolist (line lines) (when (string-match "^\\([^=]+\\)=\\(.*\\)$" line) (setenv (match-string 1 line) (match-string 2 line)))))) #+END_SRC =cloc= replacement: #+BEGIN_SRC emacs-lisp (defun my-cloc () (interactive) (let ((blanks 0) (comments 0) (code 0)) (font-lock-ensure) (save-excursion (save-restriction (when (region-active-p) (narrow-to-region (region-beginning) (region-end))) (goto-char (point-min)) (while (not (eobp)) (skip-syntax-forward " ") (cond ((eolp) (setq blanks (1+ blanks))) ((memq (get-text-property (point) 'face) '(font-lock-comment-face font-lock-comment-delimiter-face)) (setq comments (1+ comments))) (t (setq code (1+ code)))) (forward-line 1)))) (message "blank: %d, comment: %d, code: %d" blanks comments code))) #+END_SRC ** Load unpublished packages Emacs Lisp files can be easily turned into packages one can load and install via =package.el=. While initialization is done, it only happens /after/ successful startup. My unpublished packages reside in =~/.config/emacs/unpublished= and need to be added to the ~load-path~. #+BEGIN_SRC emacs-lisp (add-to-list 'load-path "~/.config/emacs/unpublished") (load "~/.config/emacs/unpublished/private.el") #+END_SRC ** Set up unpublished packages I have a bunch of stuff I'm working on. #+BEGIN_SRC emacs-lisp (require 'helm-smex) #+END_SRC ** Post-Init While packages were prematurely initialized, the following is unconditionally run after init has finished. #+BEGIN_SRC emacs-lisp (defun my-after-init () (setq my-init-time 0) (add-to-list 'load-path "~/code/elisp/mine/evil") (add-to-list 'load-path "~/code/elisp/mine/circe") (autoload 'circe-lagmon-mode "circe-lagmon" "Enable lagmon" t) (autoload 'enable-circe-color-nicks "circe-color-nicks" "Enable coloring nicks" t) (autoload 'enable-lui-autopaste "lui-autopaste" "Enable lui autopaste" t) (autoload 'enable-lui-track "lui-track" "Enable lui track bar" t) (autoload 'circe "circe" "Enable circe" t) (autoload 'nov-mode "nov" nil t) (autoload 'chicken-doc-describe "chicken-doc" nil t) (autoload 'flymake-chicken-backend "flymake-chicken" nil t) (add-to-list 'load-path "~/code/elisp/mine/shackle") (add-to-list 'load-path "~/code/elisp/mine/eyebrowse") (add-to-list 'load-path "~/code/elisp/mine/form-feed") (add-to-list 'load-path "~/code/elisp/mine/firestarter") (add-to-list 'load-path "~/code/elisp/mine/esxml") (add-to-list 'load-path "~/code/elisp/mine/nov.el") (add-to-list 'load-path "~/code/elisp/mine/goto-chg") (add-to-list 'load-path "~/code/elisp/mine/chip8") (add-to-list 'load-path "~/code/elisp/mine/chicken-doc.el") (add-to-list 'load-path "~/code/elisp/mine/flymake-chicken") (add-to-list 'load-path "~/code/elisp/mine/yaml-mode") (add-to-list 'load-path "~/code/elisp/mine/svnwiki-mode") (with-timer (global-so-long-mode)) ;; (with-timer (explain-pause-mode)) (with-timer (sml/setup)) (with-timer (recentf-mode)) (with-timer (savehist-mode)) (with-timer (save-place-mode)) (with-timer (require 'saveplace)) (with-timer (winner-mode)) (with-timer (shackle-mode)) (with-timer (firestarter-mode)) (with-timer (smex-initialize)) (with-timer (helm-mode)) (with-timer (electric-indent-mode -1)) (with-timer (my-setup-hydra)) (with-timer (require 'evil)) (with-timer (evil-mode)) (with-timer (my-disable-tramp-file-handlers)) (with-timer (cl-lib-highlight-initialize)) (with-timer (cl-lib-highlight-warn-cl-initialize)) (with-timer (line-number-mode)) (with-timer (column-number-mode)) (with-timer (my-keychain-setup)) (with-timer (company-tng-configure-default)) (with-timer (global-form-feed-mode)) (with-timer (global-hl-todo-mode)) (load custom-file) (setq gc-cons-threshold 800000) (message "post-init: (%.3fs)" my-init-time)) (add-hook 'after-init-hook 'my-after-init) #+END_SRC * Stuff to do - Learn Yasnippet and write snippets - Check company backends and reconsider their uses - [[https://github.com/wasamasa/dotemacs/blob/master/init.el][Mix in Yasnippet]] - Configure smartparens properly, contribute defaults - Fix show-smartparens on that configuration block - Get modes not working with prog- or text-mode-hook fixed - Use nadvice for ~my-kill-process-buffer-on-exit~ - https://emacs.stackexchange.com/questions/21205/flycheck-with-file-relative-eslint-executable https://github.com/ananthakumaran/tide * Epilogue I consider this experiment successful. I've managed to uncruftify my previous setup, have a highly readable init file and get recommendations and Github favourites from an reasonable amount of people. It went better than expected because explaining my reasoning and hacks in a config file fits me better than doing the same for a program's code. Debugging has suffered a bit due to the indirection. While =C-x C-e= works globally, I no longer have =C-M-x= and the prefix argument version for instrumenting, so I need to copy the piece of code over to a scratch buffer. [[https://github.com/jschaf/esup][esup]] reports an artificially low time, unlike what =M-x emacs-init-time= does.