Giter Site home page Giter Site logo

dot.emacs's Introduction

My Emacs setup

https://github.com/fimblo/dot.emacs/actions/workflows/syntax-check.yml/badge.svg

Introduction

This document is my so-called dot-emacs file. Actually, it’s not - not really. When my emacs instance starts up, it looks for and finds a file called ~/.emacs.d/init.el, which it loads and applies. It could look something like this:

;; Minimal config here. The rest will be in setup.org
(package-initialize)     ; This needs to be early in config
(require 'org)           ; Load org so we can use babel

(org-babel-load-file     ; Load my setup file
 (expand-file-name "~/.emacs.d/setup.org"))
;; EOF

In reality it’s slightly more convoluted, since we can’t assume that setup.org resides in ~/.emacs.d. But anyway.

It starts by loading the emacs lisp packages, and activating them. After that, it loads org-mode and using babel, expands the document you’re reading now.

This makes it possible for me to write my emacs setup as an org file, interleaving comments like this sentence with actual code; in this case the emacs-lisp code used to set emacs up. Quite nifty, actually.

But I get ahead of myself. My emacs setup uses 5 files, all stored in the directory ~/.emacs.d:

init.el

The entrypoint to my setup. This file in turn loads setup.org.

setup.org

This document. Contains the vast majority of my emacs configuration.

setup.el

When starting up, all the runnable code in setup.org is extracted and placed in setup.el. This is what is actually loaded behind the scenes. I only look at this file when debugging.

custom.el

Sometimes I’m lazy and I use emacs itself to customise its behaviour. These kinds of customisations end up in this file where its separated from my own code. I set it up in section Auto-customization.

EMACS_PERSONAL_DATA

This environment variable points at the file where I put all personal info. The variables are declared in section Declare personal variables.

Initial setup

Set up basic environment

Before we can go about fixing the fun parts of emacs, we’ll need to set up some basics.

(setq message-log-max
      (- (expt 2 15) 1))      ; Make message log big

Set some constants on the environment this instance of emacs is running in.

;; We also have a sys/emacs-root defined in init.el.
;; This points at the directory this file is in.

(defconst sys/hostname
  (getenv "HOSTNAME")          "This machine's hostname")
(defconst sys/home
  (getenv "HOME")              "Home directory")
(defconst sys/windowsp
  (eq system-type 'windows-nt) "Is this a Windows system?")
(defconst sys/linuxp
  (eq system-type 'gnu/linux)  "Is this a GNU/Linux system?")
(defconst sys/macp
  (eq system-type 'darwin)     "Is this a Mac system?")
(defconst sys/cygwinp
  (eq system-type 'cygwin)     "Is this a Cygwin system?")
(defconst sys/linux-x-p
  (and (display-graphic-p) sys/linuxp) "Is this a GNU/Linux system running X?")
(defconst sys/mac-x-p
  (and (display-graphic-p) sys/macp)    "Is this a Mac system running X?")

Set some variables which I used here and there. These aren’t constants since I might want to change them during runtime.

(setq default-directory (concat sys/home "/"))

;; Sice I'm running with org-babel, the variable 'this-file' does not
;; contain the file filename.org; the file gets tangled into
;; filename.el
(defvar this-file (or load-file-name (buffer-file-name)))
(defvar this-file-org (replace-regexp-in-string ".el" ".org" this-file))

(setenv "PATH" (concat (getenv "PATH") ":/usr/local/bin"))
(setq exec-path (append exec-path '("/usr/local/bin")))

Don’t do automatic file backups in these directories

Note that the specialdir var is intended to be populated with path prefixes - so if you add / then emacs won’t make backups anywhere…

Note to self - should put the specialdirs into custom.el at some point.

(defun me/my-backup-enable-predicate (name)
  (let (found)
    (dolist (specialdir '("~/gdrive/"
                          "/some/other/directory/") found)
      (if (string-prefix-p specialdir name)
          (setq found t)))
    (if found
        nil
      (normal-backup-enable-predicate name))))

(setq backup-enable-predicate #'me/my-backup-enable-predicate)

Make really sure that we’re using utf-8.

I’m so glad I don’t have to fool around with all those old character sets anymore - especially the Japanese ones…

(setq locale-coding-system      'utf-8)
(set-terminal-coding-system     'utf-8)
(set-keyboard-coding-system     'utf-8)
(set-selection-coding-system    'utf-8)
(set-default-coding-systems     'utf-8)
(prefer-coding-system           'utf-8)
(set-language-environment       "UTF-8")

Declare personal variables

Here are the variables which are private to me, and are assigned in the file pointed at by the environment variable EMACS_PERSONAL_DATA. No, this file is not in my vc.

 (defconst me/emacs/personal-data
   (getenv "EMACS_PERSONAL_DATA")     "Personal info goes here")

 (defvar me/fullname              nil "My full name.")
 (defvar me/nick                  nil "My nickname.")
 (defvar me/mail/credentials      nil "Where I store my credentials.")
 (defvar me/mail/mydomain         nil "My mail domainname.")
 (defvar me/mail/smtp-server      nil "Hostname.domainname of smtp server.")
 (defvar me/mail/signature        nil "My email signature.")
 (defvar me/erc/server            nil "Irc server hostname")
 (defvar me/erc/port              nil "Irc server port")
 (defvar me/erc/nick              nil "My nick")
 (defvar me/erc/pass              nil "My password")
 (defvar me/erc/autojoin-alist    nil "Association list of channels to join.
					For example:
					((\"chat.freenode.net\" \"#emacs\" \"#cooking\")
					 (\"another.server.org\" \"#foo\" \"#bar\" \"#baz\"))
					")
 (defvar me/erc/pass-query-string nil "How should ERC ask for the password?
					Useful if you have multiple servers to connect to.")

 ;; If me/emacs/personal-data has a value, check if it points at a file. If
 ;; it exists, then load it.
 (when me/emacs/personal-data
     (when (file-exists-p me/emacs/personal-data)
	(load me/emacs/personal-data)
	)
     )

Package.el configuration

This needs to be in place before any configurations of installed packages.

Most of this was copied from @jeekl’s emacs setup. Thanks @jeekl!

;; mkdir these and add them to load path
(dolist (path `(  ,(concat sys/emacs-root "elpa/")     ;; emacs-lisp package archive
                  ,(concat sys/emacs-root "el-get/")   ;; packages from el-get
                  ,(concat sys/emacs-root "vendor/"))) ;; stuff I downloaded myself
  (make-directory path t)
  (let ((default-directory path))
    (normal-top-level-add-subdirs-to-load-path)))

I made a lisp-learning today: In the code above, I need to use a backtick and comma in order to evaluate the functions in the list. It used to look like this:

(dolist (path '(  "~/.emacs.d/elpa/"
        ...

As you can see, it was a list (denoted by the single-quote before the left-paren) with strings in it. Earlier today, I replaced the full path to elpa/ with a function concat which concatenated the path to the configuration directory with the string elpa/, like so:

(dolist (path `(  ,(concat sys/emacs-root "elpa/")
        ...

This would help me make the configuration agnostic to where in the filesystem it was installed - a necessary prerequisite to having a github action syntax check, where it will be installed in another place than ~/.emacs.d.

The backtick is a lispism to make the interpreter selectively evaluate any function prefixed with a comma symbol. Cool!

Oh, and the practice is called Quasi-quotation.

(require 'package)

;; add these sources
(eval-after-load "package"
  '(progn
     (add-to-list 'package-archives '("org"   . "http://orgmode.org/elpa/"))
     (add-to-list 'package-archives '("melpa" . "http://stable.melpa.org/packages/"))
     ))

(eval-after-load "url-http"
  '(setq url-http-attempt-keepalives nil))  ; A package.el bug. Apparently.

Packages to install

If this is a clean install of emacs, then it will update the package list. This updating it from the various sources takes time, we skip it otherwise.

Note to self: If this isn’t a clean install but instead a major upgrade (like I just did from emacs26 to emacs28) you might need to call package-refresh-contents manually once. Or do a M-x package-list-packages then push U to update.

(if (not package-archive-contents)
    (package-refresh-contents))

Download these packages if they aren’t already downloaded.

(defvar elpa-packages
  '(

    ;; Emacs theme
    gruvbox-theme


    ;; Other packages
                                        ; muttrc-mode
                                        ; twittering-mode
    adoc-mode
    all-the-icons
    all-the-icons-dired
    all-the-icons-ibuffer
    all-the-icons-ivy
                                        ; apache-mode
                                        ; auctex
    blacken
    column-marker
    company
    counsel
    counsel-tramp
    csv-mode
                                        ; diff-hl ;; Much too slow
    dired-subtree
                                        ; docker
                                        ; docker-compose-mode
    dockerfile-mode
                                        ; docker-tramp
    dumb-jump
    editorconfig
    flycheck
    graphviz-dot-mode
    htmlize
    ibuffer-projectile
    iedit                               ; for renaming symbols
    json-mode
    js2-mode
    load-theme-buffer-local
    magit
    mpg123
    markdown-mode
    olivetti
    org-bullets
    perl-doc
    projectile
    highlight-parentheses
    rainbow-mode
    spaceline
    spaceline-all-the-icons
    ssh-config-mode
    swiper
    tide
    treemacs                       ; A tree style file explorer package
    treemacs-all-the-icons         ; all-the-icons integration for treemacs
    treemacs-magit                 ; Magit integration for treemacs
    treemacs-projectile            ; Projectile integration for treemacs
    typescript-mode
    which-key
    web-mode
    yaml-mode

    ;; Modes for editing chrome textboxes in emacs.
                                        ; atomic-chrome
                                        ; gmail-message-mode
                                        ; edit-server


    )
  "These packages are installed if necessary."
  )

(dolist (pkg elpa-packages)
  (when (and (not (package-installed-p pkg))
             (assoc pkg package-archive-contents))
    (package-install pkg)))

(defun me/package-list-unaccounted-packages ()
  "Like `package-list-packages', but shows only the packages that
  are installed and are not in `elpa-packages'.  Useful for
  cleaning out unwanted packages."
  (interactive)
  (package-show-package-list
   (remove-if-not (lambda (x) (and (not (memq x elpa-packages))
                                   (not (package-built-in-p x))
                                   (package-installed-p x)))
                  (mapcar 'car package-archive-contents))))

Load code in vendor/

Sometimes, the emacs modules aren’t available on melpa, but I have the source file. When this happens, I place it in my vendor/ folder. All .el files are loaded on startup.

(defun me/load-directory (dir)
  (let ((load-it (lambda (f)
                   (load-file (concat (file-name-as-directory dir) f)))
                 ))
    (mapc load-it (directory-files dir nil "\\.el$"))))
(me/load-directory (concat sys/emacs-root "vendor/"))

Auto-customization

Move all customization stuff to another file.

(setq custom-file (concat sys/emacs-root "custom.el"))
(load custom-file 'noerror)

Emacs server

The emacs server is useful if you use emacs for many things, and you want each session to share buffers and state. Startup time is minimal too.

(require 'server)
(load "server")
(unless (server-running-p) (server-start))

UI

Setting up the User interface so that it works the way I like it.

Basic look and feel

Configuration basics.

(setq initial-major-mode 'org-mode)     ; org-mode for the initial
                                        ; *scratch* window
(setq default-major-mode 'org-mode)     ; default mode is org-mode

(setq fci-rule-column 80)               ; fill column
(setq inhibit-startup-message t)        ; no startup message
(setq initial-scratch-message nil)      ; no *scratch* message
(setq line-number-mode t)               ; show line number
(setq column-number-mode t)             ; show current column
(global-font-lock-mode 1)               ; syntax highlightning ON
(setq transient-mark-mode t)            ; turn on transient-mark-mode
(setq indicate-buffer-boundaries t)     ; visually show end of buffer
(setq-default indicate-empty-lines t)   ; be even more obvious about it
(setq remove-help-window t)             ; kill completion-window when
                                        ; leaving minibuffer
(setq insert-default-directory t)       ; get default dir in commands
(setq enable-local-variables t)         ; enables local variables
(setq compilation-window-height 10)     ; height of compilation window.
(setq-default cursor-type 'bar)         ; make cursor thin
(tool-bar-mode -1)
(menu-bar-mode -1)
(context-menu-mode 1)                   ; right-click to get menu
(fringe-mode '(12 . 12))                 ; set default fringe

(if (boundp 'scroll-bar-mode) (scroll-bar-mode -1))


;; Look and feel for all programming modes
(require 'linum)                        ; necessary in newer emacsen
(add-hook 'prog-mode-hook
          (lambda ()
            (linum-mode 1)              ; show line number in margin
            (hl-line-mode 1)            ; highlight the current line
            (show-paren-mode t)         ; show matching parens
            )
          )

Changes in default behaviour upon user action

The first section above was how emacs presents things to me. This section is how it reacts to some of my commands.

(setq case-fold-search t)           ; ignore case in searches
(setq compilation-ask-about-save 0) ; dont ask to save when compiling
(setq apropos-do-all t)             ; show all funcs/vars in help
(put 'downcase-region 'disabled nil); allow downcase-region commands
(put 'upcase-region 'disabled nil)  ; allow downcase-region commands

(setq next-line-add-newlines t)     ; C-n at eob opens new lines.
(setq scroll-step 1)                ; Moving cursor down at bottom
                                    ; scrolls only a single line
(setq sentence-end-double-space nil); I don't add two spaces after a
                                    ; period, this makes M-a and M-e
                                    ; work as intended.

Generally, I don’t like programs asking me if I really want to do something I just told it to do. And if it must, I want that interaction to be as non-intrusive as possible.

(defun me/dummy-ring-bell-function () nil)    ; replace beep with visible bell
(setq ring-bell-function `me/dummy-ring-bell-function)

(fset 'yes-or-no-p 'y-or-n-p)                 ; y or n instead of yes or no
(setq confirm-nonexistent-file-or-buffer nil) ; just open new buffers
(setq kill-buffer-query-functions             ; dont ask to kill live buffers
      (remq 'process-kill-buffer-query-function
            kill-buffer-query-functions))
(put 'eval-expression 'disabled nil)          ; no confirm on eval-expression

Link X’s primary selection and clipboard to interplay with emacs.

(if sys/linux-x-p
    (progn
      ;; after copy Ctrl+c in Linux X11, you can paste by `yank' in emacs
      (setq select-enable-clipboard t)
      ;; after mouse selection in X11, you can paste by `yank' in emacs
      (setq select-enable-primary t)
      )
  )

Mouse behaviour

Get the mouse to work in emacs instances running in a terminal, and other mouse configuration.

(xterm-mouse-mode t)                  ; Support mouse in xterms
(setq mouse-wheel-mode t)             ; support mouse wheel
(setq mouse-wheel-follow-mouse t)     ; scrolls mouse pointer position, not pointer

Time and date

Get emacs to display time and date.

(display-time)
(setq display-time-day-and-date t)
(setq display-time-24hr-format t)

Set up time and date the way I like it.

;; I want weeks to start on Mondays rather than Sundays in
;; =M-x calendar=.
(setq calendar-week-start-day 1)

(setq calendar-date-style 'iso)     ;; yyyy-mm-dd
(setq calendar-date-display-form    ;; 13 Jun 2001
      '((if dayname
            (concat dayname ", "))
        day " " monthname " " year))

(setq calendar-time-display-form
        '(24-hours ":" minutes))

Indentation

Generally, get emacs to indent in multiples of 2 or 4 spaces. Also - avoid inserting tabs.

(setq standard-indent 2)
(setq-default indent-tabs-mode nil)
(setq-default tab-width 4)
(setq tab-width 4)
(setq-default tab-stop-list
              (mapcar #'(lambda (x) (* x 4))
                      (cdr (reverse
                            (let (value)
                              (dotimes (number 32 value)
                                (setq value (cons number value))))))))

(setq perl-continued-brace-offset -2)
(setq perl-continued-statement-offset 2)
(setq perl-indent-level 2)
(setq perl-label-offset -1)
(setq sh-basic-offset 2)
(setq sh-indentation 2)

Colours, fonts and stuff

Apparently loading a theme using load-theme overlays the new theme onto whatever was there before. This might be useful at times, but I find it easier when I get exactly the theme I select.

Anyway, the advice function below makes load-theme behave the way I like.

(defadvice load-theme (before clear-previous-themes activate)
  "Clear existing theme settings instead of layering them"
  (mapc #'disable-theme custom-enabled-themes))

(load-theme 'tsdh-dark)

The default look and feel of the theme I use (tsdh-dark) has a few details which I think doesn’t look quite right. Fixing it here.

;; Make background color a bit lighter..
(set-face-attribute 'default nil :background "gray21")

;; .. make the region a light gray
(set-face-attribute 'region nil :extend t :background "gray28")

;; and the highlight face can also be black
(set-face-attribute 'highlight nil :background "black")

;; The highlight color for isearch
(set-face-attribute 'isearch nil
                    :background "saddle brown"
                    :foreground "white smoke")
(set-face-attribute 'lazy-highlight nil :background "LightSalmon4")

;; comments in italics
(set-face-attribute font-lock-comment-face nil :slant 'italic)

;; Fringe in the same color as default.
(set-face-attribute 'fringe nil
                    :foreground "white"
                    :background "black")

Make the highlighted line a tad darker than the default background.

(eval-after-load "hl-line"
  '(set-face-attribute 'hl-line nil :background "grey10"))

For the longest time, I’ve for some reason enjoyed writing much more in traditional word processors like Google Docs, Openoffice, MSWord even if I’ve been an emacs user for decades. I never really understood why until I realised that it had to do with the UI. By changing the font into something with serifs, and writing in the “middle” of the buffer window, I discovered that writing became more enjoyable for me in an emacs environment.

The code block below toggles between prose and code mode.

By the way - to use this without modification you’ll need the font Noto-serif.

(defvar me/write-state "nowrite")
(defvar me/face-cookie nil)
(defun me/write-toggle ()
  "Toggles write-state of current buffer.

   Write-state defaults to nil, but when activated, does the following:
   - Changes the cursor to a short horizontal line
   - Changes the font to Noto Serif
   - Removes hl-line-mode
   - Activates Olivetti-mode

   Toggling again reverts the changes."

  (interactive)
  (if (string= me/write-state "write")
      (progn
        (message "write-state")
        (setq cursor-type 'bar)
        (variable-pitch-mode 0)
        (face-remap-remove-relative me/face-cookie) ; revert to old face
        (hl-line-mode 1)
        (olivetti-mode -1)
        (setq me/write-state "nowrite"))
    (progn
      (message "not write-state")
      (setq cursor-type '(hbar . 2))
      (variable-pitch-mode 1)
      (setq me/face-cookie              ; when changing face, save old
            (face-remap-add-relative   ; face in a cookie.
             'default
             '(:family "Noto Serif")))
      (hl-line-mode -1)
      (olivetti-mode 1)
      (setq me/write-state "write"))))

Display key bindings

which-key is a minor mode which shows the key bindings following any incomplete key command in a pop-up.

To page between options, hit C-h, then you can see options in the mini-buffer.

(require 'which-key)

(setq which-key-idle-delay 1.5)

;; With this option, I can hit ctrl-h to get which-key to activate
;; earlier than the delay timer
(setq which-key-show-early-on-C-h t)

(which-key-mode)
(which-key-setup-side-window-right-bottom)

Icons and modeline

This is just eye-candy for the most part. But icons in dired and a newer modeline just looks nice.

(require 'all-the-icons)
(add-hook 'dired-mode-hook 'all-the-icons-dired-mode)

;; make modeline a little nicer
(require 'spaceline-config)

                                        ; for a slightly fancier theme
(spaceline-all-the-icons-theme)

;; for a simpler but nice theme
;;(spaceline-emacs-theme)

;; add icons to ivy
(all-the-icons-ivy-setup)

The first time you run this on your system, you’ll need to run this command manually:

M-x all-the-icons-install-fonts

Highlight parens

In all programming modes, make parenthesis pairs stand out.

(setq highlight-parentheses-colors '("green"
                                     "gold"
                                     "red"
                                     "medium spring green"
                                     "cyan"
                                     "dark orange"
                                     "deep pink"))


(add-hook 'prog-mode-hook 'highlight-parentheses-mode)

(eval-after-load "highlight-parentheses-mode"
  '(set-face-attribute 'highlight-parentheses-highlight nil :weight bold))

External stuff

How emacs interacts with the world outside of it.

(setq tramp-default-method "ssh")
(setq browse-url-browser-function 'browse-url-chromium)

;; use correct browser on Mac
(if sys/macp
    (setq browse-url-browser-function 'browse-url-default-macosx-browser)
  )

;; Use correct 'ls' program regardless of systemm
(setq ls-lisp-use-insert-directory-program
      (if sys/macp  "gls"   ; mac
        (if sys/linuxp "ls" ; linux
          nil               ; win or other
          )))
;; Send the --dired flag to ls if it is supported
(setq dired-use-ls-dired "unspecified")

;; make scripts executable if they aren't already
(add-hook 'after-save-hook
          'executable-make-buffer-file-executable-if-script-p)

Map Suffixes with modes

Auto-set mode for these file suffixes.

(setq auto-mode-alist
      (append
       (list
        '("Dockerfile"            . dockerfile-mode      )
        '("\\.md"                 . markdown-mode        )
        '("\\.xml"                . xml-mode             )
        '("\\.pp"                 . puppet-mode          )
        '("\\.html"               . html-mode            )
        '("\\.xsl"                . xml-mode             )
        '("\\.cmd"                . cmd-mode             )
        '("\\.bat"                . cmd-mode             )
        '("\\.wiki"               . wikipedia-mode       )
        '("\\.org.txt"            . org-mode             )
        '("\\.txt"                . indented-text-mode   )
        '("\\.php"                . php-html-helper-mode )
        '("\\.fvwm2rc"            . shell-script-mode    )
        '("tmp/mutt-"             . message-mode         )
        '("\\.org"                . org-mode             )
        '("\\.asciidoc"           . adoc-mode            )
        '("\\.pm"                 . cperl-mode           )
        '("\\.pl"                 . cperl-mode           ))
       auto-mode-alist))

;; and ignore these suffixes when expanding
(setq completion-ignored-extensions
      '(".o" ".elc" ".class" "java~" ".ps" ".abs" ".mx" ".~jv" ))

The above works if you only look at the file suffix - but after loading, emacs will look at the first line of the file (if appropriate) and see if there is a hashbang specifying an interpreter. If that interpreter is in interpreter-mode-alist, it will use the mode specified there.

Since perl-mode is the default, Perl scripts starting with the line #!/bin/bin/perl will be associated with that despite the instructions in auto-mode-alist, so we need to add the mapping (perl . cperl-mode) in the interpreter-mode-alist.

(add-to-list 'interpreter-mode-alist '("perl" . cperl-mode))

Em and En-dash

I want to be able to insert the em-dash and the en-dash symbols in my writing.

(defun me/insert-em-dash ()
  "Insert an em-dash"
  (interactive)
  (insert ""))

(defun me/insert-en-dash ()
  "Insert an em-dash"
  (interactive)
  (insert ""))

Display lambda symbol

In python, emacs-lisp and org-mode, replace all instances of the string ‘lambda’ with the character λ.

Not only is this pretty, it saves some space on the screen :)

;; courtesy of stefan monnier on c.l.l
(defun sm-lambda-mode-hook ()
  (font-lock-add-keywords
   nil `(("\\<lambda\\>"
          (0 (progn (compose-region (match-beginning 0) (match-end 0)
                                    ,(make-char 'greek-iso8859-7 107))
                    nil))))))
(add-hook 'python-mode-hook 'sm-lambda-mode-hook)
(add-hook 'emacs-lisp-mode-hook 'sm-lambda-mode-hook)
(add-hook 'org-mode-hook 'sm-lambda-mode-hook)

Modes - Emacs behaviour

Atomic-chrome

A nifty tool which enables me to edit text areas in google chrome inside of an emacs frame. To get this to work, make sure you install the Atomic-chrome extension for Google chrome. Apparently there’s another extension you could use for firefox too.

;; (require 'atomic-chrome)
;; (atomic-chrome-start-server)
;; (setq atomic-chrome-buffer-open-style 'frame)
;; (setq atomic-chrome-extension-type-list '(atomic-chrome))
;;(setq atomic-chrome-default-major-mode 'markdown-mode)

Comint-mode

Comint-mode is essential for emacs to interact with another process - like the shell, or a database user interface (sqsh, isql, etc).

(ansi-color-for-comint-mode-on)         ; interpret and use ansi color codes 
                                        ; in shell output windows
(custom-set-variables
 '(comint-scroll-to-bottom-on-input t)  ; always insert at the bottom
 '(comint-scroll-to-bottom-on-output t) ; always add output at the bottom
 '(comint-scroll-show-maximum-output t) ; scroll to show max possible output
 '(comint-completion-autolist t)        ; show completion list when ambiguous
 '(comint-input-ignoredups t)           ; no duplicates in command history
 '(comint-completion-addsuffix t)       ; insert space/slash after file completion
 )

Company

This is my initial setup of company-mode, which lets me get a nice auto-completion thing going when writing code.

(defun me/setup-company-mode ()
  (company-mode)
  (setq company-idle-delay 1)                ; small delay when proposing suggestions
  (setq company-minimum-prefix-length 2)     ; two characters before suggesting
  (setq company-selection-wrap-around t)     ; make suggestion list a ring
  (setq company-tooltip-align-annotations t) ; aligns annotation to the right hand side

  (set-face-attribute 'company-scrollbar-bg nil :background "#000000")
  (set-face-attribute 'company-scrollbar-fg nil :background "#332222")

  (set-face-attribute 'company-tooltip nil
                    :foreground (face-foreground 'default)
                    :background "grey10")

  (set-face-attribute 'company-tooltip-common nil ; the text I entered, common to all tips
                      :inherit font-lock-constant-face
                      )
  (set-face-attribute 'company-tooltip-selection nil ; the part which is selected
                      :inherit font-lock-function-name-face
                      :foreground "light salmon")
  )


  (add-hook 'prog-mode-hook 'me/setup-company-mode)


  (require 'color)

CUA-mode

Cua-mode is normally used to make emacs act more like Windows (control-c to copy, etc). I use a subset so that I can use Cua-mode’s nice rectangle functions in addition to the normal ones.

Cua’s global-mark is really cool. This is what it says in the manual:

CUA mode also has a global mark feature which allows easy moving and copying of text between buffers. Use C-S-<SPC> to toggle the global mark on and off. When the global mark is on, all text that you kill or copy is automatically inserted at the global mark, and text you type is inserted at the global mark rather than at the current position.

Really useful for copying text from one buffer to another.

(cua-mode t)
(setq cua-enable-cua-keys nil)               ; go with cua, but without c-x/v/c et al
(setq shift-select-mode nil)                 ; do not select text when moving with shift.
(setq cua-delete-selection nil)              ; dont kill selections on keypress
(setq cua-enable-cursor-indications t)       ; customize cursor color

(setq cua-normal-cursor-color "white")
;; if Buffer is...
;;(setq cua-normal-cursor-color "#15FF00")     ; R/W, then cursor is green
;;(setq cua-read-only-cursor-color "purple1")  ; R/O, then cursor is purple
;;(setq cua-overwrite-cursor-color "red")      ; in Overwrite mode, cursor is red
;;(setq cua-global-mark-cursor-color "yellow") ; in Global mark mode, cursor is yellow

Dired-mode

Order to display files

In dired-mode, show directories first, then regular files. Dotfiles before non-dotfiles. Also, open dired-mode in the simple view. Toggle between simple and detailed view using (.

For more keybindings, see Dired keybindings.

(setq dired-listing-switches "-aFhlv --group-directories-first")
(add-hook 'dired-mode-hook 'dired-hide-details-mode)
(add-hook 'dired-mode-hook 'toggle-truncate-lines)

This function makes it easy to toggle between showing dotfiles and hiding them. I bound it in a section a bit further below to ..

(defvar me/dired-dotfiles-shown t "helper var for dired-dotfiles-toggle function." )
(defun me/dired-dotfiles-toggle ()
  "Toggle for displaying or hiding hidden files."

  (interactive)
  (setq me/dired-dotfiles-shown
        (if me/dired-dotfiles-shown
            (progn
              (dired-sort-other "-Fhlv --group-directories-first")
              nil)
          (progn
            (dired-sort-other "-aFhlv --group-directories-first")
            t)
          )
        )
  )

Date format in Dired

So many worthless date formats. ISO 8601 simplifies things.

(setq ls-lisp-format-time-list  '("%Y-%m-%d %H:%M" "%Y-%m-%d %H:%M")
      ls-lisp-use-localized-time-format t)

Wdired modifications

Enable changing permissions and creating directories using a / in the filename in writable dired-mode (wdired).

By the way, use C-x C-q to enter wdired, and C-c C-c to exit.

(setq wdired-allow-to-change-permissions t)
(setq wdired-create-parent-directories t)

Erc-mode

I don’t use IRC as much nowadays, but used this config when I did.

;; set a max-size to a irc buffer...
(setq erc-max-buffer-size 20000)

;; Make erc prompt show channelname.
(setq erc-prompt
      (lambda ()
        (if (and (boundp 'erc-default-recipients) (erc-default-target))
            (erc-propertize (concat (erc-default-target) ">")
                            'read-only t 'rear-nonsticky t 'front-nonsticky t)
          (erc-propertize (concat "ERC>")
                          'read-only t 'rear-nonsticky t 'front-nonsticky t))))

(defun me/start-irc ()
  "Connect to IRC."
  (interactive)
  (require 'erc)
  (erc-ssl
   :server me/erc/server
   :port me/erc/port
   :nick me/erc/nick
   :password me/erc/pass ; (read-passwd me/erc/pass-query-string)
   :full-name me/fullname)
  (setq erc-autojoin-channels-alist me/erc/autojoin-alist)
  )

Editorconfig

Enable support for .editorconfig files as specified by editorconfig.org.

(require 'editorconfig)
(add-hook 'prog-mode-hook 'editorconfig-mode)

Once enabled, emacs looks for a .editorconfig file (typically at the root of a project directory) and applies the rules for all buffers belonging to said project. Oh and afaict it’s buffer-local, so that’s nice.

root = true

[*]
charset = utf-8
end_of_line = lf
indent_size = 2
indent_style = space
insert_final_newline = true
max_line_length = 120
tab_width = 4
trim_trailing_whitespace = true

Flycheck

I’ve been using flymake for decades, but this one seems better. It’s smarter about where it shows the errors/warnings for one, and feels faster than flymake.

When troubleshooting flycheck, flycheck-verify-setup is a useful command.

Basic configuration

(add-hook 'after-init-hook #'global-flycheck-mode)

Flycheck for C/C++

(with-eval-after-load 'flycheck
  (setq-local flycheck-checker 'c/c++-gcc)
  (setq flycheck-c/c++-gcc-executable "gcc")

  (setq-default flycheck-disabled-checkers '(c/c++-clang))
  (add-hook 'c-mode-common-hook (lambda () (setq flycheck-checker 'c/c++-gcc)))

  (when sys/macp
    (setq flycheck-gcc-include-path '("/opt/homebrew/include")))
)

Flycheck for Perl

Dependencies: Perl::Critic:: (cpan)

(setq flycheck-perl-include-path '("." "lib") )

;; Perl critic levels of severity:
;; 1 - brutal
;; 2 - cruel
;; 3 - harsh
;; 4 - stern
;; 5 - gentle
(setq flycheck-perlcritic-severity 5)

Flycheck for emacs-lisp

Dependencies: None

;; Use load-path's path for flycheck
(setq-default flycheck-emacs-lisp-load-path 'inherit)

;; Get rid of annoying checkdoc warnings
(with-eval-after-load 'flycheck
  (setq-default flycheck-disabled-checkers '(emacs-lisp-checkdoc)))

Flycheck for bash

Dependencies: shellcheck (homebrew, apt, etc)

To get shellcheck to correctly check sourced bash files, make sure to add the #!/bin/bash shebang.

(add-hook 'sh-mode-hook 'flycheck-mode)

(with-eval-after-load 'flycheck
  (setq flycheck-checker 'sh-shellcheck))

Flyspell-mode

Spell-checker for emacs.

(setq ispell-program-name "aspell")
(setq flyspell-mark-duplications-flag nil)
(setq flyspell-consider-dash-as-word-delimiter-flag t)

Ibuffer-mode

A nice list-buffer replacement.

(require 'ibuffer)

(add-hook 'ibuffer-hook #'all-the-icons-ibuffer-mode)

(add-hook 'ibuffer-hook
          (lambda ()
            (ibuffer-projectile-set-filter-groups)
            (unless (eq ibuffer-sorting-mode 'alphabetic)
              (ibuffer-do-sort-by-alphabetic))))

Iedit

(require 'iedit)

Longlines-mode

(add-hook 'longlines-mode-hook
          (lambda()
            (auto-fill-mode -1)
            (longlines-show-hard-newlines)))

Projectile-mode

Let Emacs become aware of software projects. What this means in practice right now is that it looks up the directory hierarchy towards root, looking for a VC root of some kind, and sets the project there.

;; auto-load projectile upon startup
(add-hook 'after-init-hook #'projectile-mode)

;; set projectile keybindings on load of projectile
(add-hook 'projectile-mode-hook
          (lambda()
            (define-key projectile-mode-map (kbd "C-c p") 'projectile-command-map)
            ))

There’s a lot to learn here - and integrations to other modes to configure. For example:

  • ivy
  • magit
  • ibuffer

Swiper, Ivy and Counsel

For about six months, I tried Ido-mode and icomplete-mode, and somehow they often made me feel more frustrated than helped. I was introduced to Swiper and friends at an emacs-meetup, and will give it a try for a while.

For keybindings, see: Swiper/Ivy/Counsel keybindings

(add-hook 'ivy-mode-hook
          (lambda()
            (setq ivy-use-virtual-buffers t
                  enable-recursive-minibuffers t
                  ivy-count-format "%d/%d ")

            (set-face-attribute 'ivy-current-match nil
                                :background "black")
            (set-face-attribute 'ivy-minibuffer-match-face-1 nil
                                :background "black"
                                :foreground "#887733")
            (set-face-attribute 'ivy-minibuffer-match-face-2 nil
                                :background "grey10"
                                :foreground "#887733")
            (set-face-attribute 'ivy-minibuffer-match-face-3 nil
                                :background "grey18"
                                :foreground "#887733")
            (set-face-attribute 'ivy-minibuffer-match-highlight nil
                                :background "black"
                                :slant 'italic)
            )
          )
(ivy-mode 1)

Since Ivy, Counsel and Swiper always come together, their individual functionality always confuses me a bit. Here’s what they do:

Ivy
This is a completion framework. Given a list, it will present them and let you limit what is shown and ultimately select one item from the list.

Here’s an example from the docs. Evaluate the following code:

(ivy-read "My buffers: " (mapcar #'buffer-name (buffer-list)))
    
Counsel
A library which provides things to choose from. Like counsel-find-file or counsel-describe-function, which in turn uses Ivy to present the alternatives to me.
Swiper
A tool to search through the current buffer, replacing built-in functions like isearch-forward.

Treemacs

When mob-programming over video, it’s easier for others to follow where I am if I have a sidebar showing where I am like in many other editors. Giving treemacs a try.

There’s many, many more options to modify should I want to, and they can be found here.

(require 'treemacs)

(setq treemacs-hide-dot-git-directory     nil
      treemacs-recenter-after-file-follow t
      treemacs-recenter-after-tag-follow  t
      )


(treemacs-follow-mode t)
(treemacs-filewatch-mode t)
(treemacs-fringe-indicator-mode 'always)


(when treemacs-python-executable (treemacs-git-commit-diff-mode t))
(pcase (cons (not (null (executable-find "git")))
             (not (null treemacs-python-executable)))
  (`(t . t)
   (treemacs-git-mode 'deferred))
  (`(t . _)
   (treemacs-git-mode 'simple)))

(treemacs-hide-gitignored-files-mode nil)

It’s the little things. Quickly, I got really tired of how invoking treemacs-mode moved the cursor (and window focus) away from whatever I was doing and into the treemacs window. I understand that normally, the reason for opening treemacs was to be able to navigate the tree, and that having the cursor auto-moved there accomplishes this. But in my case, I want it open mostly for my colleagues - as a hint of where I am in the project space.

So this little function changes the default behaviour to keep the focus in place.

(defun me/toggle-treemacs ()
  "Invoking treemacs moves the window selection to the treemacs
window and away from whatever I was doing. I want the sidebar to
just appear without any other changes."

  (interactive)
  (let ((old-win (selected-window)))
    (treemacs)
    (select-window old-win)
    )
  )

Treemacs keybindings can be found here: Treemacs keybindings.

Visual-line-mode

Make it easy to set margin on visual-line-mode regardless of frame size.

(defvar visual-wrap-column nil)

(defun set-visual-wrap-column (new-wrap-column &optional buffer)
  "Force visual line wrap at NEW-WRAP-COLUMN in BUFFER (defaults
     to current buffer) by setting the right-hand margin on every
     window that displays BUFFER.  A value of NIL or 0 for
     NEW-WRAP-COLUMN disables this behavior."
  (interactive (list (read-number "New visual wrap column, 0 to disable: "
                                  (or visual-wrap-column fill-column 0))))
  (if (and (numberp new-wrap-column)
           (zerop new-wrap-column))
      (setq new-wrap-column nil))
  (with-current-buffer (or buffer (current-buffer))
    (visual-line-mode t)
    (set (make-local-variable 'visual-wrap-column) new-wrap-column)
    (add-hook 'window-configuration-change-hook 'update-visual-wrap-column nil t)
    (let ((windows (get-buffer-window-list)))
      (while windows
        (when (window-live-p (car windows))
          (with-selected-window (car windows)
            (update-visual-wrap-column)))
        (setq windows (cdr windows))))))

(defun update-visual-wrap-column ()
  (if (not visual-wrap-column)
      (set-window-margins nil nil)
    (let* ((current-margins (window-margins))
           (right-margin (or (cdr current-margins) 0))
           (current-width (window-width))
           (current-available (+ current-width right-margin)))
      (if (<= current-available visual-wrap-column)
          (set-window-margins nil (car current-margins))
        (set-window-margins nil (car current-margins)
                            (- current-available visual-wrap-column))))))

Modes - Language specific

Mail and Mutt mode

Basics

First some settings to get mail to work.

(require 'smtpmail)
(require 'gnutls)

;;(setq smtpmail-auth-credentials '(("smtp.gmail.com" 25 "USERNAME" "PASSWORD")))
;;(setq smtpmail-debug-info t)
(setq message-send-mail-function 'smtpmail-send-it)
(setq send-mail-function 'smtpmail-send-it)
(setq smtpmail-debug-info t)
(setq mail-host-address me/mail/mydomain)
(setq smtpmail-local-domain me/mail/mydomain)
(setq smtpmail-sendto-domain me/mail/mydomain)
(setq smtpmail-smtp-server me/mail/smtp-server)
(setq smtpmail-auth-credentials me/mail/credentials)
(setq smtpmail-smtp-service 587)
(setq smtpmail-warn-about-unknown-extensions t)
(setq starttls-extra-arguments nil)
(setq starttls-use-gnutls t)
(setq user-full-name me/fullname)
(setq mail-default-headers
      (concat
       "CC:\n"
       "BCC:\n"
       "X-RefLink: http://tinyurl.com/bprfeg\n"
       "User-Agent: " (mapconcat 'identity (cl-subseq (split-string (emacs-version) " ") 0 3) " ") "\n"
       ))
(setq mail-signature me/mail/signature)

Good to know

Oh and before I forget - when I flub my password, use the following to drop all credentials.

M-x auth-source-forget-all-cached

Mail hook

A hook to set things up nicely for mutt.

For keybindings, see Mail keybindings.

(defun me/mutt-mode-hook ()
  (visual-line-mode)
  (orgstruct-mode)
  )
(add-hook 'message-mode-hook 'me/mutt-mode-hook)

Adoc-mode-hook

For reading asciidoc files.

(add-hook 'adoc-mode-hook
          (lambda()
            (auto-fill-mode -1)
            (visual-line-mode)))

Python-mode

(add-hook 'python-mode-hook
          (lambda()
            (cond ((eq buffer-file-number nil)
                   (progn (interactive)
                          (goto-line 1)
                          (insert "#!/usr/bin/env python\n")
                          (insert "# -*- tab-width: 4 -*-\n")
                          )))))
(add-hook 'python-mode-hook 'blacken-mode)

DNS-mode

A decade or so ago, I manually edited dns zone files a lot, and I made frequent use of the $INCLUDE directive - meaning most dns zone files didn’t have a SOA post to increment. This resulted in an error when saving.

I wrote this piece of advice to avoid this problem.

(defadvice dns-mode-soa-maybe-increment-serial (before maybe-set-increment)
  "if there is a dns soa post, increment it. Otherwise, just save"
  (save-excursion
    (beginning-of-buffer)
    (message "dns-mode-soa-auto-increment-serial %s"
             (setq dns-mode-soa-auto-increment-serial
                   (and (search-forward-regexp "IN[ ''\t'']+SOA" nil t)
                        (not (search-forward-regexp "@SERIAL@" nil t)))
                   )
             )
    )
  )

(ad-activate 'dns-mode-soa-maybe-increment-serial)

Org-mode

I love org-mode, even if I only use a fraction of its capabilities.

Some commands I keep on forgetting how to use:

(org-insert-structure-template)

Insert structure block with shortcut C-c C-, (like code, comment, etc). If region is selected, the structure will wrap around it.

(org-edit-special)

If in a structure block, C-c C-’ spawns a separate window for editing the contents of the block. If code structure block, will set the right major mode. Exit using the same C-c C-’ key sequence.

Basic configuration

;; Basic config of Org
(require 'org)
(setq org-startup-indented t)
(setq org-log-done 'time)

(add-hook 'org-mode-hook
          (lambda ()
            (visual-line-mode)
            (flyspell-mode)
            (org-bullets-mode)
            (auto-fill-mode -1)))

;; My notes
(setq org-directory (concat sys/home "/notes/"))
(make-directory org-directory 1)
(setq org-default-notes-file (concat org-directory "/notes.org"))

;; Editing code in Org
(setq org-edit-src-content-indentation 2)
(setq org-src-fontify-natively t)
(setq org-src-tab-acts-natively t)

;; (setq org-fontify-whole-heading-line t)
;; (defun org-font-lock-ensure ()  ; This is apparently a bugfix. (?)
;;   (font-lock-fontify-buffer))

Org-bullets

Change how Org shows bullets - nice UTF-8 bullets instead of stars.

(setq org-hide-leading-stars t)           ; remove leading stars in org-mode
(setq org-bullets t)                      ; activate said pretty bullets

Look and feel of org-blocks

Make the org-blocks (the code where the emacs-lisp is) a darker colour than the default.

(set-face-attribute 'org-block-begin-line nil :background "grey10")
(set-face-attribute 'org-block nil :background "grey16")
(set-face-attribute 'org-block-end-line nil :background "grey10")

Executing code in shell blocks

(org-babel-do-load-languages 'org-babel-load-languages '((shell . t)))

With the above configuration, I can run shell commands in my org-mode files. All examples below prefixed with a . to force the interpreter of this doc to not evaluate it…

.#+BEGIN_SRC shell :results output :exports results
.  echo hello
.#+END_SRC

… or even this:

.#+BEGIN_SRC shell :results output :exports results
. ping -c 1 ping.sunet.se
.#+END_SRC

To execute the code, move the cursor into the block and hit C-c. It will ask you if you really want to do this, then the output (marked with #RESULTS:) will be placed immediately below the shell block.

.#+BEGIN_SRC shell :results output :exports results
.  ping -c 1 ping.sunet.se
.#+END_SRC
.
.#+RESULTS:
.: PING ping.sunet.se (192.36.125.18): 56 data bytes
.: 64 bytes from 192.36.125.18: icmp_seq=0 ttl=251 time=16.815 ms
.: 
.: --- ping.sunet.se ping statistics ---
.: 1 packets transmitted, 1 packets received, 0.0% packet loss
.: round-trip min/avg/max/stddev = 16.815/16.815/16.815/0.000 ms

Javascript-mode

See Tide-mode for Javascript or Tide-mode for JSX for more configuration stuff.

(add-hook 'js-mode-hook 'js2-minor-mode)

Java-mode

(defun me/java-mode-hook ()
  (c-add-style
   "my-java"
   '("java"
     (c-basic-offset . 2)))
  (c-set-style "my-java"))
(add-hook 'java-mode-hook 'me/java-mode-hook)

C-mode

(defun insert-c-function-doc-comment ()
"Insert a C function-style doc comment and position the cursor."
(interactive)
(insert "/**\n")
(insert " * \n")
(insert " */")
(forward-line -1)
(move-end-of-line nil))

Cperl-mode

For keybindings, see Perl keybindings.

Cperl indentation

Generally, I like how indentation is done in cperl, but one thing which drives me nuts is when cperl decides that my one-line code like:

if ( $a == 1 ) { print "hello"; }

Should be spaced out in the traditional way:

if ( $a == 1 ) {
  print "hello";
}

Of course I understand that one-liners can be jarring, but it’s useful at times. And I really don’t want my editor to join the invisible throngs of people who have opinions about my coding style.

So let’s kill this behaviour before it gets out of hand.

(setq cperl-break-one-line-blocks-when-indent nil)

It’s useful to auto-indent when I press semicolon.

(setq cperl-autoindent-on-semi t)

Some hairy cperl settings

Cperl-mode has more useful features than plain Perl-mode. Since Perl-mode is autoloaded when opening files with perl suffixes, we begin below by replacing perl-mode with cperl-mode.

I mentioned lots of useful features right? To turn most of them on, set cperl-hairy to t. But this turns all of the bells and whistles on, so instead I activate only the stuff I want.

cperl-electric-parens

Setting this to t, I get auto-complete of the following paired symbols: ({[]}) and in special cases, like in the following code, the <> too.

cperl-electric-keywords

If set to t some keywords get auto-expanded. E.g. if, while, for, unless, until.

cperl-electric-linefeed

If set to t, hitting C-j inside of, say, the inner conditional parens will place the cursor inside the curly brackets with the right indentation.

(defalias 'perl-mode 'cperl-mode)
;;(setq cperl-hairy t)
(setq cperl-electric-parens nil)
(setq cperl-electric-keywords nil)
(setq cperl-electric-linefeed t)
    

Cperl-hooks

Next, a cperl hook to set some stuff up.

  • First, load =flymake-mode= when cperl is started. I’m giving flycheck a chance for a while…
  • Next, cperl-mode has quite aggressive syntax-highlighting, and its face for arrays and hashes are kind of ugly. Here I change it so it’s slanted, unbolded and coloured.
(add-hook 'cperl-mode-hook
          (lambda () (progn
                       ;;(flymake-mode)
                       (flycheck-mode)

                       (set-face-italic 'cperl-array-face t)
                       (set-face-bold 'cperl-array-face nil)
                       (set-face-foreground 'cperl-array-face "yellow")
                       (set-face-background 'cperl-array-face nil)
                       (set-face-italic 'cperl-hash-face t)
                       (set-face-bold 'cperl-hash-face nil)
                       (set-face-foreground 'cperl-hash-face "red")
                       (set-face-background 'cperl-hash-face nil)
                       )
            )
          )

Tide-mode (Typescript, Javascript, jsx and tsx)

I’ll be re-using this function to initialise tide-mode with the configuration I want in the following sections.

(defun setup-tide-mode ()
  (interactive)
  (tide-setup)
  (flycheck-mode 1)           ; enable flycheck
  (setq flycheck-check-syntax-automatically '(save mode-enabled))
  (eldoc-mode 1)              ; show language item at point in the echo area
  (tide-hl-identifier-mode 1) ; highlight identical strings
  (company-mode 1)            ; turn on company mode
  )

Tide-mode for Typescript

;; Associate typescript-mode with .ts files
(add-to-list 'auto-mode-alist '("\\.ts\\'"  . typescript-mode))

;; When starting up typescript-mode:
(add-hook 'typescript-mode-hook #'setup-tide-mode)
(add-hook 'typescript-mode-hook
          (lambda ()
            (setq company-idle-delay 0.5)
            (local-set-key (kbd "C-M-,") 'me/insert-fat-comma)

            ;; I noticed that if I don't add editorconfig-apply after
            ;; setup-tide-mode, the editorconfig rules of the project
            ;; are overruled by what tide-mode thinks is good-looking
            ;; code. Very annoying. 

            (editorconfig-apply)
            ))

;; formats the buffer before saving
(add-hook 'before-save-hook 'tide-format-before-save)

Tide-mode for Javascript

(add-hook 'js-mode-hook #'setup-tide-mode)

;; configure javascript-tide checker to run after your default javascript checker
;(flycheck-add-next-checker 'javascript-eslint 'javascript-tide 'append)

Tide-mode for JSX

(require 'web-mode)
(add-to-list 'auto-mode-alist '("\\.jsx\\'" . web-mode))
(add-hook 'web-mode-hook
          (lambda ()
            ;; configure jsx-tide checker to run after your default jsx checker
            (flycheck-add-mode 'javascript-eslint 'web-mode)
            (flycheck-add-next-checker 'javascript-eslint 'jsx-tide 'append)

            (when (string-equal "jsx" (file-name-extension buffer-file-name))
              (setup-tide-mode))))

Tide-mode for TSX

(require 'web-mode)
(add-to-list 'auto-mode-alist '("\\.tsx\\'" . web-mode))
(add-hook 'web-mode-hook
          (lambda ()
            ;; enable typescript-tslint checker
            (flycheck-add-mode 'typescript-tslint 'web-mode)
            (when (string-equal "tsx" (file-name-extension buffer-file-name))
              (setup-tide-mode))))

Other languages/types of text

Finally, a bunch of small hooks for various modes.

(add-hook 'css-mode-hook 'hexcolour-add-to-font-lock)
(add-hook 'html-helper-mode-hook 'hexcolour-add-to-font-lock)
(add-hook 'html-mode-hook 'hexcolour-add-to-font-lock)
(add-hook 'text-mode-hook 'visual-line-mode)

Interactive functions

Here’s a bunch of functions, some of them written by me, most by other people.

Set frame title bar

Create a reasonable titlebar for emacs, which works on both windows and unix. Note: assumes HOSTNAME is exported.

(defun me/create-title-format (user host)
  "Creates a window title string which works for both win and unix"
  (interactive)
  (list
   "<Emacs> " (getenv user) "@" (getenv host) ":"
   '(:eval
     (if buffer-file-name
         (replace-regexp-in-string
          sys/home
          "~"
          (buffer-file-name))
       (buffer-name))))
  )

;; Set window and icon title.
(if (eq system-type 'windows-nt)
    (setq frame-title-format (me/create-title-format "USERNAME" "COMPUTERNAME"))
  (setq frame-title-format (me/create-title-format "USER" "HOSTNAME")))

Buffer navigation functions

This function has been really useful for me, since I often find myself wanting to jot something down in some trash buffer.

(defun me/switch-to-scratch ()
  "Switch to scratch buffer. Create one in `org-mode' if not exists."
  (interactive)
  (let ((previous (get-buffer "*scratch*")))
    (switch-to-buffer "*scratch*")
    ;; don't change current mode
    (unless previous (org-mode))))

Until lately, my emacs configuration was in ~/.emacs-stuff/dot.emacs.el which I symlinked to from ~/.emacs.el. Up until then (1992-2018), this function pointed at this file, which was opened upon invocation. Since switching to literal emacs configuration using org-babel, I’ve modified it a bit so that it opens ~/.emacs.d and moves the pointer to setup.org, which I open most often.

The function name isn’t really correct anymore since it actually doesn’t open the file, but call me melodramatic - this name reminds me of those other times. :)

(defun me/open-dot-emacs ()
  "Opens my main emacs configuration file."
  (interactive)
  (find-file sys/emacs-root)
  (end-of-buffer)
  (search-backward (concat (file-name-base this-file-org)
                           (file-name-extension this-file-org t)))
  )

Ansi-term, when invoked, normally starts by asking which shell I want. Since I go with /bin/bash, and I can have multiple ansi-term sessions running simultaneously on different machines or for different purposes, I replaced the query for what shell I want with a name for the ansi-term buffer.

(defun me/ansi-term()
  "Starts an ansi-term with optional buffer name"

  (interactive)
  (let (string)
    (setq string
          (read-from-minibuffer
           "Enter terminal buffer name: "
           "ansi-term"))
    (ansi-term "/bin/bash" string)
    )
  )

I found this function at emacswiki.org, helping me jump to important symbols in my buffer.

(defun ido-goto-symbol (&optional symbol-list)
  "Refresh imenu and jump to a place in the buffer using Ido."
  (interactive)
  (unless (featurep 'imenu)
    (require 'imenu nil t))
  (cond
   ((not symbol-list)
    (let ((ido-mode ido-mode)
          (ido-enable-flex-matching
           (if (boundp 'ido-enable-flex-matching)
               ido-enable-flex-matching t))
          name-and-pos symbol-names position)
      (unless ido-mode
        (ido-mode 1)
        (setq ido-enable-flex-matching t))
      (while (progn
               (imenu--cleanup)
               (setq imenu--index-alist nil)
               (ido-goto-symbol (imenu--make-index-alist))
               (setq selected-symbol
                     (ido-completing-read "Symbol? " symbol-names))
               (string= (car imenu--rescan-item) selected-symbol)))
      (unless (and (boundp 'mark-active) mark-active)
        (push-mark nil t nil))
      (setq position (cdr (assoc selected-symbol name-and-pos)))
      (cond
       ((overlayp position)
        (goto-char (overlay-start position)))
       (t
        (goto-char position)))))
   ((listp symbol-list)
    (dolist (symbol symbol-list)
      (let (name position)
        (cond
         ((and (listp symbol) (imenu--subalist-p symbol))
          (ido-goto-symbol symbol))
         ((listp symbol)
          (setq name (car symbol))
          (setq position (cdr symbol)))
         ((stringp symbol)
          (setq name symbol)
          (setq position
                (get-text-property 1 'org-imenu-marker symbol))))
        (unless (or (null position) (null name)
                    (string= (car imenu--rescan-item) name))
          (add-to-list 'symbol-names name)
          (add-to-list 'name-and-pos (cons name position))))))))

DNS-related functions

The functions me/generate-ptr-records and me/sort-A-records were really useful for me back when I managed Spotify’s DNS manually in the bad-old-days (which were in fact really good old days despite having to deal with our chaos that was DNS :))

(defun me/generate-ptr-records (start-pos end-pos)
  "Finds DNS A-records in region, and for each one, creates a PTR
   record in a temporary buffer.

   The PTR posts are sorted into sections by domainname.

   If no region was set, finds all A-records from point to end of
   buffer."

  (interactive "r")
  (let (origin            ; to make the hostname a fqdn
        rgx               ; ugly regex matching an A-record

        hostname          ; one hostname
        ip                ; one IPv4 address
        oct-list          ; each IPv4 octet in a list
        first-octets      ; 'aaa.bbb.ccc'
        last-octet        ; 'ddd'
        comment           ; optional comment, if any

        ptr-rec           ; one generated PTR record
        list-of-ptr-recs  ; PTR records with first 3 octets in common
        ptr-hash          ; key first 3 octets, value list-of-ptr-recs
        )

    ;; if no region was set, work from point to end-of-buffer.
    (setq end-pos (if (= (point) (mark)) (end-of-buffer)))

    ;; Bring point to beginning of region if selection was made from
    ;; upper part of the buffer to the end.
    (if (> (point) (mark)) (exchange-point-and-mark))

    ;; Pads string to three chars
    (defun pad-octet (octet)
      (if (= (length octet) 3)
          octet
        (pad-octet (concat octet " "))))

    ;; Read Origin from minibuffer
    (setq origin
          (read-from-minibuffer
           "Enter $ORIGIN: "
           (chomp (shell-command-to-string (concat "hostname -d")))))
    (setq origin (if (string= (substring origin -1) ".") ; make fqdn
                     origin                              ; if not fqdn
                   (concat origin ".")))

    ;; Regexp matching an A-record with optional comment
    (setq rgx
          (concat
           ;; hostname part
           "^\\([[:alnum:]\.-]+\\)"
           ".*?"

           ;; followed by A
           "[ ''\t'']A[ ''\t'']+"
           ".*?"

           ;; followed by (very) loose definition of an ip address
           "\\([[:digit:]]+\.[[:digit:]]+\.[[:digit:]]+\.[[:digit:]]+\\)"

           ;; followed by an optional comment
           ".*?\\(;.*?\\)?$"))

    ;; Walk through region, picking up all A-records and putting them
    ;; into a hash, using first three octets as key
    (setq ptr-hash (make-hash-table :test 'equal))
    (while (search-forward-regexp rgx end-pos 1)
      (setq hostname (match-string 1))
      (setq ip (match-string 2))
      (setq comment (if (null (match-string 3)) "" (match-string 3)))

      (setq oct-list (split-string ip "\\."))
      (setq first-octets (mapconcat
                          (lambda (x) x)
                          (nreverse (cons "IN-ADDR.ARPA." (butlast oct-list 1)))
                          "."))
      (setq last-octet (nth 3 oct-list))

      ;; create a PTR record
      (setq ptr-rec (concat (pad-octet last-octet)
                            "  IN  PTR  "
                            hostname "." origin
                            " " comment))

      ;; put the PTR record into the correct list
      (setq list-of-ptr-recs (gethash first-octets ptr-hash))
      (setq list-of-ptr-recs
            (if (null list-of-ptr-recs)
                (list ptr-rec)
              (cons ptr-rec list-of-ptr-recs)))

      ;; put the list
      (puthash first-octets list-of-ptr-recs ptr-hash)
      )

    (with-output-to-temp-buffer "ptr-records"
      (maphash
       (lambda (k v)
         (princ (format "\n$ORIGIN %s\n" k))
         (setq v (sort v (lambda (a b)
                           (< (string-to-number (car (split-string a " ")))
                              (string-to-number (car (split-string b " ")))))))
         (while (not (null v))
           (princ (format "%s\n" (pop v)))
           )
         )
       ptr-hash)
      )
    )
  )

(defun me/sort-A-records (start-pos end-pos)
  "Given a DNS buffer containing a bunch of A-records, this
function finds all records inside a region and sorts them by ip
address. The output is placed in a temporary buffer called
'sorted-ips'.

Todo someday: support the GENERATE directive"
  (interactive "r")

  ;; --------------------------------------------------
  ;; Helper functions
  (defun eq-octet (a b index)
    (= (string-to-number (nth index a))
       (string-to-number (nth index b))))

  (defun lt-octet (a b index)
    (< (string-to-number (nth index a))
       (string-to-number (nth index b))))

  (defun sort-hash-by-ip (hashtable)
    (let (mylist)
      (setq mylist         ;; Create a list of ip-hostname pairs
            (let (mylist)
              (maphash
               (lambda (kk vv)
                 (setq mylist (cons (list kk vv) mylist))) hashtable)
              mylist
              ))
      (sort mylist         ;; sort them by ip
            (lambda (y z)
              (setq y (split-string  (car y) "\\."))
              (setq z (split-string  (car z) "\\."))

              (if (eq-octet y z 0)
                  (if (eq-octet y z 1)
                      (if (eq-octet y z 2)
                          (lt-octet y z 3)
                        (lt-octet y z 2))
                    (lt-octet y z 1))
                (lt-octet y z 0))
              )
            )
      )
    )

  ;; --------------------------------------------------
  ;; Main body starts here
  (let (iphash)
    ;; create hash
    (setq iphash (make-hash-table :test 'equal))

    ;; if no region selected, just grab all A-records from point.
    (setq end-pos (if (= (point) (mark)) (end-of-buffer)))
    (if (> (point) (mark)) (exchange-point-and-mark))

    (while (search-forward-regexp
            "^\\([[:alnum:]\.-]+\\).*?[ ''\t'']A[ ''\t'']+.*?\\([[:digit:]]+\.[[:digit:]]+\.[[:digit:]]+\.[[:digit:]]+\\)" end-pos 1)
      (puthash (match-string 2) (match-string 1) iphash)
      )

    (with-output-to-temp-buffer "sorted-ips"
      (let (item mylist)
        (setq mylist (sort-hash-by-ip iphash))
        (while (setq item (pop mylist))
          (princ (format "%s\t%s\n" (car item) (cadr item)))
          )
        )
      )
    )
  )

Mail helper functions

Gmail messed everything up.

Prior to 2009, I had my own mail server, synced all my mail to my local machine using offline. I read email using mutt-ng and composed email in emacs. Often, I also sent email directly from emacs.

This worked flawlessly for me - I configured everything just the way I wanted it, and it was sweeeet. Mass mailing a long list of people with payloads which were all slightly different? No problem. Using GPG for people who understood what it was, but not others? Simple. Emailing someone a IRC transcript or code with just a few keystrokes? Wonderfully quick.

Then came Gmail. With lots of storage. And a powerful search engine. And how they almost email threads to work quite well (but not as well as in mutt). And how they used some vim and emacs navigation keybindings. And all of this without having to worry about maintaining my mail server…

Ultimately, I couldn’t resist the change. I moved everything to Google, and though I’m still concerned about my privacy, convenience is.. well, convenient.

So.

These functions are from before 2009, and I’m not 100% sure that bitrot hasn’t set in.

(defun me/random-quote ()
  "Gets a random quote"
  (load "fimblo-quotes" nil t)
  (aref fimblo-quotes
        (random (- (length fimblo-quotes) 1)))
  )

(defun me/generate-sig ()
  (with-temp-buffer
    (insert (me/random-quote))
    (goto-char (point-min))
    (fill-paragraph)
    (insert (concat
             mail-signature
             "\n\n"))
    (goto-char (point-min))
    ;;   (while (re-search-forward "^" nil t) (replace-match "  "))
    ;;   (goto-char (point-min))
    ;;   (insert "\n-- \n")
    (buffer-string)
    )
  )

(defun me/kill-signature ()
  "Delete current sig"
  (interactive)
  (end-of-buffer)
  (if (search-backward-regexp "^-- $" nil t )
      (progn
        (beginning-of-line)
        (setq start (point))
        (end-of-buffer)
        (delete-region start (point))))
  )

(defun me/message-replace-sig ()
  "Replaces signature with new sig"
  (interactive)
  (me/kill-signature)
  (end-of-buffer)
  (delete-char -1)
  (insert (me/generate-sig))
  )

(defun me/kill-to-signature ()
  "Delete all text between text and signature."
  (interactive)
  (setq start (point))
  (end-of-buffer)
  (search-backward-regexp "^-- $" nil 1)
  (previous-line)
  (setq end (point))
  (delete-region start end)
  (recenter-top-bottom)
  (insert "\n\n\n")
  (previous-line 2)
  )

(defun me/mail-snip (b e summ)
  "remove selected lines, and replace it with [snip:summary (n lines)]"
  (interactive "r\nsSummary: ")
  (let ((n (count-lines b e)))
    (delete-region b e)
    (insert (format "\n[snip%s (%d line%s)]\n\n"
                    (if (= 0 (length summ)) "" (concat ": " summ))
                    n
                    (if (= 1 n) "" "s")))))

Simple text manipulation

Here’s a bunch of small functions which help me modify text in different ways.

First off, some interactive functions to convert strings into camelCase. I bound it to M-s-c over in Buffer manipulation.

(defun me/upper-camelcase-region (beg end)
  "Convert region to upper camelCase."
  (interactive "r")
  (let ((str (buffer-substring-no-properties beg end)))
    (delete-region beg end)
    (insert (mapconcat 'capitalize (split-string str) ""))))

(defun me/lower-camelcase-region (beg end)
  "Convert region to lower camelCase."
  (interactive "r")
  (let* ((str (buffer-substring-no-properties beg end))
         (words (split-string str))
         (first-word (car words))
         (rest-words (cdr words)))
    (delete-region beg end)
    (insert (concat (downcase first-word)
                    (mapconcat 'capitalize rest-words "")))))

(defun me/camelcase-region (beg end &optional upperCamelCase)
  "Transform words in region to camelCase.

  If the universal argument is given, transform words instead
  to upperCamelCase."
  (interactive "rP")
  (if (equal current-prefix-arg '(4))
      (me/upper-camelcase-region beg end)
    (me/lower-camelcase-region beg end)))

Then the rest which I’ve written some time in the past.

(defun me/insert-fat-comma ()
  "Inserts a ' => ' at point.

   Used in Perl and Javascript."
  (interactive)
  (insert " => ")
  )

(defun me/merge-lines ()
  "Make paragraph I am in right now into one line."
  (interactive)
  (let (p)
    (forward-paragraph)
    (setq p (point))
    (backward-paragraph)
    (next-line)
    (while (re-search-forward "\n +"  p t)
      (replace-match " ")
      )
    )
  )

;; inserts a context-aware commented separator
(fset 'add-separator
      [?\C-a return up ?\C-5 ?\C-0 ?- ?\C-  ?\C-a ?\M-x ?c ?o ?m ?m ?e ?n ?t ?  ?r ?e ?g ?i ?o ?n return down])


(defun me/insert-time ()
  "Insert time at point in format %H:%M:%S. If universal-argument
   is set, use format %H%M%S instead."
  (interactive)
  (if current-prefix-arg
      (insert (format-time-string "%H%M%S"))
    (insert (format-time-string "%H:%M:%S"))))

(defun me/insert-date ()
  "Insert date at point in format %Y/%m/%d. If universal-argument
   is set, use format %Y%m%d instead."
  (interactive)
  (if current-prefix-arg
      (insert (format-time-string "%Y%m%d"))
    (insert (format-time-string "%Y/%m/%d"))))

(defun me/insert-datetime ()
  "Insert datetime at point in format %Y/%m/%d-%H:%M:%S. If
   universal-argument is set, use format %Y%m%d-%H%M%S instead."
  (interactive)
  (if current-prefix-arg
      (insert (format-time-string "%Y%m%d-%H%M%S"))
    (insert (format-time-string "%Y/%m/%d-%H:%M:%S"))))

;; skipping the 'me/' prefix since this needs to be short
(defun iwb ()
  "indent and untabify whole buffer"
  (interactive)
  (delete-trailing-whitespace)
  (indent-region (point-min) (point-max) nil)
  (untabify (point-min) (point-max)))

(defun me/wrap-text (start end)
  "Asks for two strings, which will be placed before and after a
   selected region"
  (interactive "r")
  (let (prefix suffix)
    (setq prefix (read-from-minibuffer "Prefix: "))
    (setq suffix (read-from-minibuffer "Suffix: "))
    (save-restriction
      (narrow-to-region start end)
      (goto-char (point-min))
      (insert prefix)
      (goto-char (point-max))
      (insert suffix)
      )))

(defun me/wrap-region (start end)
  "Given a prefix and a suffix, this function will wrap each line
in the region such that they are prefixed with the prefix and
suffixed with the suffix.

If no region is selected, it will do the above for all lines from
point to the end of the buffer."

  (interactive "r")
  (let (prefix suffix linecount str-len end-pos)
    (setq prefix (read-from-minibuffer "Prefix: "))
    (setq suffix (read-from-minibuffer "Suffix: "))

    ;; if no region was set, work from point to end-of-buffer.
    (setq end-pos (if (= (point) (mark)) (end-of-buffer) end))

    ;; Bring point to beginning of region if selection was made from
    ;; upper part of the buffer to the end.
    (if (> (point) (mark)) (exchange-point-and-mark))

    (setq linecount (count-lines (point) end-pos))
    (setq linecount (if (= start (point))
                        linecount
                      (progn
                        (forward-line)
                        (- linecount 1))))

    (setq str-len (+ end-pos (* linecount  (+ (length (concat prefix suffix))))))

    (message "Start: %s, End-Pos: %s, Point: %s" start end-pos (point))
    (message "Linecount: %s" linecount)

    (while (re-search-forward "^\\(.*\\)$"  str-len  nil)
      (replace-match (concat prefix "\\1" suffix) nil nil)
      )
    )
  )

HTML stuff

In html-mode and css-mode, make all instances of strings matching #xxyyzz where x, y, and z are two-char hex chars get syntax highlighting corresponding to the colour specified.

(defun hexcolour-luminance (color)
  "Calculate the luminance of a color string (e.g. \"#ffaa00\", \"blue\").
  This is 0.3 red + 0.59 green + 0.11 blue and always between 0 and 255."
  (let* ((values (x-color-values color))
         (r (car values))
         (g (cadr values))
         (b (caddr values)))
    (floor (+ (* .3 r) (* .59 g) (* .11 b)) 256)))

(defun hexcolour-add-to-font-lock ()
  (interactive)
  (font-lock-add-keywords
   nil
   `((,(concat "#[0-9a-fA-F]\\{3\\}[0-9a-fA-F]\\{3\\}?\\|"
               (regexp-opt (x-defined-colors) 'words))
      (0 (let ((colour (match-string-no-properties 0)))
           (put-text-property
            (match-beginning 0) (match-end 0)
            'face `((:foreground ,(if (> 128.0 (hexcolour-luminance colour))
                                      "white" "black"))
                    (:background ,colour)))))))))

Org functions

For a couple of years I put all my todos into an org-file called ~/todo.org. These functions helped me with this.

(defun me/switch-to-todo ()
  "Switch to todo buffer. Open file if necessary"
  (interactive)
  (find-file-other-window (concat sys/home "/todo.org"))
  (goto-char (point-min)))

(defun me/add-todo ()
  "Add a todo to the todo buffer."
  (interactive)
  (me/add-todo-helper (read-from-minibuffer "Todo: "))
  )

(defun me/add-todo-helper (msg)
  (save-current-buffer
    (set-buffer (find-file-noselect (concat sys/home "/todo.org")))
    (goto-char (point-min))
    (re-search-forward "^\* Todo$" nil t)
    (insert "\n** TODO " msg)
    (org-schedule nil (current-time))
    (save-buffer)
    )
  )

I use this following function when I use plain org-mode for presentations.

;; http://stackoverflow.com/questions/12915528/easier-outline-navigation-in-emacs
(defun org-show-next-heading-tidily ()
  "Show next entry, keeping other entries closed."
  (interactive)
  (if (save-excursion (end-of-line) (outline-invisible-p))
      (progn (org-show-entry) (show-children))
    (outline-next-heading)
    (unless (and (bolp) (org-on-heading-p))
      (org-up-heading-safe)
      (hide-subtree)
      (error "Boundary reached"))
    (org-overview)
    (org-reveal t)
    (org-show-entry)
    (show-children)
    )
  )

Moving lines and regions

These functions allow me to move single lines or entire regions up and down. For keybindings, see: Buffer manipulation.

;; http://www.emacswiki.org/emacs/MoveLineRegion

(defun me/move-line (&optional n)
  "Move current line N (1) lines up/down leaving point in place."
  (interactive "p")
  (when (null n)
    (setq n 1))
  (let ((col (current-column)))
    (beginning-of-line)
    (forward-line)
    (transpose-lines n)
    (forward-line -1)
    (forward-char col))
  (indent-according-to-mode))

(defun me/move-line-up (n)
  "Moves current line N (1) lines up leaving point in place."
  (interactive "p")
  (me/move-line (if (null n) -1 (- n))))

(defun me/move-line-down (n)
  "Moves current line N (1) lines down leaving point in place."
  (interactive "p")
  (me/move-line (if (null n) 1 n)))

(defun me/move-region (start end n)
  "Move the current region up or down by N lines."
  (interactive "r\np")
  (let ((line-text (delete-and-extract-region start end)))
    (forward-line n)
    (let ((start (point)))
      (insert line-text)
      (setq deactivate-mark nil)
      (set-mark start))))

(defun me/move-region-up (start end n)
  "Move the current region up by N lines."
  (interactive "r\np")
  (me/move-region start end (if (null n) -1 (- n))))

(defun me/move-region-down (start end n)
  "Move the current region down by N lines."
  (interactive "r\np")
  (me/move-region start end (if (null n) 1 n)))

(defun me/move-line-region-up (start end n)
  (interactive "r\np")
  (if (region-active-p) (me/move-region-up start end n) (me/move-line-up n)))

(defun me/move-line-region-down (start end n)
  (interactive "r\np")
  (if (region-active-p) (me/move-region-down start end n) (me/move-line-down n)))

Other functions

This function is useful to toggle selective-display, which is often (but not always) used to show all lines which don’t start with indentation - that is, function/method/class names in a buffer.

(defun me/toggle-selective-display ()
  "Run this to show only lines in buffer with a non-whitespace
   character on column 0. run again to go back."
  (interactive)
  (set-selective-display (if selective-display nil 1)))

When I want to do simple arithmetic in the buffer, I write (for example): (+ 3 8) then place my cursor after the close paren and run eval-and-replace which replaces the expression with its output.

(defun me/eval-and-replace ()
  "Replace the preceding sexp with its value."
  (interactive)
  (backward-kill-sexp)
  (condition-case nil
      (prin1 (eval (read (current-kill 0)))
             (current-buffer))
    (error (message "Invalid expression")
           (insert (current-kill 0)))))

These two functions help me do operations on both a file and its corresponding buffer.

;; Ripped from Steve Yegges .emacs
(defun rename-file-and-buffer (new-name)
  "Renames both current buffer and file it's visiting to NEW-NAME."
  (interactive "sNew name: ")
  (let ((name (buffer-name))
        (filename (buffer-file-name)))
    (if (not filename)
        (message "Buffer '%s' is not visiting a file!" name)
      (if (get-buffer new-name)
          (message "A buffer named '%s' already exists!" new-name)
        (progn
          (rename-file name new-name 1)
          (rename-buffer new-name)
          (set-visited-file-name new-name)
          (set-buffer-modified-p nil))))))

;; copied from http://blog.tuxicity.se/
(defun delete-file-and-buffer ()
  "Deletes file connected to current buffer and kills buffer."
  (interactive)
  (let ((filename (buffer-file-name))
        (buffer (current-buffer))
        (name (buffer-name)))
    (if (not (and filename (file-exists-p filename)))
        (error "Buffer '%s' is not visiting a file!" name)
      (when (yes-or-no-p "Are you sure you want to remove this file? ")
        (delete-file filename)
        (kill-buffer buffer)
        (message "File '%s' successfully removed" filename)))))

I used this function before I found out about forward-sexp and backward-sexp, bound by default to C-M-f and C-M-b. I’m keeping it mostly as an example of how to use prefix arguments in (interactive "p").

(defun me/match-paren (arg)
  "Go to the matching paren if on a paren; otherwise insert %."
  (interactive "p")
  (cond ((looking-at "\\s\(") (forward-list 1) (backward-char 1))
        ((looking-at "\\s\)") (forward-char 1) (backward-list 1))
        (t (self-insert-command (or arg 1)))))

My oldest remaining emacs configuration, copied in ‘93 from someone who in turn copied it from someone called “phille” at KTH. He was considered an emacs-god at the time.

I don’t really use these anymore, since there are simpler ways of removing ^M or removing whitespaces at the end of all lines in a buffer.

But I keep them here to remind me of those early days when I had to turn off my modem to exit emacs.

(defun philles-takM-formatterare ()
  "Tar bort dessa irriterande ^M."
  (interactive)
  (save-excursion
    (goto-char (point-min))
    (while (search-forward "
" nil t)
      (replace-match "" nil t)))
  )

(defun philles-whitespace-formatterare ()
  "Ta bort allt whitespace (space + tabbar) i slutet av varje rad i bufferten"
  (interactive)
  (message "Function disabled. Use delete-trailing-whitespace instead.")
  )

Helper functions

These functions are called by others.

(defun file-string (file)
  "Read the contents of a file and return as a string."
  (with-temp-buffer
    (insert-file-contents file)
    (buffer-string)))

(defun chomp (str)
  "Chomp tailing newlines from string"
  (let ((s (if (symbolp str) (symbol-name str) str)))
    (replace-regexp-in-string "[''\n'']*$" "" s)))

(defun get-ipv4-regex ()
  (let (p1 p2 p3 octet-re)
    (setq p1 "[01]?[[:digit:]]?[[:digit:]]")
    (setq p2 "2[01234][[:digit:]]")
    (setq p3 "25[012345]")
    (setq octet-re (concat "\\(" p1 "\\|" p2 "\\|" p3 "\\)"))
    (concat "^" (mapconcat (lambda (x) x)
                           (list octet-re octet-re octet-re octet-re)
                           "\\.") "$")
    )
  )

Keybindings

Keybindings!

I split the bindings into global keybindings which work everywhere, and local keybindings which work only in buffers with specific buffers.

Global keybindings

Of all these global keybindings, I think I just use a handful. Some of them should be local too.

General keybindings

(global-set-key "\C-c\C-d"            'me/insert-date)
(global-set-key "\C-x\C-y"            'toggle-truncate-lines)
(global-set-key [ f7 ]                'me/ansi-term)
(global-set-key (kbd "M-%")           'query-replace-regexp)
(global-set-key (kbd "C-x SPC")       'whitespace-mode)

Window management

Change focus to window in the direction of the arrow.

(global-set-key (kbd "C-x <down>")    'windmove-down)
(global-set-key (kbd "C-x <up>")      'windmove-up)
(global-set-key (kbd "C-x <right>")   'windmove-right)
(global-set-key (kbd "C-x <left>")    'windmove-left)

Swap contents of this window the the window in the direction of the arrow.

(global-set-key (kbd "C-M-x <down>")  'windmove-swap-states-down)
(global-set-key (kbd "C-M-x <up>")    'windmove-swap-states-up)
(global-set-key (kbd "C-M-x <right>") 'windmove-swap-states-right)
(global-set-key (kbd "C-M-x <left>")  'windmove-swap-states-left)

Enlarge/shrink this window vertically or horizontally.

;; Vertical expand/shrink
(global-set-key [ f11 ]               #'(lambda () (interactive) (enlarge-window 4 )))
(global-set-key [ M-f11 ]             #'(lambda () (interactive) (enlarge-window -4)))

;; Horizontal expand/shrink
(global-set-key [ f12 ]               #'(lambda () (interactive) (enlarge-window 4 1)))
(global-set-key [ M-f12 ]             #'(lambda () (interactive) (enlarge-window -4 1)))

Buffer manipulation

(global-set-key (kbd "C-:")           'iedit-mode)

(global-set-key (kbd "C-S-e")         'me/merge-lines)
(global-set-key [(shift meta ?c)]     'me/camelcase-region)

;; Move lines and regions up and down
(global-set-key [(meta up)]           'me/move-line-up)
(global-set-key [(meta down)]         'me/move-line-down)
(global-set-key [(shift meta up)]     'me/move-line-region-up)
(global-set-key [(shift meta down)]   'me/move-line-region-down)

Buffer navigation

(global-set-key "\C-c\C-g"            'goto-line)
(global-set-key (kbd "M-i")           'ido-goto-symbol)

;; move the buffer contents up and down without moving the cursor
(global-set-key [(meta ?n)]           #'(lambda () (interactive) (scroll-up 3)))
(global-set-key [(meta ?p)]           #'(lambda () (interactive) (scroll-down 3)))
(global-set-key [(shift meta ?n)]     #'(lambda () (interactive) (scroll-other-window 3)))
(global-set-key [(shift meta ?p)]     #'(lambda () (interactive) (scroll-other-window -3)))

;; Jump to top and bottom of a buffer or the other one
;; The last two are default keybindings. But I added them here to remind me of them.
(global-set-key [ home ]              'beginning-of-buffer)
(global-set-key [ end ]               'end-of-buffer )
(global-set-key [(meta home) ]        'beginning-of-buffer-other-window)
(global-set-key [(meta end) ]         'end-of-buffer-other-window )

Opening new buffers

(global-set-key [ f5 ]                'me/switch-to-scratch)
(global-set-key [ M-f5 ]              'me/open-dot-emacs)

(global-set-key (kbd "C-x C-b")       'ibuffer)

(global-set-key "\C-x\C-g"            'find-file-at-point)
(global-set-key (kbd "C-h C-s")       'find-function-at-point)

Swiper/Ivy/Counsel keybindings

  (global-set-key (kbd "C-S-s")         'swiper)
  (global-set-key (kbd "C-c C-r")       'ivy-resume)
;;  (global-set-key (kbd "<f6>")          'ivy-resume)
  (global-set-key (kbd "M-x")           'counsel-M-x)
  (global-set-key (kbd "C-x C-f")       'counsel-find-file)
  (global-set-key (kbd "<f1> f")        'counsel-describe-function)
  (global-set-key (kbd "<f1> v")        'counsel-describe-variable)
  (global-set-key (kbd "<f1> l")        'counsel-find-library)
  (global-set-key (kbd "<f2> i")        'counsel-info-lookup-symbol)
  (global-set-key (kbd "<f2> u")        'counsel-unicode-char)
  (global-set-key (kbd "C-c g")         'counsel-git)
  (global-set-key (kbd "C-c j")         'counsel-git-grep)
  (define-key minibuffer-local-map (kbd "C-r") 'counsel-minibuffer-history)
                                          ;(global-set-key (kbd "C-x l") 'counsel-locate)
                                          ;(global-set-key (kbd "C-c k") 'counsel-ag)

Treemacs keybindings

(global-set-key (kbd "<f1> 1")        'me/toggle-treemacs)

Unused global keybindings

(global-set-key "\C-x\C-m"            'execute-extended-command)
(global-set-key "\C-c\C-m"            'execute-extended-command)
(global-set-key "\C-c\C-k"            'kill-buffer)
(global-set-key "\C-co"               'org-capture)
(global-set-key "\C-xm"               'mail)
(global-set-key [ f6 ]                'me/switch-to-todo)
(global-set-key [ S-f6 ]              'me/add-todo)
(global-set-key [ f10 ]               'me/org-show-next-heading-tidily)

Mode-specific keybindings

I use two different ways of assigning mode-local keybindings.

  1. The first tells emacs to add a key-function mapping to a specific mode-map after it loads the module (e.g. Mail keybindings).
  2. The second adds a lambda where a key is mapped to a function to a mode’s hook. (e.g. Javascript keybindings).

    I think I like the second method more.

Prog-mode keybindings

Available in all prog-modes.

(defun me/revert-buffer-with-fine-grain ()
  (interactive)
  (revert-buffer-with-fine-grain nil t))

(add-hook 'prog-mode-hook
          (lambda ()
            (progn
              (local-set-key "\C-cc"         'compile)
              (local-set-key "\C-cd"         'gdb)
              (local-set-key "\C-cn"         'next-error)
              (local-set-key (kbd "M-0")     'add-separator)
              (local-set-key [ \C-tab ]      'hippie-expand)
              (local-set-key [ f6 ]          'me/toggle-selective-display)
              (local-set-key [ f8 ]          'hl-line-mode)
              (local-set-key [ M-f8 ]        'linum-mode)
              (local-set-key (kbd "C-x x g") 'me/revert-buffer-with-fine-grain)
              )
            )
          )

Javascript keybindings

(add-hook 'js-mode-hook
          (lambda ()
            (local-set-key (kbd "C-M-,") 'me/insert-fat-comma)))

Mail keybindings

(eval-after-load 'message
  '(define-key message-mode-map [ f9 ] 'me/message-replace-sig))

(eval-after-load 'message
  '(define-key message-mode-map [?\C-c ?\C-k] 'me/kill-to-signature))

(add-hook 'mail-mode-hook
          #'(lambda ()
              (define-key mail-mode-map "\C-c\C-w" 'me/message-replace-sig)
              ))

Perl keybindings

(add-hook 'cperl-mode-hook
          #'(lambda () (local-set-key (kbd "C-M-,") 'me/insert-fat-comma)))

Dired keybindings

When your cursor is on a directory and you press i, dired-maybe-insert-subdir is called. It adds the subdirectory at the bottom of the buffer. Though this is useful, Dired-subtree is better - it adds the subdir directly under the dir you opened, indented a bit.

Use <tab> to expand a dir, and <tab> again to close it. If you’ve moved your cursor into the contents of the dir, then shift-tab will close it for you.

(eval-after-load "dired"
  '(progn
     (define-key dired-mode-map (kbd "<tab>")     'dired-subtree-toggle)
     (define-key dired-mode-map (kbd "<backtab>") 'dired-subtree-remove)
     (define-key dired-mode-map (kbd ".")         'me/dired-dotfiles-toggle)
     )
  )

Other stuff

On Etags

For some reason, I haven’t used etags (or any other tag functionality) over all these years. Kind of strange. Anyway, here’s how to set up etags for a project.

First, run this command to create the etags table. Stand in the root of the project in question, since the etags file (called TAGS) will be created there. The pattern you specify depends on what language you’re creating the tags for.

find . -name '<pattern>' -exec etags -a {} \;

For perl, one could have the pattern: *.p[lm] to capture both .pl and .pm files. For emacs-lisp it would be *.el. Etc.

Next, open one of the files in that directory, and load it with M-x visit-tags-table. After this, you can find the definition of a function or variable using M-. and jump back to where you were with meta-comma.

lint

Here some pocket lint which I don’t use but might want to at some point in the future.

;; Never compile .emacs by hand again
;;(add-hook 'after-save-hook 'autocompile)
;; (defun autocompile ()
;;   "compile itself if dot.emacs.el"
;;   (interactive)
;;   (if (string= (buffer-file-name) (concat default-directory "dot.emacs.el"))
;;       (byte-compile-file (buffer-file-name))))

;;(defmacro help/on-gui (statement &rest statements)
;;  "Evaluate the enclosed body only when run on GUI."
;;  `(when (display-graphic-p)
;;     ,statement
;;     ,@statements))

;; or
;;
;;(when (display-graphic-p)
;;  (set-frame-font "...")
;;  (require '...)
;;  (...-mode))
;;

;; (defun html-mode-end-paragraph ()
;;   "End the paragraph nicely"
;;   (interactive)
;;  (insert "</p>\n"))

dot.emacs's People

Contributors

fimblo avatar

Stargazers

Giuseppe Sforza avatar Olivia 5000 avatar

Watchers

Olivia 5000 avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.