My Emacs configuration

2021-03-23

Below are fragments from my Emacs configuration file (~/.emacs.d/init.el).

The newest version of this config is available from a github repo.

I recommend watching the following video by BuildFunThings called My GNU Emacs configuration for programming.

If you put this config on a new machine with no Emacs packages installed then you will be asked if you want automatically install all packages used below.

Later, if you add new packages you can temporary set use-package-always-ensure below to t start Emacs and then set it back to nil (or always have it set to t, if you prefer).

Note that for Emacs 25 you first need to install new ELPA signing key as described here.

Basic settings

Directory with local Emacs lisp files

I add a directory to the lisp search path where I can add my own lisp code and (now less often) downloaded lisp code which is not available through MELPA. Then I initialize secure downloading from GNU and MELPA archives of Emacs packages.

(let ((path (expand-file-name "~/.emacs.d/lisp")))
  (if (file-accessible-directory-p path)
      (add-to-list 'load-path path t)))

Add MELPA package list

You can install many Emacs packages from MELPA repository. To add MELPA to the package list add the following to your ~/.emacs.d/init.el file

(require 'package)
(setq package-archives
      '(("gnu" . "https://elpa.gnu.org/packages/")
        ("melpa-stb" . "https://stable.melpa.org/packages/")
        ("melpa" . "https://melpa.org/packages/"))
      tls-checktrust t
      tls-program '("gnutls-cli --x509cafile %t -p %p %h")
      gnutls-verify-error t)

This also turns on checking TLS certificates (in both possible modes) with tls-program set to only the first value from the default value (for more info see Your Text Editor Is Malware).

We use major version specific package directory (but only if the directory already exists, meaning you may be switching between more than one version of Emacs).

(let ((path (format "~/.emacs.d/elpa-%s" emacs-major-version)))
  (if (file-accessible-directory-p path)
      (setq package-user-dir path)))

Now we are ready to initialize package system.

(package-initialize)

(setq use-package-always-ensure nil)

Now you can list available packages by running M-x list-packages. Mark packages you want to install by pressing i and later press x to install all marked packages (the necessary dependencies will be installed automatically).

Now, load use-package package or propose automatic installation if it is not yet installed.

(unless (require 'use-package nil t)
  (if (not (yes-or-no-p (concat "Refresh packages, install use-package and"
                                " other packages used by init file? ")))
      (error "you need to install use-package first")
    (package-refresh-contents)
    (package-install 'use-package)
    (require 'use-package)
    (setq use-package-always-ensure t)))

After loading use-package we can use it to configure other packages.

Workaround for security vulnerability in Emacs >= 21.1 and < 25.3

See Changes in Emacs 25.3

(eval-after-load "enriched"
    '(defun enriched-decode-display-prop (start end &optional param)
       (list start end)))

Productivity

Key discoverability

If you type a prefix key (such as C-x r) and wait some time then display window with keys that can follow.

(use-package which-key
  :config
  (which-key-mode))

List of personal key bindings

(global-set-key (kbd "C-c h b") 'describe-personal-keybindings)

(use-package remind-bindings
  :bind ("H-?" . remind-bindings-togglebuffer))

More efficient buffer/file selection

(setq recentf-max-saved-items 100)

(global-set-key "\C-cq" #'bury-buffer)

(use-package flx
  :after ivy)

(use-package counsel
  :demand
  :init
  (setq ivy-use-virtual-buffers t
        ivy-re-builders-alist
        '((counsel-git-grep . ivy--regex-plus)
          (counsel-rg . ivy--regex-plus)
          (swiper . ivy--regex-plus)
          (swiper-all . ivy--regex-plus)
          (t . ivy--regex-fuzzy)))
  :config
  (add-to-list 'ivy-ignore-buffers "\\`\\*remind-bindings\\*")
  (ivy-mode 1)
  (counsel-mode 1)
  :bind
  (("C-c E" . counsel-flycheck)
   ("C-c f" . counsel-fzf)
   ("C-c g" . counsel-git)
   ("C-c j" . counsel-git-grep)
   ("C-c L" . counsel-locate)
   ("C-c o" . counsel-outline)
   ("C-c r" . counsel-rg)
   ("C-c R" . counsel-register)
   ("C-c T" . counsel-load-theme)))

(use-package ivy-rich
  :config
  (setq ivy-rich-display-transformers-list
        (plist-put ivy-rich-display-transformers-list
                   'ivy-switch-buffer
                   '(:columns
                     ((ivy-switch-buffer-transformer (:width 40))
                      (ivy-rich-switch-buffer-project
                       (:width 15 :face success))
                      (ivy-rich-switch-buffer-path
                       (:width (lambda (x)
                                 (ivy-rich-switch-buffer-shorten-path
                                  x (ivy-rich-minibuffer-width 0.3))))))
                     :predicate (lambda (cand) (get-buffer cand)))))
  (ivy-rich-mode 1))

For counsel-fzf install fzf and for counsel-rg install ripgrep (rg).

File explorer sidebar

(use-package treemacs
  :bind
  (("C-c t" . treemacs)
   ("s-a" . treemacs)))

Cycle through buffers' history

(use-package buffer-flip
  :bind
  (("H-f" . buffer-flip)
   :map buffer-flip-map
   ("H-f" . buffer-flip-forward)
   ("H-F" . buffer-flip-backward)
   ("C-g" . buffer-flip-abort)))

Window selection enhancements

(use-package ace-window
  :init
  (setq aw-scope 'frame ; limit to single frame
        aw-keys '(?a ?s ?d ?f ?g ?h ?j ?k ?l))
  :bind
  ("C-x o" . ace-window))

(use-package windmove
  :demand
  :bind
  (("H-w j" . windmove-down)
   ("H-w k" . windmove-up)
   ("H-w h" . windmove-left)
   ("H-w l" . windmove-right))
  :config
  (windmove-default-keybindings))

(use-package windswap
  :demand
  :bind
  (("H-w J" . windswap-down)
   ("H-w K" . windswap-up)
   ("H-w H" . windswap-left)
   ("H-w L" . windswap-right))
  :config
  (windswap-default-keybindings))

Allow for Undo/Redo of window manipulations (such as C-x 1)

(winner-mode 1)

In buffer movement enhancements

Improved in buffer search

(use-package ctrlf
  :config
  (ctrlf-mode 1))

Type substring and wait to select one of its visible occurrences (even in other windows) with a single or two letters.

(use-package avy
  :bind
  (("H-." . avy-goto-char-timer)
   ("H-," . avy-goto-line)))

Bind key o to selection of a link in help or info buffers by a single or two letters.

(use-package ace-link
  :config
  (ace-link-setup-default))

Select from visible errors by a single letter

(use-package avy-flycheck
  :bind
  ("C-c '" . avy-flycheck-goto-error))

Go to last change in the current buffer

(use-package goto-chg
  :bind
  ("C-c G" . goto-last-change))

Editing enhancements

Context aware insertion of pairs of parenthesis

(use-package smartparens
  :defer)

Edit with multiple cursors

(use-package multiple-cursors
  :bind
  (("C-c n" . mc/mark-next-like-this)
   ("C-c p" . mc/mark-previous-like-this)))

Fix trailing spaces but only in modified lines

(use-package ws-butler
  :hook (prog-mode . ws-butler-mode))

Expand region

(use-package expand-region
  :bind ("H-e" . er/expand-region))

Spell checking

(setq ispell-dictionary "american")

(defun my-american-dict ()
  "Change dictionary to american."
  (interactive)
  (setq ispell-local-dictionary "american")
  (flyspell-mode 1)
  (flyspell-buffer))

(defun my-polish-dict ()
  "Change dictionary to polish."
  (interactive)
  (setq ispell-local-dictionary "polish")
  (flyspell-mode 1)
  (flyspell-buffer))

(defalias 'ir #'ispell-region)

Shell and terminal

(use-package shell-pop
  :init
  (setq shell-pop-full-span t)
  :bind (("C-c s" . shell-pop)))

(use-package vterm
  :defer)

(use-package vterm-toggle
  :bind (("H-z" . vterm-toggle)
         ("H-F" . vterm-toggle-forward)
         ("H-B" . vterm-toggle-backward)))

Git

(use-package magit
  :bind (("C-x g" . magit-status)
         ("C-x M-g" . magit-dispatch)))

(use-package git-commit
  :hook (git-commit-mode . my-american-dict))

(use-package git-messenger
  :bind ("C-x G" . git-messenger:popup-message)
  :config
  (setq git-messenger:show-detail t
        git-messenger:use-magit-popup t))

Switching buffers

Bind keys from H-r a to H-r z to switch to buffers from a register from a to z.

(defalias 'pr #'point-to-register)

(defun my-switch-to-register ()
  "Switch to buffer given by a register named by last character
of the key binding used to execute this command."
  (interactive)
  (let* ((v (this-command-keys-vector))
         (c (aref v (1- (length v))))
         (r (get-register c)))
    (if (and (markerp r) (marker-buffer r))
        (switch-to-buffer (marker-buffer r))
      (jump-to-register c))))

(setq my-switch-to-register-map (make-sparse-keymap))

(dolist (character (number-sequence ?a ?z))
  (define-key my-switch-to-register-map
    (char-to-string character) #'my-switch-to-register))

(global-set-key (kbd "H-r") my-switch-to-register-map)

Yasnippet and abbrev mode

(setq-default abbrev-mode 1)

(use-package yasnippet
  :hook (after-init . yas-global-mode)
  :bind
  (:map yas-minor-mode-map
        ("C-c & t" . yas-describe-tables)
        ("C-c & &" . org-mark-ring-goto)))

(use-package yasnippet-snippets
  :defer)

(use-package ivy-yasnippet
  :bind
  (("C-c y" . ivy-yasnippet)))

Programming languages

Common settings for programming modes

For modes using company for tab completion add

(use-package company
  :init
  (setq company-idle-delay nil  ; avoid auto completion popup, use TAB
                                ; to show it
        company-async-timeout 15        ; completion may be slow
        company-tooltip-align-annotations t)
  :hook (after-init . global-company-mode)
  :bind
  (:map prog-mode-map
        ("C-i" . company-indent-or-complete-common)
        ("C-M-i" . counsel-company)))

For modes that also use Language Server Protocol from lsp-mode add

(defun my-lsp-format-buffer ()
  (if (eq major-mode 'go-mode)
      (lsp-format-buffer)))

(defun my-lsp-organize-imports ()
  (if (eq major-mode 'go-mode)
      (lsp-organize-imports)))

(use-package lsp-mode
  :commands lsp
  :init
  (setq lsp-keymap-prefix "H-l")
  :config
  (lsp-enable-which-key-integration t)
  ;; reformat Go code and add missing (or remove old) imports
  :hook ((before-save . my-lsp-format-buffer)
         (before-save . my-lsp-organize-imports))
  :bind (("C-c e n" . flymake-goto-next-error)
         ("C-c e p" . flymake-goto-prev-error)
         ("C-c e r" . lsp-find-references)
         ("C-c e R" . lsp-rename)
         ("C-c e i" . lsp-find-implementation)
         ("C-c e t" . lsp-find-type-definition)))

(use-package lsp-ui
  :init
  (setq lsp-ui-doc-enable nil
        lsp-ui-sideline-delay 5.0)
  :bind (("C-c d" . lsp-ui-doc-show)
         ("C-c i" . lsp-ui-imenu)))

Compilation

(global-set-key "\C-ck" #'compile)

C and C++

The following needs clangd to be in you PATH. You also need to provide compiler flags. You may also look at available keybindings (I use H-l instead of s-l as the prefix).

For tab completion and lsp support also add my common settings for programming modes.

(setq-default c-basic-offset 8)

(defun my-c-c++-mode-hook-fn ()
  (lsp)
  (lsp-headerline-breadcrumb-mode)
  (smartparens-mode)
  (local-set-key (kbd "<tab>") #'company-indent-or-complete-common))

(add-hook 'c-mode-hook #'my-c-c++-mode-hook-fn)
(add-hook 'c++-mode-hook #'my-c-c++-mode-hook-fn)

Lisp and Emacs lisp

;; in emacs 25.1: M-. runs xref-find-definitions,  M-, jumps back
(global-set-key (kbd "C-c e l") #'find-library)

(setq slime-lisp-implementations '((sbcl ("sbcl")))
      slime-default-lisp 'sbcl
      slime-contribs '(slime-fancy))

(let ((path "/usr/local/share/doc/HyperSpec/"))
  (if (file-accessible-directory-p path)
      (setq common-lisp-hyperspec-root (concat "file://" path))))

(use-package lispy
  :hook (eval-expression-minibuffer-setup . lispy-mode))

(use-package paren-face
  :defer)

(defun my-emacs-lisp-mode-hook-fn ()
  (set (make-local-variable 'lisp-indent-function) #'lisp-indent-function)
  (lispy-mode 1)
  (local-set-key (kbd "C-c S") (global-key-binding (kbd "M-s")))
  (local-set-key (kbd "C-c C-z")
                 (lambda () (interactive) (switch-to-buffer "*scratch*")))
  (local-set-key (kbd "C-M-i") #'counsel-company)
  (show-paren-mode 1)
  (paren-face-mode))

(use-package slime-company
  :defer)

(use-package slime
  :demand
  :config
  (slime-setup '(slime-fancy slime-company slime-cl-indent)))

(defun my-lisp-mode-hook-fn ()
  (set (make-local-variable 'lisp-indent-function)
       #'common-lisp-indent-function)
  (lispy-mode 1)
  (local-set-key (kbd "C-c S") (global-key-binding (kbd "M-s")))
  (show-paren-mode 1)
  (paren-face-mode)
  (set (make-local-variable 'company-backends) '(company-slime))
  (company-mode)
  (local-set-key "\C-i" #'company-indent-or-complete-common))

(add-hook 'emacs-lisp-mode-hook #'my-emacs-lisp-mode-hook-fn)
(add-hook 'lisp-mode-hook #'my-lisp-mode-hook-fn)

JavaScript

(setq js-indent-level 8)

Go (golang)

This is my new Go setup (partially) based on gopls (which is still in alpha stage) and may not work for you if that is the case try my old Go setup (below). gopls supports Go modules outside of GOPATH (some Go tools, for example guru does not).

I use the following setup for the go-mode in my ~/.emacs.d/init.el. This adds syntax highlighting but without fontifing names of called functions, autocompletion and info on called function in mode line, auto formatting of the code on save with adding of missing imports.

It is quite long as I define two interactive functions:

  1. my-go-electric-brace which is bind to { key and inserts an indented pair of braces (if previous character is a space, otherwise it inserts single opening brace),

  2. my-godoc-package which is bind to C-c P key and display documentation for a package choosen from a list of installed packages.

For tab completion and lsp support also add my common settings for programming modes.

(defun my-go-electric-brace ()
  "Insert an opening brace may be with the closing one.
If there is a space before the brace also adds new line with
properly indented closing brace and moves cursor to another line
inserted between the braces between the braces."
  (interactive)
  (insert "{")
  (when (looking-back " {")
    (newline)
    (indent-according-to-mode)
    (save-excursion
      (newline)
      (insert "}")
      (indent-according-to-mode))))

(defun my-godoc-package ()
  "Display godoc for given package (with completion)."
  (interactive)
  (godoc (ivy-read "Package: " (go-packages) :require-match t)))

(use-package go-guru
  :after go-mode)

(use-package go-mode
  :init
  (setq go-fontify-function-calls nil)  ; fontifing names of called
                                        ; functions is too much for me
  :bind
  (:map go-mode-map
        ("C-c e g" . godoc)
        ("C-c P" . my-godoc-package)
        ("{" . my-go-electric-brace))
  :hook ((go-mode . lsp)
         (go-mode . smartparens-mode)))

;; Go/speedbar integration

(eval-after-load 'speedbar
  '(speedbar-add-supported-extension ".go"))

Now, in go buffers you can use M-. to jump to the definition of the identifier at point (use M-, to jump back as for normal tags in Emacs 25.1) and you can also use C-c C-d for a short description of the identifier at point (actually it is constantly displayed in the mode line by enabled lsp support). You can use C-c d for a longer description of the identifier at point.

For this to work you have to

  1. After adding above to your emacs config file see how to install from MELPA all required packages. Or just install go-mode, go-guru.

  2. Install Go compiler. Under Debian you may install golang package (but in Debian 10 Buster it is 1.11 compared to the current 1.14, so you may consider downloading the current version of Go). Otherwise search for the package for your system or see Getting started.

  3. Install gopls with (but in your home directory, not inside some Go module)

    $ GO111MODULE=on go get golang.org/x/tools/gopls@latest
    
  4. Install guru with

    $ go get -u golang.org/x/tools/cmd/guru
    
  5. Add your $GOPATH/bin to your PATH environment variable (or copy the gopls and guru executables from $GOPATH/bin to some directory which is in your PATH).

See also Go, pls stop breaking my editor - GopherCon SG 2019 and Writing Go in Emacs for more info.

Old Go setup

This is my old Go setup, you may try the new setup (above) based (partially) on gopls if the new one does not work for you try this one.

I used the following setup for the go-mode in my ~/.emacs.d/init.el. This adds syntax highlighting but without fontifing names of called functions, autocompletion and eldoc support, auto formatting of the code on save with adding of missing imports (goimports).

It is quite long as I define two interactive functions:

  1. my-go-electric-brace which is bind to { key and inserts an indented pair of braces (if previous character is a space, otherwise it inserts single opening brace),

  2. my-godoc-package which is bind to C-c P key and display documentation for a package choosen from a list of installed packages.

(defun my-go-electric-brace ()
  "Insert an opening brace may be with the closing one.
If there is a space before the brace also adds new line with
properly indented closing brace and moves cursor to another line
inserted between the braces between the braces."
  (interactive)
  (insert "{")
  (when (looking-back " {")
    (newline)
    (indent-according-to-mode)
    (save-excursion
      (newline)
      (insert "}")
      (indent-according-to-mode))))

(defun my-godoc-package ()
  "Display godoc for given package (with completion)."
  (interactive)
  (godoc (ivy-read "Package: " (go-packages) :require-match t)))

(use-package flycheck
  :defer)

(use-package go-eldoc
  :defer)

(use-package company-go
  :defer)

(use-package go-guru
  :after go-mode)

(use-package go-mode
  :init
  (setq gofmt-command "goimports"     ; use goimports instead of gofmt
        go-fontify-function-calls nil ; fontifing names of called
                                      ; functions is too much for me
        company-idle-delay nil) ; avoid auto completion popup, use TAB
                                ; to show it
  :bind
  (:map go-mode-map
   ("M-." . go-guru-definition)
   ("C-c d" . godoc-at-point)
   ("C-c g" . godoc)
   ("C-c h" . go-guru-hl-identifier)
   ("C-c P" . my-godoc-package)
   ("{" . my-go-electric-brace)
   ("C-i" . company-indent-or-complete-common)
   ("C-M-i" . company-indent-or-complete-common))
  :config
  ;; run gofmt/goimports when saving the file
  (add-hook 'before-save-hook #'gofmt-before-save)

  (defun my-go-mode-hook-fn ()
    (go-eldoc-setup)
    (set (make-local-variable 'company-backends) '(company-go))
    (company-mode)
    (smartparens-mode 1)
    (flycheck-mode 1)
    (setq imenu-generic-expression
          '(("type" "^type *\\([^ \t\n\r\f]*\\)" 1)
            ("func" "^func *\\(.*\\) {" 1))))

  (add-hook 'go-mode-hook #'my-go-mode-hook-fn))

;; Go/speedbar integration

(eval-after-load 'speedbar
  '(speedbar-add-supported-extension ".go"))

Now, in go buffers you can use M-. to jump to the definition of the identifier at point (use M-, to jump back as for normal tags in Emacs 25.1) and you can also use C-c C-d for a short description of the identifier at point (actually it is constantly displayed in the mode line by enabled Go eldoc support). You can use C-c d for a longer description of the identifier at point.

For this to work you have to

  1. Install from MELPA the following Emacs packages: go-mode, company-go, go-eldoc, and go-guru.

  2. Install Go compiler. Under Debian you may install golang package (but in Debian 10 Buster it is 1.11 compared to the current 1.14, so you may consider downloading the current version of Go). Otherwise search for the package for your system or see Getting started.

  3. Install godef with

    $ go get github.com/rogpeppe/godef
    
  4. Install goimports which can be installed from Debian package golang-go.tools or with

    $ go get golang.org/x/tools/cmd/goimports
    
  5. Install gocode (a fork with Go modules support) which can be installed with

    $ go get -u github.com/stamblerre/gocode
    
  6. Install guru with

    $ go get golang.org/x/tools/cmd/guru
    
  7. Add your $GOPATH/bin to your PATH environment variable (or copy the godef, goimports, gocode, and guru executables from $GOPATH/bin to some directory which is in your PATH).

See also Writing Go in Emacs for more info.

D (Dlang)

Install dcd and dtools (for rdmd) either by following instructions on previous two links or on Arch Linux you can also run

$ sudo pacman -S dcd dtools

And install serve-d (but check the link for newer version) with

$ dub fetch serve-d@0.7.0-beta.5
$ dub run serve-d --build=release

For tab completion and lsp support also add my common settings for programming modes.

(use-package lsp-mode
  :commands lsp
  :config
  (add-to-list 'lsp-language-id-configuration '(vala-mode . "D"))
  (lsp-register-client
   (make-lsp-client :new-connection (lsp-stdio-connection '("serve-d"))
                    :major-modes '(d-mode)
                    :server-id 'serve-d)))

(defun my-d-mode-hook-fn ()
  (setq c-basic-offset 4
        indent-tabs-mode nil)
  (company-mode 1)
  (local-set-key "\C-i" #'company-indent-or-complete-common)
  (lsp 1))

(use-package d-mode
  :hook (d-mode . my-d-mode-hook-fn))

Python

(use-package company-jedi
  :defer)

(defun my-python-mode-hook-fn ()
  (set (make-local-variable 'company-backends) '(company-jedi))
  (company-mode)
  (smartparens-mode 1)
  (local-set-key (kbd "M-.") #'jedi:goto-definition)
  (local-set-key (kbd "M-,") #'jedi:goto-definition-pop-marker)
  (local-set-key "\C-i" #'company-indent-or-complete-common))

(add-hook 'python-mode-hook #'my-python-mode-hook-fn)

Rust

If you have Emacs 26 than you need Rustup and using Rustup you should install components rls and rust-src with the command

$ rustup component add rls rust-src

For tab completion and lsp support add my common settings for programming modes and then add

(when (>= emacs-major-version 26)
  (defun my-rustic-mode-hook-fn ()
    "needed for lsp-format-buffer to indent with 4 spaces"
    (setq tab-width 4
          indent-tabs-mode nil))

  (use-package rustic
    :init
    ;; to use rustic-mode even if rust-mode also installed
    (setq auto-mode-alist (delete '("\\.rs\\'" . rust-mode) auto-mode-alist))
    :hook (rustic-mode . my-rustic-mode-hook-fn)))

But if you have Emacs older than 26 than you should install Rustup and racer and with Rustup install component rust-src with the command

$ rustup component add rust-src

for tab completion add my common settings for programming modes and then add

(when (< emacs-major-version 26)
  (use-package cargo
    :defer)

  (use-package racer
    :defer)

  (use-package rust-mode
    :init
    (setq rust-format-on-save t)
    :hook ((rust-mode . company-mode)
           (rust-mode . cargo-minor-mode)
           (rust-mode . racer-mode)
           (rust-mode . eldoc-mode))))

Meson build system

(use-package meson-mode
  :defer
  :init
  (setq meson-indent-basic 4))

To use cmake-ide with Meson build system see.

Vala

You need to install vala-language-server.

For tab completion and lsp support also add my common settings for programming modes.

(use-package lsp-mode
  :commands lsp
  :config
  (add-to-list 'lsp-language-id-configuration '(vala-mode . "vala"))
  (lsp-register-client
   (make-lsp-client :new-connection (lsp-stdio-connection
                                     '("vala-language-server"))
                    :major-modes '(vala-mode)
                    :server-id 'vala-ls)))

(defun my-vala-mode-hook-fn ()
  (setq c-basic-offset 4
        tab-width 8
        indent-tabs-mode nil)
  (company-mode 1)
  (local-set-key "\C-i" #'company-indent-or-complete-common)
  (lsp 1))

(use-package vala-mode
  :hook (vala-mode . my-vala-mode-hook-fn))

Dart

NOTE: pub and dart must be in PATH for lsp to start in dart-mode.

For tab completion and lsp support also add my common settings for programming modes.

(use-package dart-mode
  :init
  (setq lsp-dart-analysis-sdk-dir "~/local/flutter/bin/cache/dart-sdk/")
  :hook ((dart-mode . smartparens-mode)
         (dart-mode . lsp)))

(use-package flutter
  :after dart-mode
  :init
  (setq flutter-sdk-path "~/local/flutter/")
  :bind
  (:map dart-mode-map
        ("C-M-x" . #'flutter-run-or-hot-reload)))

PHP

(use-package php-mode
  :defer)

(use-package company-php
  :defer)

(defun my-php-mode-hook-fn()
  (when (require 'company-php nil t)
    (set (make-local-variable 'company-backends)
         '(company-ac-php-backend))
    (company-mode t)
    (local-set-key (kbd "M-.") #'ac-php-find-symbol-at-point)))

(add-hook 'php-mode-hook #'my-php-mode-hook-fn)

TypeScript

(defun my-setup-tide-mode ()
  (tide-setup)
  (flycheck-mode 1)
  (setq flycheck-check-syntax-automatically '(save mode-enabled))
  (eldoc-mode 1)
  (company-mode 1))

(use-package typescript-mode
  :init
  (setq typescript-indent-level 2)
  :bind
  (:map typescript-mode-map
   ("C-i" . company-indent-or-complete-common)
   ("C-M-i" . company-indent-or-complete-common)))

(use-package tide
  :after typescript-mode
  :hook ((before-save . tide-format-before-save)
         (typescript-mode . my-setup-tide-mode)))

(use-package ng2-mode
  :defer)

Other modes

Web mode

(defun my-web-mode-hook-fn()
  (cond
   ((string= web-mode-engine "php")
    (my-php-mode-hook-fn))))

(use-package web-mode
  :init
  (add-hook 'web-mode-hook #'my-web-mode-hook-fn)
  (add-to-list 'auto-mode-alist '("\\.php\\'" . web-mode))
  :bind
  (:map web-mode-map
        ("C-i" . company-indent-or-complete-common)))

CSS

(use-package rainbow-mode
  :hook (css-mode . rainbow-mode))

Org mode

(use-package org
  :init
  (setq org-default-notes-file "~/org/notes.org"
        org-highlight-latex-and-related '(latex)
        org-hide-leading-stars t
        org-ellipsis "…"
        org-catch-invisible-edits 'smart
        gnuplot-program "pyxplot")
  (defun my-org-timer-done ()
    (shell-command "echo Timer timed out; date &"))
  :bind
  (("C-c A" . org-agenda)
   ("C-c B" . org-switchb)
   ("C-c c" . org-capture)
   ("C-c l" . org-store-link))
  :hook ((org-timer-done . my-org-timer-done))
  :config
  (require 'ox-beamer))

Search engines

(use-package engine-mode
  :config
  (engine-mode t)
  (defengine duckduckgo
    "https://duckduckgo.com/?q=%s"
    :keybinding "d")
  (defengine google
    "http://www.google.com/search?ie=utf-8&oe=utf-8&q=%s"
    :keybinding "g"))

WWW browsers

(setq my-browsers
      '(("Firefox" . browse-url-firefox)
        ("Chromium" . browse-url-chromium)
        ("EWW" . eww-browse-url)))

(defun my-browse-url (&rest args)
  "Select the prefered browser from a menu before opening the URL."
  (interactive)
  (let ((browser (ivy-read "WWW browser: " my-browsers :require-match t)))
    (apply (cdr (assoc browser my-browsers)) args)))

(setq browse-url-browser-function #'my-browse-url)

(defun my-eww-scale-adjust ()
  "Slightly bigger font but text shorter than text."
  (interactive)
  (text-scale-adjust 0)
  (text-scale-adjust 1)
  (eww-toggle-fonts)
  (split-window-right)
  (eww-toggle-fonts)
  (other-window 1)
  (sleep-for 1)
  (delete-window))

Mail

(use-package notmuch
  :defer
  :init
  (setq notmuch-search-oldest-first nil))

API documentation

(when (require 'devdocs-lookup nil t)
  (devdocs-setup))

Appearance and custom file

Minimalistic look

(setq inhibit-startup-screen t
      frame-title-format "%b"
      ediff-window-setup-function #'ediff-setup-windows-plain)
(add-to-list 'global-mode-string
             `(t (,(user-login-name) "@" system-name " ")) t)
(set-scroll-bar-mode 'right)
(menu-bar-mode 0)
(tool-bar-mode 0)
(scroll-bar-mode 0)

Easy switching between some fonts

(setq my-font-list
      '("Fantasque Sans Mono" "Fira Code Light" "Go mono" "IBM 3270"
        "Inconsolata" "Iosevka Light" "Monofur" "Monoid" "mononoki"))

(defun my-set-frame-font (font-name size &optional frames)
  "Set font to one of the fonts from `my-font-list'
Argument FRAMES has the same meaning as for `set-frame-font'"
  (interactive
   (list (ivy-read "Font name: " my-font-list)
         (read-number "Font size: ")))
  (set-frame-font
   (format "%s:pixelsize=%d:antialias=true:autohint=true" font-name size)
   nil frames))

(global-set-key (kbd "C-c F") #'my-set-frame-font)

Toggle between dark and light themes with a key

Toggle between my own dark and light themes if available in ~/.emacs.d/lupan-themes otherwise falls back to another themes.

(let ((path (expand-file-name "~/.emacs.d/lupan-themes")))
  (when (file-accessible-directory-p path)
    (add-to-list 'load-path path t)))

(if (require 'lupan-themes nil t)
    (setq my-dark-theme 'lupan-dark
          my-light-theme 'lupan-light)
  (setq my-dark-theme 'apropospriate-dark
        my-light-theme 'apropospriate-light))

(defun my-select-theme (theme)
  (mapc #'disable-theme custom-enabled-themes)
  (load-theme theme t))

(defun my-toggle-theme ()
  "Toggle between dark and light themes"
  (interactive)
  (let ((dark-p (custom-theme-enabled-p my-dark-theme)))
    (my-select-theme (if dark-p my-light-theme my-dark-theme))))

(global-set-key (kbd "C-S-<f6>") #'my-toggle-theme)

(use-package apropospriate-theme
  :defer)

(if (or (require 'lupan-themes nil t)
        (require 'apropospriate-theme nil t))
    (my-toggle-theme))

(defun my-frame-setup-fn (&optional frame)
  (unless (display-graphic-p frame)
    (set-face-background 'default "unspecified-bg"
                         (or frame (selected-frame)))))

(add-hook 'after-make-frame-functions #'my-frame-setup-fn)
(add-hook 'window-setup-hook #'my-frame-setup-fn)

Hyper bindings

(dolist (k '(("H-1" . "C-x 1")
             ("H-2" . "C-x 2")
             ("H-3" . "C-x 3")
             ("H-0" . "C-x 0")
             ("H-b" . "C-x b")
             ("H-g" . "C-x g")
             ("H-k" . "C-x k")
             ("H-o" . "C-x o")))
  (global-set-key (kbd (car k))
                  (lookup-key (current-global-map) (kbd (cdr k)))))

Use separate custom file

(setq custom-file "~/.emacs.d/custom.el")
(if (file-exists-p custom-file)
    (load custom-file))