#+Title: Emacs Customizations #+Author: Ricardo Wurmus #+PROPERTY: noweb tangle #+PROPERTY: mkdirp t #+OPTIONS: tasks:nil toc:1 * Introduction My Emacs configuration is a mess. As I’m writing this my Emacs configuration stretches across multiple files, each containing various snippets of code that seemed like a good idea to group. Unfortunately, there are a some things that don’t have a “natural” home. Enabling the same minor mode in various major modes is one of these cases—do I duplicate the hook and place it in a file for each major mode? Or do I write a new file for the minor mode in which I add it to the major modes at once? With multiple files I spend too much time trying to find the best place for any bit of configuration I add. This slows me down and sometimes I just append to the main =init.el=, so I often feel that my configuration is in need of reorganisation. But configuring Emacs should be fun! I don’t want it to create an uncomfortable clean-up task as a side-effect. This is why I’m now trying to use a literate approach with =org-mode=. My Emacs configuration should be prose first and code second. In my experience, finding the right spot in prose for a new paragraph requires a lot less effort as the text itself acts as a connection between unrelated bits of code. * How to use this? :PROPERTIES: :header-args: :noweb-ref compile-init :END: We take all code blocks in this file and assemble an =init.el= from it if the source file =init.org= is younger. At startup time we check if the =init.el= has to be regenerated. To get started you need to have an =init.el= with at least these contents. #+BEGIN_SRC elisp (setq lexical-binding t) (let ((orgfile (expand-file-name (concat user-emacs-directory "init.org"))) (target (expand-file-name (concat user-emacs-directory "init.el")))) (when (not (file-newer-than-file-p target orgfile)) (progn (require 'org) (org-babel-tangle-file orgfile) (byte-compile-file target) (load target)))) #+END_SRC * Initialise packages :PROPERTIES: :header-args: :noweb-ref packages :END: Emacs is an operating system and I use it as such (see [[http://elephly.net/posts/2016-02-14-ilovefs-emacs.html][this blog post]]). I rely on quite a few extensions that have been made available on various ELPA repositories. Recently, I have moved to installing and managing Emacs packages like any other software package on my system with the functional package manager [[https://gnu.org/s/guix][GNU Guix]]. I find this more reliable, although at first it is slightly less convenient as I can no longer just use =package.el= but first need to package the Elisp code for Guix. I install a Guix profile just for Emacs in =~/.emacs.d/.guix-profile= using Guix Home. #+RESULTS: Make Emacs load packages from the profile: #+begin_src elisp (setq package-directory-list '("/home/rekado/.emacs.d/.guix-profile/share/emacs/site-lisp")) #+end_src * Better defaults :PROPERTIES: :header-args: :noweb-ref better-defaults :END: Emacs defaults are hostile to most people. They are what kept me from using Emacs for many years. I’m easily confused by the way the cursor (point) keeps jumping around when scrolling by pages. Let the cursor keep its screen position constant even when scrolling by full screens and don’t jump around when scrolling. #+BEGIN_SRC elisp (setq scroll-margin 7 scroll-step 1 scroll-conservatively 10000 scroll-preserve-screen-position 1) #+END_SRC Here are a few more simple tweaks: #+BEGIN_SRC elisp ;; No splash screen please ... (setq inhibit-startup-message t) ;; Use UTF-8 by default (prefer-coding-system 'utf-8-unix) ;; Keep all visited files in recentf (setq recentf-max-saved-items nil) ;; When point is on a file name and find-file is used, populate the prompt with the name of the file at point. (ffap-bindings) ;; display tool tips in echo area only (tooltip-mode -1) (tool-bar-mode 0) (menu-bar-mode 0) ;; by default Emacs will only resize the frame line by line (setq frame-resize-pixelwise t) ;; don’t force me to input “yes” or “no” (defalias 'yes-or-no-p 'y-or-n-p) ;; Use ibuffer instead of list-buffer (global-set-key (kbd "C-x C-b") 'ibuffer) ;; disable mouse scrolling (mouse-wheel-mode -1) ;(set-frame-parameter nil 'undecorated t) #+END_SRC I don’t use Helm because it’s too “busy” but I do want a more intelligent way to select buffers and find files. #+BEGIN_SRC elisp (require 'use-package) (use-package vertico :ensure t :init (vertico-mode)) #+END_SRC Save recent completion selections. #+BEGIN_SRC elisp (use-package savehist :init (savehist-mode)) #+END_SRC Display more information for vertico choices. #+BEGIN_SRC elisp (use-package marginalia :after vertico :ensure t :custom (marginalia-annotators '(marginalia-annotators-heavy marginalia-annotators-light nil)) :init (marginalia-mode)) #+END_SRC Match any part in any order. #+BEGIN_SRC elisp (use-package orderless :ensure t :custom (completion-styles '(orderless basic)) (completion-category-overrides '((file (styles basic partial-completion))))) #+END_SRC Also tell Emacs that I want to have a separate file for all other customisations that are handled through =M-x customize=. ,#+BEGIN_SRC elisp ;; Keep emacs Custom-settings in separate file (setq custom-file (expand-file-name "custom.el" user-emacs-directory)) (load custom-file) #+END_SRC * Theme :PROPERTIES: :header-args: :noweb-ref theme :END: ** Use only one theme at a time The default behavior of Emacs is that you can compose multiple themes; however, in practice that’s never done and will likely just mess things up. With this little advice, we tell Emacs that once a theme is loaded, all prior themes should be disabled. #+BEGIN_SRC elisp (defadvice load-theme (before theme-dont-propagate activate) (progn (mapc #'disable-theme custom-enabled-themes) (run-hooks 'after-load-theme-hook))) #+END_SRC I like to highlight source buffers to distinguish them from side windows, so I enable =solaire-mode= first. #+BEGIN_SRC elisp (solaire-global-mode +1) #+END_SRC The Modus themes look pretty nice. #+BEGIN_SRC elisp (setq modus-themes-mode-line '(borderless)) (setq modus-themes-region '(bg-only)) (setq modus-themes-completions '(opinionated)) (setq modus-themes-paren-match '(bold intense)) (setq modus-themes-syntax nil) (setq modus-themes-org-agenda '((header-block . (variable-pitch scale-title)) (header-date . (grayscale workaholic bold-today)) (event . (accented scale-small)) (scheduled . uniform) (habit . traffic-light))) (setq modus-themes-headings '((1 . (bold 1.4)) (2 . (bold 1.3)) (3 . (bold 1.2)) (t . (semilight 1.1)))) (setq modus-themes-scale-headings t) (setq modus-themes-bold-constructs t) (setq modus-themes-org-blocks 'gray-background) (setq modus-themes-variable-pitch-headings t) (load-theme 'modus-vivendi t) #+END_SRC Dired mode becomes much prettier with =all-the-icons=. #+BEGIN_SRC elisp (add-hook 'dired-mode-hook 'all-the-icons-dired-mode) #+END_SRC I like the doom emacs modeline with buffer-appropriate icons. #+BEGIN_SRC elisp (use-package doom-modeline :after eshell ;Make sure it gets hooked after eshell :init (doom-modeline-mode) :custom (doom-modeline-height 35) (doom-modeline-bar-width 6) (doom-modeline-lsp t) (doom-modeline-github nil) (doom-modeline-mu4e nil) (doom-modeline-irc t) (doom-modeline-minor-modes nil) (doom-modeline-persp-name nil) (doom-modeline-buffer-file-name-style 'truncate-except-project) (doom-modeline-major-mode-icon nil)) #+END_SRC * Default fonts :PROPERTIES: :header-args: :noweb-ref default-fonts :END: I like pretty faces. For coding I like to use the DejaVu Sans Mono font. In =org-mode= and in =eww= I like to use a font with variable pitch instead of the default mono-spaced font. I find Linux Biolinum pretty, especially when it’s rendered large. #+BEGIN_SRC elisp (set-frame-font "DejaVu Sans Mono") (set-face-attribute 'variable-pitch nil :height 1.25 :family "Linux Biolinum") #+END_SRC * Guix :PROPERTIES: :header-args: :noweb-ref guix :END: Store paths have long hashes. In most cases I don’t really care, so I use =guix-prettify-mode= to hide them. #+BEGIN_SRC elisp (when (require 'guix-prettify nil t) (global-guix-prettify-mode)) #+END_SRC I’m often building Guix packages in the shell. =guix-build-log-minor-mode= gives me key bindings to fold and jump over build phases, and it adds pretty faces to the otherwise bland wall of text. #+BEGIN_SRC elisp (add-hook 'shell-mode-hook 'guix-build-log-minor-mode) #+END_SRC I’m monitoring the Guix build farm =berlin.guixsd.org=, which is hosted at the MDC. #+BEGIN_SRC elisp (setq guix-hydra-url "https://ci.guix.gnu.org") #+END_SRC For bug and patch tracking the Guix project uses debbugs. Here are some better defaults for using =debbugs-gnu= with Guix: #+BEGIN_SRC elisp (eval-when-compile (require 'debbugs-gnu)) (with-eval-after-load "debbugs-gnu" (setq debbugs-gnu-default-packages '("guix" "guix-patches")) (add-to-list 'debbugs-gnu-all-packages "guix-patches")) #+END_SRC Oleg Pykhalov shared this useful snippet to list bugs for which I am listed as the owner. #+BEGIN_SRC elisp (with-eval-after-load "debbugs-gnu" (defun my/debbugs-gnu () (interactive) (let ((debbugs-gnu-current-query `((submitter . ,user-mail-address)))) (debbugs-gnu nil)))) #+END_SRC When working on Guix it helps to reduce boilerplate with snippets. I like to have YASnippet enabled and let it use the snippets that are provided with Guix: #+BEGIN_SRC elisp (require 'yasnippet) (add-to-list 'yas-snippet-dirs "~/dev/gx/branches/master/etc/snippets") (yas-global-mode) #+END_SRC * Manuals :PROPERTIES: :header-args: :noweb-ref manuals :END: Also in Info manuals I want to use variable-pitch fonts where possible. Unfortunately, Info manuals don’t contain enough semantic markup, so I cannot selectively use a monospace font for examples or inline code and use a variable pitch font for everything else. So I just use variable pitch in headings. #+BEGIN_SRC elisp (require 'info) (set-face-attribute 'info-title-1 nil :inherit 'variable-pitch :height 1.3) (set-face-attribute 'info-title-2 nil :inherit 'variable-pitch :height 1.3) (set-face-attribute 'info-title-3 nil :inherit 'variable-pitch :height 1.3) (set-face-attribute 'info-menu-header nil :inherit 'variable-pitch :height 1.1) #+END_SRC Since Emacs 25 there is a new face for quoted expressions in Info manuals. By default it uses the “courier” font, which looks terrible. #+BEGIN_SRC elisp (set-face-attribute 'Info-quoted nil :inherit 'fixed-pitch :family "Monospace") #+END_SRC * Org-mode :PROPERTIES: :header-args: :noweb-ref org-mode :noweb yes :END: This is my org mode configuration. Much of it is in one big blob and I haven’t yet taken the time to document it. #+BEGIN_SRC elisp (require 'org-indent) (setq org-ellipsis "⤵") (setq org-src-fontify-natively t) (global-set-key (kbd "C-c o l") 'org-store-link) (global-set-key (kbd "C-c o a") 'org-agenda) ;; TODO: make these available in org-mode only (global-set-key (kbd "C-c o s") 'org-schedule) (setq org-log-done t) (setq org-return-follows-link t) (setq org-directory "~/Documents/org") (setq org-agenda-files (mapcar (lambda (x) (concat org-directory x)) (list "/master.org" "/email.org" "/home.org" "/birthdays.org"))) (setq org-default-notes-file (concat org-directory "/notes.org")) (setq org-agenda-custom-commands '(("w" todo "WAITING" nil) ("n" todo "NEXT" nil) ("d" "Agenda + Next Actions" ((agenda) (todo "NEXT"))))) (setq org-fontify-done-headline t) (require 'org-bullets) (setq org-bullets-bullet-list '("◉" "○" "◇" "◇")) (add-hook 'org-mode-hook (lambda () (org-bullets-mode 1) (variable-pitch-mode 1) (visual-line-mode 1))) (defun gtd () (interactive) (find-file (concat org-directory "/master.org"))) #+END_SRC I don’t like the way org-mode looks by default. It’s noisy, too colourful and seems old-fashioned to the point of being somewhat unattractive. This is why I find it important to change some of the default faces. Since I enable =variable-pitch-mode= in my org-mode buffers I also need to explicitly make a few faces inherit from the =fixed-width= face to be rendered with a monospaced font. #+BEGIN_SRC elisp (set-face-attribute 'org-done nil :strike-through t) (set-face-attribute 'org-headline-done nil :strike-through t :foreground "light gray") (set-face-attribute 'org-document-title nil :height 1.6 :foreground (face-attribute 'default :foreground)) (set-face-attribute 'org-level-1 nil :height 1.0 :foreground (face-attribute 'default :foreground)) (set-face-attribute 'org-level-2 nil :height 0.9 :foreground (face-attribute 'default :foreground)) (set-face-attribute 'org-level-3 nil :height 0.8 :foreground (face-attribute 'default :foreground)) (set-face-attribute 'org-level-4 nil :height 0.8 :foreground (face-attribute 'default :foreground)) (set-face-attribute 'org-verbatim nil :background "#efe9d6") (dolist (face '(org-meta-line org-document-info-keyword org-special-keyword)) (set-face-attribute face nil :foreground "#93a1a1" :weight 'normal)) (dolist (face '(org-block-begin-line org-block-end-line org-block org-table org-meta-line org-document-info-keyword org-special-keyword org-verbatim org-todo org-tag org-done org-hide org-indent org-checkbox)) (set-face-attribute face nil :inherit 'fixed-pitch) ;; TODO: this is ugly. When scaling up the variable-pitch face the ;; fixed-pitch face will become even larger. (set-face-attribute face nil :height 0.8)) (set-face-attribute 'org-tag nil :foreground (face-attribute 'default :foreground) :weight 'normal :height (face-attribute 'default :height) :overline nil :underline nil :box '(:line-width -1 :color "#859900")) #+END_SRC To ensure that indented blocks line up with their headings despite using =variable-pitch-mode= we set the indentation character to =*= and hide it by setting the foreground colour to the same as the default background colour. #+BEGIN_SRC elisp (setq org-indent-boundary-char ?*) (set-face-attribute 'org-indent nil :foreground (face-attribute 'default :background)) #+END_SRC =variable-pitch-mode= also makes it impossible to align tags at a fixed column, so I don’t. Instead I just let tags appear right behind the heading. #+BEGIN_SRC elisp (setq org-tags-column 0) #+END_SRC The following snippet is an attempt to prettify the somewhat ugly headers of source code blocks in =org-mode=. The snippet was taken from [[https://pank.eu/blog/pretty-babel-src-blocks.html][the blog of Rasmus Pank]] and slightly modified to suit my needs. #+BEGIN_SRC elisp (defvar-local my/org-at-src-begin -1 "Variable that holds whether last position was an org source code block.") (defvar-local my/org-src-begin-regexp "^[ \t]*#\\+begin_src[ \t]+[^ \f\t\n\r\v]+[ \t]*") (defvar my/ob-header-symbol ?☰ "Symbol used for babel headers") (defun my/org-prettify-src--update () (let ((case-fold-search t) (re my/org-src-begin-regexp) found) (save-excursion (goto-char (point-min)) (while (re-search-forward re nil t) (goto-char (match-end 0)) (let ((args (org-trim (buffer-substring-no-properties (point) (line-end-position))))) (when (org-string-nw-p args) (let ((new-cell (cons args my/ob-header-symbol))) (cl-pushnew new-cell prettify-symbols-alist :test #'equal) (cl-pushnew new-cell found :test #'equal))))) (setq prettify-symbols-alist (cl-set-difference prettify-symbols-alist (cl-set-difference (cl-remove-if-not (lambda (elm) (eq (cdr elm) my/ob-header-symbol)) prettify-symbols-alist) found :test #'equal))) ;; Clean up old font-lock-keywords. (font-lock-remove-keywords nil prettify-symbols--keywords) (setq prettify-symbols--keywords (prettify-symbols--make-keywords)) (font-lock-add-keywords nil prettify-symbols--keywords) (while (re-search-forward re nil t) (font-lock-flush (line-beginning-position) (line-end-position))) ;; Toggle prettify-symbols-mode to restore composition of ;; regions on which the "composition" text-property was deleted. (prettify-symbols-mode -1) (prettify-symbols-mode +1)))) (defun my/org-prettify-src () "Hide src options via `prettify-symbols-mode'. `prettify-symbols-mode' is used because expanding is simple. It’s not very efficient and maybe should be implemented using overlays." (let* ((case-fold-search t) (at-src-block (save-excursion (beginning-of-line) (looking-at my/org-src-begin-regexp)))) ;; Test if we moved out of a block. (cond ((or (and my/org-at-src-begin (not at-src-block)) ;; File was just opened. (eq my/org-at-src-begin -1)) (my/org-prettify-src--update)) ;; Remove composition if at line (at-src-block (with-silent-modifications (remove-text-properties (match-end 0) (1+ (line-end-position)) '(composition))))) (setq my/org-at-src-begin at-src-block))) (defun my/org-prettify-symbols () (mapc (apply-partially 'add-to-list 'prettify-symbols-alist) (cl-reduce 'append (mapcar (lambda (x) (list x (cons (upcase (car x)) (cdr x)))) `(("#+begin_src" . ?✎) ;; ➤ 🖝 ➟ ➤ ✎ ("#+end_src" . ?□) ;; ⏹ ("#+header:" . ,my/ob-header-symbol) ("#+begin_quote" . ?«) ("#+end_quote" . ?»))))) (turn-on-prettify-symbols-mode) (add-hook 'post-command-hook 'my/org-prettify-src t t)) (add-hook 'org-mode-hook #'my/org-prettify-symbols) #+END_SRC I use the capture feature to quickly record ideas and tasks, and to create links to emails that I need to work on. This is much better than just leaving these emails in my Inbox. #+BEGIN_SRC elisp (global-set-key (kbd "C-c o c") 'org-capture) (setq org-capture-templates '(("t" "Task" entry (file+headline (concat org-directory "/home.org") "Tasks") "* %?\n %i\n %a") ("m" "Email" entry (file+headline (concat org-directory "/email.org") "Email") "* Reply to %:fromname%? :email:\n [%:date]\n To: %:to\n %a"))) #+END_SRC For exporting org documents to PDF I use =lualatex= instead of the default =pdflatex=. I also use =biber= to refresh the bibliography. This requires me to change the value of =org-latex-pdf-process=. #+BEGIN_SRC elisp (setq org-latex-pdf-process '("lualatex -interaction nonstopmode -output-directory %o %f" "biber %b" "lualatex -interaction nonstopmode -output-directory %o %f" "lualatex -interaction nonstopmode -output-directory %o %f")) #+END_SRC Org mode is an excellent environment for literate programming through Babel. Here I configure a couple of common languages to be used with Babel. #+BEGIN_SRC elisp (require 'ob-shell) (require 'ob-scheme) (require 'ob-R) #+END_SRC After editing source snippets with =C-c '= please don’t indent everything with two spaces. Just leave things as I edited them. #+BEGIN_SRC elisp (setq org-edit-src-content-indentation 0) #+END_SRC All of this should be loaded lazily. #+BEGIN_SRC elisp :noweb-ref org-mode-lazy (with-eval-after-load "org" <> ) #+END_SRC * Editing files on remote systems :PROPERTIES: :header-args: :noweb-ref tramp :END: TRAMP is a really convenient way to edit files on remote systems from within the comfort of my cozy customised local Emacs session. I use it to edit files at work, to edit things on my server =elephly.net=, and even to edit things as root on the local system. #+BEGIN_SRC elisp (require 'tramp) (setq tramp-default-method "ssh") (setq tramp-default-proxies-alist (list ;; Do not use a proxy on the same system. '((regexp-quote (system-name)) nil nil) ;; For root connections to remote hosts, log in via ssh with normal ;; user account first, then su/sudo to root '("elephly\\.net\\'" "\\`root\\'" "/ssh:%h:") ;; Pass through ssh1 as user ‘rwurmus’ to reach remote hosts on ;; MDC network. '("mdc-berlin\\.net" "\\`rwurmus\\'" "/ssh:rwurmus@ssh1.mdc-berlin.de:"))) ;; ssh1 runs a restricted shell session, so "exec ssh" cannot be used. (add-to-list 'tramp-restricted-shell-hosts-alist "\\`ssh1\\.mdc-berlin\\.de\\'") ;; respect the PATH variable on the remote machine (add-to-list 'tramp-remote-path 'tramp-own-remote-path) (add-to-list 'tramp-remote-path "~/.guix-profile/bin") (setq tramp-verbose 3) #+END_SRC I’m not using this yet. I’d like to figure out how to make TRAMP use =ssh1= as a proxy only when I’m not connected to the institute’s network. #+BEGIN_SRC elisp :noweb-ref nil (defun at-work-p () "Tell me if I’m connected to the network at work." (with-temp-buffer (call-process "ip" nil t nil "route" "list" "root" "141.80/16") ;; There is no output when there is no route with this prefix. (not (equalp (point-min) (point-max))))) #+END_SRC * Shell :PROPERTIES: :header-args: :noweb-ref shell :noweb yes :END: I used to like Eshell a lot. Eshell is a shell implemented in Elisp. It is well integrated with the rest of Emacs. For example, you can pipe commands to buffers and use TRAMP paths right on the command line. Nowadays I’m no longer using it much because for most purposes =shell-mode= is more mature. Nevertheless, here is some configuration to make Eshell a little more usable. #+BEGIN_SRC elisp :noweb-ref eshell (require 'eshell) (setq eshell-history-size 10000) ;; author: KaiGrossjohann on EmacsWiki (defun eshell/ff (&rest args) "Invoke `find-file' on the file. \"ff +42 foo\" also goes to line 42 in the buffer." (while args (if (string-match "\\`\\+\\([0-9]+\\)\\'" (car args)) (let* ((line (string-to-number (match-string 1 (pop args)))) (file (pop args))) (find-file file) (forward-line (1- line))) (find-file (pop args))))) ;; convenience functions to input the remote root/home dir when in a ;; directory on a remote host (defun my/tramp-root () "Print root directory on the remote host." (interactive) (let ((pieces (split-string (eshell/pwd) ":/"))) (insert (if (> (length pieces) 1) (concat (car pieces) ":/") "/")))) (defun my/tramp-home () "Print home directory path on the remote host." (interactive) (let ((pieces (split-string (eshell/pwd) ":/"))) (insert (if (> (length pieces) 1) (concat (car pieces) ":~/") "~/")))) (define-key eshell-mode-map (kbd "C-c /") 'my/tramp-root) (define-key eshell-mode-map (kbd "C-c ~") 'my/tramp-home) #+END_SRC Of course, all of this should only be loaded when Eshell is used. #+BEGIN_SRC elisp (with-eval-after-load "eshell" <> ) #+END_SRC TODO: here’s the rest of my shell configuration: #+BEGIN_SRC elisp (require 'shell-switcher) (setq shell-switcher-mode t) (add-hook 'eshell-mode-hook 'shell-switcher-manually-register-shell) (add-hook 'shell-mode-hook 'shell-switcher-manually-register-shell) (setq shell-switcher-new-shell-function 'shell-switcher-make-shell) ;; use cat as the pager in shell mode, because shell-mode is not an ;; ANSI terminal (setenv "PAGER" "cat") ;; C-d on an empty line in the shell terminates the process. (defun my/comint-delchar-or-eof-or-kill-buffer (arg) (interactive "p") (if (null (get-buffer-process (current-buffer))) (kill-buffer) (comint-delchar-or-maybe-eof arg))) (add-hook 'shell-mode-hook (lambda () ;; needed for proper display of "ls" (setq tab-width 8) ;; load shared bash history (setq comint-input-ring-file-name "~/.bash_history") (comint-read-input-ring t) (define-key shell-mode-map (kbd "C-d") 'my/comint-delchar-or-eof-or-kill-buffer) (define-key shell-mode-map (kbd "") 'comint-previous-matching-input-from-input))) ;; Show current path instead of just "*shell*<2>" (setq uniquify-buffer-name-style 'forward) (setq uniquify-min-dir-content 1024) (require 'uniquify) #+END_SRC * Magit :PROPERTIES: :header-args: :noweb-ref magit :noweb yes :END: #+BEGIN_SRC elisp ;; full screen magit-status (defadvice magit-status (around magit-fullscreen activate) (window-configuration-to-register :magit-fullscreen) ad-do-it (delete-other-windows)) (defun my/magit-quit-session () "Restores the previous window configuration and kills the magit buffer" (interactive) (kill-buffer) (jump-to-register :magit-fullscreen)) (defun my/magit-toggle-whitespace () "Toggles git option -w" (interactive) (if (member "-w" magit-diff-arguments) (my/magit-dont-ignore-whitespace) (my/magit-ignore-whitespace))) (defun my/magit-ignore-whitespace () "Adds git option -w" (interactive) (add-to-list 'magit-diff-arguments "-w") (magit-refresh)) (defun my/magit-dont-ignore-whitespace () "Removes git option -w" (interactive) (setq magit-diff-arguments (remove "-w" magit-diff-arguments)) (magit-refresh)) (define-key magit-status-mode-map (kbd "q") 'my/magit-quit-session) (define-key magit-status-mode-map (kbd "W") 'my/magit-toggle-whitespace) (setq magit-diff-refine-hunk 'all) #+END_SRC #+BEGIN_SRC elisp :noweb-ref magit-lazy (eval-when-compile (require 'magit)) (with-eval-after-load "magit" <> ) (global-set-key (kbd "C-c m") 'magit-status) #+END_SRC * Completion :PROPERTIES: :header-args: :noweb-ref completion :noweb yes :END: Company mode provides automatic completion. I like to enable it in all programming modes. I don’t use the global company mode because that would enable it in =org-mode= where the pop-up looks terrible with =variable-pitch-mode= enabled. #+BEGIN_SRC elisp :noweb-ref company-hook (add-hook 'prog-mode-hook 'company-mode) #+END_SRC I like automatic completion, but it’s nice to also have a key to trigger completion. #+BEGIN_SRC elisp (setq company-idle-delay 0.5) (define-key company-mode-map (kbd "C-c ") 'company-complete) #+END_SRC #+BEGIN_SRC elisp (require 'color) (let ((bg (face-attribute 'default :background))) (custom-set-faces `(company-tooltip ((t (:inherit default :background ,(color-lighten-name bg 2))))) `(company-scrollbar-bg ((t (:background ,(color-lighten-name bg 10))))) `(company-scrollbar-fg ((t (:background ,(color-lighten-name bg 5))))) `(company-tooltip-selection ((t (:inherit font-lock-function-name-face)))) `(company-tooltip-common ((t (:inherit font-lock-constant-face)))))) #+END_SRC I also use snippets for commonly typed expressions. #+BEGIN_SRC elisp :noweb-ref yas (yas-global-mode 1) #+END_SRC Load all of this lazily. #+BEGIN_SRC elisp :noweb-ref completion-lazy (with-eval-after-load "company" <> ) <> (with-eval-after-load "yasnippet" <> ) (require 'yasnippet) #+END_SRC * Paste :PROPERTIES: :header-args: :noweb-ref paste :noweb yes :END: The =scpaste= package allows me to quickly paste text to my personal web site. It only needs to know where to place the text files and where they would be publicly accessible via HTTP. #+BEGIN_SRC elisp (setq scpaste-http-destination "https://elephly.net/paste") (setq scpaste-scp-destination "elephly.net:~/elephly.net/paste/") (setq scpaste-scp-port "1022") (setq scpaste-make-name-function #'scpaste-make-name-from-timestamp) #+END_SRC * Pretty symbols :PROPERTIES: :header-args: :noweb-ref pretty-symbols :END: #+BEGIN_SRC elisp (defun my/pretty-js-symbols () (push '("===" . ?≡) prettify-symbols-alist) (push '("function" . ?𝑓) prettify-symbols-alist)) (defun my/pretty-r-symbols () (push '("%>%" . ?⤚) prettify-symbols-alist) (push '("%$%" . ?⤜) prettify-symbols-alist) (push '("==" . ?≡) prettify-symbols-alist) (push '("function" . ?𝑓) prettify-symbols-alist)) (when (boundp 'global-prettify-symbols-mode) (add-hook 'js2-mode-hook 'my/pretty-js-symbols) (add-hook 'ess-mode-hook 'my/pretty-r-symbols) (add-hook 'inferior-ess-mode-hook 'my/pretty-r-symbols) (global-prettify-symbols-mode +1)) #+END_SRC * Resize buffer margins dynamically :PROPERTIES: :header-args: :noweb-ref resize-dynamically :END: I don’t want to have any margins by default. #+BEGIN_SRC elisp (setq-default left-margin-width 0 right-margin-width 0) #+END_SRC When writing Org-mode documents or when browsing the web with Eww I prefer to see shorter lines. =olivetti-mode= adjusts the buffer margins such that the buffer contents are restricted in width and centered. I find this much more readable when editing Org documents or browsing with Eww. #+BEGIN_SRC elisp (require 'olivetti) (add-hook 'org-mode-hook #'olivetti-mode) #+END_SRC * Multimedia with EMMS :PROPERTIES: :header-args: :noweb-ref emms :noweb yes :END: Not EMMS but MPC: #+BEGIN_SRC elisp (setq simple-mpc-arguments "-h 192.168.178.20") #+END_SRC * Modeline :PROPERTIES: :header-args: :noweb-ref modeline :END: #+BEGIN_SRC elisp (doom-modeline-mode) #+END_SRC * Lilypond :PROPERTIES: :header-args: :noweb-ref lilypond :END: Activate Lilypond mode when I’m opening a Lilypond score. #+BEGIN_SRC elisp (require 'lilypond-mode) (add-to-list 'auto-mode-alist '("\\.ly\\'" . LilyPond-mode)) #+END_SRC Enable =subword-mode= in Lilypond files because I use CamelCase for music variables. #+BEGIN_SRC elisp (add-hook 'LilyPond-mode-hook 'subword-mode) #+END_SRC I like to render Lilypond snippets in Org mode buffers. To do that I need to load the Lilypond backend first. However, I don’t think this should be enabled by default. #+BEGIN_SRC elisp :tangle nil (with-eval-after-load "org" (require 'ob-lilypond)) #+END_SRC * Scheme development :PROPERTIES: :header-args: :noweb-ref scheme :noweb yes :END: Geiser makes Scheme development really nice. It’s also used for Guix development in combination with =guix-devel-mode=, so I’m adding the Guix development directory to Guile’s load path in all Geiser sessions. #+BEGIN_SRC elisp (with-eval-after-load "geiser" (setq geiser-active-implementations '(guile)) (setq geiser-guile-load-path '("~/dev/gx/branches/master"))) #+END_SRC Automatically start =guix-devel-mode= when in =scheme-mode= because I’m likely working on Guix anyway. #+BEGIN_SRC elisp (add-hook 'scheme-mode-hook 'guix-devel-mode) #+END_SRC Parentheses don’t annoy me but I still prefer to have them fade into the background a little. This is what =paren-face-mode= does. #+BEGIN_SRC elisp :noweb-ref paren-face-hook (require 'paren-face) (global-paren-face-mode 1) #+END_SRC Emacs also highlights matching parentheses, but it does so with a delay. Here I’m disabling the delay. #+BEGIN_SRC elisp :noweb-ref show-paren-hook (require 'paren) (setq show-paren-delay 0) (show-paren-mode 1) #+END_SRC Editing lispy languages is no fun without =paredit=, a mode to enforce balanced parentheses. Enable it automatically when editing Scheme, Common Lisp, or Elisp. #+BEGIN_SRC elisp (add-hook 'scheme-mode-hook (lambda () (paredit-mode 1))) (add-hook 'emacs-lisp-mode-hook (lambda () (paredit-mode 1))) (add-hook 'lisp-mode-hook (lambda () (paredit-mode 1))) (add-hook 'geiser-repl-mode-hook (lambda () (paredit-mode 1))) #+END_SRC Also enable =paredit= when editing Elisp in the minibuffer. #+BEGIN_SRC elisp ;; Enable `paredit-mode' in the minibuffer, during `eval-expression'. (defun conditionally-enable-paredit-mode () (if (eq this-command 'eval-expression) (paredit-mode 1))) (add-hook 'minibuffer-setup-hook 'conditionally-enable-paredit-mode) #+END_SRC Some customisations for =paredit=. #+BEGIN_SRC elisp (eval-when-compile (require 'paredit)) (with-eval-after-load "paredit" ;; don't hijack \ please (define-key paredit-mode-map (kbd "\\") nil) ;; keybindings (define-key paredit-mode-map (kbd "M-C-") 'backward-kill-sexp) ;; making paredit work with delete-selection-mode (put 'paredit-forward-delete 'delete-selection 'supersede) (put 'paredit-backward-delete 'delete-selection 'supersede) (put 'paredit-newline 'delete-selection t)) #+END_SRC TODO: the parentheses adjustments should happen only when I’m in programming mode. #+BEGIN_SRC elisp <> <> #+END_SRC * God mode :PROPERTIES: :header-args: :noweb-ref god :END: #+BEGIN_SRC elisp (eval-when-compile (require 'god-mode)) (eval-after-load "god-mode" '(progn (defadvice god-mode-lookup-key-sequence (before my-swap-x-t) "Swap ?x and ?t KEY arguments." (case (ad-get-arg 0) (?x (ad-set-arg 0 ?t)) (?t (ad-set-arg 0 ?x)))) (ad-activate 'god-mode-lookup-key-sequence) (defvar original-color (frame-parameter nil 'cursor-color)) (defun my-update-cursor () (if god-local-mode (set-cursor-color "Red") (set-cursor-color original-color))) (define-key god-local-mode-map (kbd ".") 'repeat) (add-hook 'god-mode-enabled-hook 'my-update-cursor) (add-hook 'god-mode-disabled-hook 'my-update-cursor) ;; reliably toggle god-mode for all modes (setq god-exempt-major-modes nil) (setq god-exempt-predicates nil) (defun god-toggle-on-overwrite () "Toggle god-mode on overwrite-mode." (if (bound-and-true-p overwrite-mode) (god-local-mode-pause) (god-local-mode-resume))) (add-hook 'overwrite-mode-hook 'god-toggle-on-overwrite) (define-key god-local-mode-map (kbd "i") 'god-local-mode) (global-set-key (kbd "C-x C-k") 'kill-this-buffer) (global-set-key (kbd "C-x C-1") 'delete-other-windows) (global-set-key (kbd "C-x C-2") 'split-window-below) (global-set-key (kbd "C-x C-3") 'split-window-right) (global-set-key (kbd "C-x C-0") 'delete-window))) ;; TODO: only do this on the Thinkpad (global-set-key (kbd "") 'god-mode-all) #+END_SRC * Email :PROPERTIES: :header-args: :noweb-ref email :noweb yes :END: TODO: this is a big blob of email configuration. Document this properly! #+BEGIN_SRC elisp (require 'mu4e) (require 'org-mu4e) (require 'mu4e-contrib) (setq shr-color-visible-luminance-min 30) (setq mu4e-get-mail-command "offlineimap" mu4e-compose-signature-auto-include nil mu4e-compose-dont-reply-to-self t mu4e-update-interval 60 mu4e-headers-include-related t) (setq mu4e-use-fancy-chars t) (setq mu4e-headers-seen-mark '("" . "")) (setq mu4e-headers-unread-mark '("u" . "✉")) ;; Don't update list of emails automatically. I update the list ;; manually with "g". (setq mu4e-headers-auto-update nil) (setq mu4e-view-show-addresses t) (setq mu4e-hide-index-messages t) (setq mu4e-html2text-command 'mu4e-shr2text) (setq mu4e-view-show-images t) ;; use imagemagick, if available (when (fboundp 'imagemagick-register-types) (imagemagick-register-types)) (setq mu4e-attachment-dir "~/Downloads" mu4e-compose-signature-auto-include t) ;; pretty quotes! (add-hook 'message-mode-hook (lambda () (require 'typo) (typo-mode 1))) (defun my/set-mu4e-bookmarks (maildir) (let ((guix "(list:guix-devel.gnu.org OR list:bug-guix.gnu.org OR list:help-guix.gnu.org OR list:guix-sysadmin.gnu.org OR list:guix-security.gnu.org OR list:guix-patches.gnu.org OR list:guix-commits.gnu.org)") (guile "(list:guile-user.gnu.org OR list:guile-devel.gnu.org OR list:bug-guile.gnu.org)") (emacs "(list:emacs-devel.gnu.org)") (waldow "(list:waldow.googlegroups.com)") (fsfe "(subject:\"Willkommen in der FSFE\" list:coordinators.lists.fsfe.org OR list:berlin.lists.fsfe.org OR list:newsletter-en@fsfeurope.org OR list:newsletter-de@fsfeurope.org)") (unread "(flag:unread AND NOT flag:trashed)") (me "(body:rekado OR body:Ricardo)")) (setq mu4e-bookmarks (list ;; TODO: don't match my own signature (list (concat me " " unread) "Mentioning me (unread)" ?R) (list (concat "maildir:\"/" maildir "/INBOX\"") "Inbox" ?i) (list "date:today..now" "Today's messages" ?t) (list "date:today..now" "Last 7 days" ?w) (list (concat "maildir:\"/" maildir "/INBOX\"" " " unread) "Unread messages" ?u) (list (concat guix " " unread) "Guix" ?1) (list (concat guile " " unread) "Guile" ?2) (list (concat fsfe " " unread) "FSFE" ?3) (list waldow "Waldow" ?4) (list (concat emacs " " unread) "Emacs" ?5) (list (concat "maildir:\"/private/mailinglists\"" " " unread " NOT " guix " NOT " guile " NOT " fsfe " NOT " waldow " NOT " emacs) "Unread list messages" ?m) (list (concat "maildir:\"/" maildir "/INBOX\" flag:flagged") "Flagged" ?f))))) ; set up email sending with msmtp (setq mail-user-agent 'mu4e-user-agent) (setq mail-specify-envelope-from t) (setq mail-envelope-from 'header) (setq message-kill-buffer-on-exit t) (setq message-sendmail-envelope-from 'header) (setq message-send-mail-function 'message-send-mail-with-sendmail) ;;use msmtp instead of sendmail (setq sendmail-program "~/.guix-profile/bin/msmtp") ;; Crypto (setq mml-secure-openpgp-encrypt-to-self t) (setq mml-secure-openpgp-sign-with-sender t) (add-hook 'mu4e-compose-mode-hook (defun my/maybe-reply-encrypted () "Encrypt automatically if parent message was also encrypted." (let ((msg mu4e-compose-parent-message)) (when (and msg (or (member 'encrypted (mu4e-message-field msg :flags)) (string-match "-----BEGIN PGP MESSAGE-----$" (mu4e-message-field msg :body-txt)))) (mml-secure-message-sign-encrypt))))) (add-hook 'mu4e-compose-mode-hook (defun my/mu4e-add-headers () "Add some personal headers." (save-excursion (message-add-header "X-URL: https://elephly.net\n") (message-add-header "X-PGP-Key: https://elephly.net/rekado.pubkey\n") (message-add-header "X-PGP-Fingerprint: BCA6 89B6 3655 3801 C3C6 2150 197A 5888 235F ACAC")))) ;; Don't open a new window to show the results of signature validation (setq epa-popup-info-window nil) (require 'mu4e-actions) (add-to-list 'mu4e-view-actions '("git am" . mu4e-action-git-apply-mbox)) (add-to-list 'mu4e-headers-actions '("git am" . mu4e-action-git-apply-mbox)) ;; Don't ask for a 'context' upon opening mu4e (setq mu4e-context-policy 'pick-first) ;; Don't ask to quit (setq mu4e-confirm-quit nil) #+END_SRC I read and write email in different contexts. This is my context for private email: #+BEGIN_SRC elisp (setq my/mu4e-context-private (make-mu4e-context :name "Private" :enter-func (lambda () (mu4e-message "Switch to the Private context") (my/set-mu4e-bookmarks "private")) :match-func (lambda (msg) (when msg (or (mu4e-message-contact-field-matches msg :to (rot13 "erxnqb@ryrcuyl.arg")) (mu4e-message-contact-field-matches msg :from (rot13 "erxnqb@ryrcuyl.arg")) ;; Additional check if this is a mailing list email. (and (mu4e-message-field msg :mailing-list) (zerop (call-process "grep" nil nil nil "-E" "^Delivered-To: .*elephly.net" (mu4e-message-field msg :path))))))) :vars `((user-mail-address . ,(rot13 "erxnqb@ryrcuyl.arg")) (user-full-name . ,(rot13 "Evpneqb Jhezhf")) (mu4e-sent-folder . "/private/Sent") (mu4e-trash-folder . "/private/Trash") (mu4e-refile-folder . "/private/Archives") (mu4e-drafts-folder . "/private/Drafts") (mu4e-compose-signature . "Ricardo")))) #+END_SRC And here’s the context for work email: #+BEGIN_SRC elisp (setq my/mu4e-context-work (make-mu4e-context :name "Work" :enter-func (lambda () (mu4e-message "Switch to the Work context") (my/set-mu4e-bookmarks "mdc-personal")) :match-func (lambda (msg) (when msg (or (mu4e-message-contact-field-matches msg :to (rot13 "evpneqb.jhezhf@zqp-oreyva.qr")) (mu4e-message-contact-field-matches msg :from (rot13 "evpneqb.jhezhf@zqp-oreyva.qr")) ;; Additional check if this is a mailing list email. (and (mu4e-message-field msg :mailing-list) (zerop (call-process "grep" nil nil nil "-E" "^Received: from .*mdc-berlin.de" (mu4e-message-field msg :path))))))) :vars `((user-mail-address . ,(rot13 "evpneqb.jhezhf@zqp-oreyva.qr")) (user-full-name . ,(rot13 "Evpneqb Jhezhf")) (mu4e-sent-folder . "/mdc-personal/Sent Items") (mu4e-trash-folder . "/mdc-personal/Deleted Items") (mu4e-refile-folder . "/mdc-personal/Archive") (mu4e-drafts-folder . "/mdc-personal/Drafts") (mu4e-compose-signature . ,(rot13 (concat "\ Evpneqb Jhezhf Flfgrz nqzvavfgengbe OVZFO - Fpvragvsvp Ovbvasbezngvpf Cyngsbez Znk Qryoehrpx Pragre sbe Zbyrphyne Zrqvpvar Eboreg-Eöffyr-Fge. 10, 13125, Oreyva, Treznal Ohvyqvat 89, Ebbz 1.08 rznvy: evpneqb.jhezhf@zqp-oreyva.qr gry: +49 30 9406 " (number-to-string (+ (* 1 2 2 3 4 5 6) (expt 2 8) 100)))))))) #+END_SRC When I’m using the workstation in the office, all I want is the work context. #+BEGIN_SRC elisp (setq mu4e-contexts (if (string= (system-name) (rot13 "ovzfo-flf02.zqp-oreyva.arg")) (list my/mu4e-context-work) (list my/mu4e-context-private my/mu4e-context-work))) #+END_SRC Load all of this email configuration code only when I start =mu4e=. #+BEGIN_SRC elisp :noweb-ref email-lazy (eval-when-compile (require 'mu4e)) (global-set-key (kbd "") 'mu4e) (with-eval-after-load "mu4e" <> ) #+END_SRC * TODO More stuff :PROPERTIES: :header-args: :noweb-ref initial-after-packages :END: This is even more stuff to be done after initialising packages. I still need to process all of this and clean it up. #+BEGIN_SRC elisp (require 'projectile) (projectile-mode) #+END_SRC * TODO And even more :PROPERTIES: :header-args: :noweb-ref old-init :END: #+BEGIN_SRC elisp (setq backup-directory-alist `(;; Do not backup or auto-save remote files to prevent delays. (,tramp-file-name-regexp . nil) ;; Write backup files to a dedicated directory. ("." . ,(expand-file-name (concat user-emacs-directory "backups"))))) ;; Make backups of files, even when they're in version control (setq vc-make-backup-files t) (add-to-list 'auto-mode-alist '("\\.markdown\\'" . markdown-mode)) (add-to-list 'auto-mode-alist '("\\.md\\'" . markdown-mode)) (add-to-list 'auto-mode-alist '("\\.js$" . js2-mode)) (setq scss-compile-at-save nil) ;; (setq whitespace-global-modes '(not erc-mode)) ;; (global-whitespace-mode 1) ;; (set-face-attribute 'whitespace-space nil :background nil :foreground "gray20") ;; (set-face-attribute 'whitespace-newline nil :background nil :foreground "gray20") ;; (setq whitespace-style ;; '(face spaces tabs newline space-mark tab-mark newline-mark)) ;; (setq whitespace-display-mappings ;; ;; all numbers are Unicode codepoint in decimal. try ;; ;; (insert-char 182 ) to see it ;; '( ;; (space-mark 32 [183] [46]) ; 32 SPACE, 183 MIDDLE DOT 「·」, ;; ; 46 FULL STOP 「.」 ;; (newline-mark 10 [182 10]) ; 10 LINE FEED ;; (tab-mark 9 [9655 9] [92 9]) ; 9 TAB, 9655 WHITE ;; ; RIGHT-POINTING TRIANGLE 「▷」 ;; )) (desktop-save-mode t) (setq desktop-restore-eager 5) ; restore buffers lazily (setq desktop-lazy-idle-delay 3) ; no hurry ;; ediff settings (setq ediff-diff-options "-w") ;; fewer backslashes in regexp builder (require 're-builder) (setq reb-re-syntax 'string) ;; remove prompt on killing process buffer (setq kill-buffer-query-functions (remq 'process-kill-buffer-query-function kill-buffer-query-functions)) ;; enable features that are disabled by default (put 'narrow-to-region 'disabled nil) (put 'erase-buffer 'disabled nil) (put 'narrow-to-page 'disabled nil) (require 'fill-column-indicator) (setq fci-rule-use-dashes t) (setq fci-rule-color "#cccccc") (setq fci-dash-pattern 0.3) (add-hook 'prog-mode-hook 'fci-mode) ;; This is a workaround to display the company-mode completion popup ;; when fci-mode is enabled. It was taken from here: ;; https://github.com/company-mode/company-mode/issues/180#issuecomment-55047120 (defvar-local company-fci-mode-on-p nil) (defun company-turn-off-fci (&rest ignore) (when (boundp 'fci-mode) (setq company-fci-mode-on-p fci-mode) (when fci-mode (fci-mode -1)))) (defun company-maybe-turn-on-fci (&rest ignore) (when company-fci-mode-on-p (fci-mode 1))) (add-hook 'company-completion-started-hook 'company-turn-off-fci) (add-hook 'company-completion-finished-hook 'company-maybe-turn-on-fci) (add-hook 'company-completion-cancelled-hook 'company-maybe-turn-on-fci) ;; expand region (global-set-key (kbd "M-@") 'er/expand-region) ;; Swap C-t and C-x, so it's easier to type on Dvorak layout ;; `keyboard-translate` does not work when attaching an emacsclient to ;; a running emacs in daemon mode, so instead we define the key in the ;; key-translation-map. ;; http://lists.gnu.org/archive/html/help-gnu-emacs/2009-10/msg00505.html (define-key key-translation-map [?\C-x] [?\C-t]) (define-key key-translation-map [?\C-t] [?\C-x]) ;; Use narrow tab width (set-default 'tab-width 4) (setq tab-width 4) (setq gnus-select-method '(nntp "news.gmane.org")) ;; disable away timestamp in ERC (setq erc-away-timestamp-format nil) (setq erc-timestamp-format nil) ;; don’t switch to a newly created IRC buffer (setq erc-join-buffer 'bury) ;; Revert stale document graphics buffers automatically when the files ;; have changed. (add-hook 'doc-view-mode-hook 'auto-revert-mode) (page-break-lines-mode 1) (global-set-key (kbd "") 'backward-page) (global-set-key (kbd "") 'forward-page) (add-to-list 'auto-mode-alist '("\\.html\\'" . sgml-mode)) (eval-when-compile (require 'tagedit)) (eval-after-load "sgml-mode" '(progn (require 'tagedit) (tagedit-add-paredit-like-keybindings) (tagedit-add-experimental-features) (add-hook 'html-mode-hook (lambda () (tagedit-mode 1))))) (delete-selection-mode 1) ; delete seleted text when typing ;; don't let the cursor go into minibuffer prompt, HT Xah Lee (setq minibuffer-prompt-properties '(read-only t point-entered minibuffer-avoid-prompt face minibuffer-prompt)) (savehist-mode) ;; PDF view mode (setq pdf-info-epdfinfo-program "~/.guix-profile/bin/epdfinfo") (pdf-tools-install) (add-to-list 'auto-mode-alist '("\\.pdf\\'" . pdf-view-mode)) ;; enable variable-pitch-mode in eww (add-hook 'eww-mode-hook (lambda () (variable-pitch-mode 1) (olivetti-mode 1))) ;; pretty quotes! (add-hook 'erc-mode-hook (lambda () (require 'typo) (typo-mode 1))) (add-hook 'org-mode-hook (lambda () (require 'typo) (typo-mode 1))) #+END_SRC Here are a few commands that I used pretty often: #+BEGIN_SRC elisp (defun my/new-empty-buffer () "Open a new empty buffer." (interactive) (let ((buf (generate-new-buffer "untitled"))) (switch-to-buffer buf) (funcall (and initial-major-mode)) (setq buffer-offer-save t))) (global-set-key (kbd "C-c n") 'my/new-empty-buffer) ;; http://whattheemacsd.com/key-bindings.el-01.html#disqus_thread (require 'linum) (defun my/goto-line-with-feedback () "Show line numbers temporarily, while prompting for the line number input" (interactive) (let ((line-numbers-off-p (not linum-mode))) (unwind-protect (progn (when line-numbers-off-p (linum-mode 1)) (call-interactively 'goto-line)) (when line-numbers-off-p (linum-mode -1))))) (global-set-key [remap goto-line] 'my/goto-line-with-feedback) ;; kill current buffer (global-set-key (kbd "C-x C-k") (lambda () (interactive) (kill-buffer (current-buffer)))) ;; delete up to non-whitespace character (global-set-key (kbd "C-c d") (lambda () (interactive) (cycle-spacing -1 t nil))) (defun ssh-dtach (host) "Open SSH connection to HOST and start dtach session." (interactive "sHost: ") (let ((explicit-shell-file-name "dtach") (explicit-dtach-args '("-A" "/tmp/emacs.dtach" "-z" "/bin/bash" "--noediting" "-login")) (default-directory (format "/ssh:%s:" host))) (shell (format "*ssh %s*" host)))) ;; http://blog.vivekhaldar.com/post/4809065853/dotemacs-extract-interactively-change-font-size (defun my/zoom-in () "Increase font size by 10 points" (interactive) (set-face-attribute 'default nil :height (+ (face-attribute 'default :height) 10))) (defun my/zoom-out () "Decrease font size by 10 points" (interactive) (set-face-attribute 'default nil :height (- (face-attribute 'default :height) 10))) ;; change font size, interactively (global-set-key (kbd "C->") 'my/zoom-in) (global-set-key (kbd "C-<") 'my/zoom-out) ;; easier way to jump to other window (global-set-key (kbd "M-o") 'other-window) (defun my/smart-open-line () "Insert an empty line after the current line. Position the cursor at its beginning, according to the current mode." (interactive) (move-end-of-line nil) (newline-and-indent)) (global-set-key [(shift return)] 'my/smart-open-line) ;; http://stackoverflow.com/a/18814469/519736 (defun my/copy-buffer-file-name (choice) "Copy the buffer-file-name to the kill-ring" (interactive "cCopy Buffer Name (F) Full, (D) Directory, (N) Name") (let ((new-kill-string) (name (if (eq major-mode 'dired-mode) (dired-get-filename) (or (buffer-file-name) "")))) (cond ((eq choice ?f) (setq new-kill-string name)) ((eq choice ?d) (setq new-kill-string (file-name-directory name))) ((eq choice ?n) (setq new-kill-string (file-name-nondirectory name))) (t (message "Quit"))) (when new-kill-string (message "%s copied" new-kill-string) (kill-new new-kill-string)))) #+END_SRC * Putting it all together Having defined named code blocks in the sections above we can finally put them all together to build the init file #+BEGIN_SRC elisp :noweb yes :tangle yes <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> (when (not (string= (system-name) (rot13 "ovzfo-flf02.zqp-oreyva.arg"))) <> ) <> <> <> (global-set-key (kbd "C-x RET 1") (lambda () (interactive) (insert "¯\\_(ツ)_/¯"))) #+END_SRC * Variables :noexport: # Local Variables: # eval: (add-hook 'org-babel-post-tangle-hook (lambda nil (byte-compile-file "~/.emacs.d/init.el"))) # End: