Emacs

This article introduces emacs, and provides you a handy .emacs config for you to start.

Emacs is probably my favourite development tool. It can be used for any kind of text editing, including writing code, scripts, and configs. Emacs is backed up by lisp, making it extremely powerful and extensible.

However, the learning curve of emacs is just steep. It requires a lot of efforts before you can use it skillfully, and most people just give up before that happens.

This article aims at helping you get started with emacs. It talks about all the key concepts in emacs, and provides you a startup config for you to get started easily.

Why Emacs?

You might want to ask, there are many awesome text editing tools out there in 2020s (e.g., VSCode, sublime text, atom), why are we still using emacs in 2020s? This is a good question, and there are a couple of reasons:

  • Mouseless: The number one reason probably is mouseless. Emacs can run within terminal, and all of your coding work can be done via keyboard without the need of a mouse. If you set up your hotkeys well, you can use emacs pretty efficient.
  • Run on server: Emacs can run on the server side within terminal. This is super handy. When you are doing remote Linux development work. It is nice to run your text editor there, since all the text editing happens there.
  • Powerful enough: Emacs is backed up by lisp, it is powerful enough for you to do your daily work.
  • Fast: Emacs is faster compared to a lot of the heavy weight IDEs.

Overall, emacs gives you pretty good efficiency on Linux, if you are skillful at it.

Key Concepts

Although the initial version of emacs was released in 1985, the system design of emacs is actually pretty advanced, even from today’s perspective. The following sub-sections talk about some of the key concepts.

Lisp

The overall design of emacs can be broken down in two parts.

  • the emacs application: it provides a minimum text editor without a lot of complicated features, but it can run as a separate window or within a terminal.
  • the backend scripting language (lisp): emacs uses a lisp as its backend scripting language. Emacs has a lot of built-in lisp features in it, and developers can also build their own features as well.

Since lisp is a scripting language, this design makes emacs really extensible. Anyone who wants to develop a new text editing feature, they can do it without the need of recompiling emacs, which is really awesome.

This scripting langauge is also used as emacs configuration system. Your emacs startup config is also written in lisp.

This design is simple, but extremely effective. Very impressive to see this happened almost 35+ years ago.

Buffer

Buffer is an important concept of emacs. Basically, a buffer is the internal data structure holding the text that you want to edit. You can create as many buffers as you want in emacs. Some of the buffers can be associated with files on disk, others can simply be in-memory buffers.

Window

Window is basically a box area in your emacs UI. Same as buffer, you can also create as many windows as you want (as long as your screen is large enough). A window can be splitted vertically or horizontally.

Each window is associated with a buffer. The buffer content will be displayed by the window. The association of buffer and window can change at any time.

Frame

As said earlier, emacs can run in GUI mode or in terminal mode. If you run emacs in GUI mode, an OS window (not emacs window) is going to be created for emacs, and that is called an emacs frame. Technically, a single emacs program can open multiple frames. If you run emacs in terminal, you will only get a single frame.

By default, there is one window in a frame.

It is recommended to use and learn emacs in terminal, since you are going to be doing a lot of Linux coding. And if you want to code on a remote Linux, you can’t open up a window through a terminal (unless you use X forwarding, which works, but is not the best choice). Besides, emacs in terminal is faster, since it doesn’t need to handle the text alignmend issue (caused by texts with different width and images in a buffer) in GUI mode.

Modes

Mode is also a very important concept in emacs.

In emacs, each buffer can have one major mode, and a couple of optional minor modes. Major mode decides how your content in buffer if interpreted. For example, if you are in python-mode, it’s going to treat your content in buffer as python code, and tries to do python highlighting for you. Minor modes are optional. It can alter the behavior of emacs in certain way.

Emacs determines major modes based on your file name extensions. For example, a .cpp file will be loaded in c-mode, and .py file will be loaded in python-mode. You can also change the mode later if you prefer.

Mode Line

On the bottom of each window, there is a status line presenting all related statuses of its associated buffer, e.g., its major/minor mode, its filename (if there is any).

Minibuffer

On the bottom of the frame, there is a minibuffer. It’s also a buffer, but a special one. The purpose of the minibuffer is to interact with users. For example, users can type in emacs commands (which are basically interactive lisp functions) in it, and it will also present messages back to us (if there is any).

Mark And Region

Block editing is a very common use case in text editing. In emacs, you can mark a position, and then move your cursor to another one, then a region is defined, which is just the area between the mark and your current cursor position.

It is pretty much the same as you are doing shift + arrow to select a block in any other text editors. In emacs, you can also do that, which behaves the same as you set the mark for the other end.

Undo And Redo

In traditional editors, undo works like a stack. If you undo, you pop up an element from the undo stack. In emacs, undo is a little bit different. Undo is just treated as another operation, which is also append to the undo list for later use. So theorectically, you can undo infinitely.

I hope the above concepts are going to give you a high level understanding of emacs, to really get to started, you’ll need a startup config, because emacs default setup is just weird to use. The recommended config goes at the bottom of this section, which includes the following changes:

  • Enable cua-mode for ctrl-x, ctrl-c, ctrl-v style cut/copy/paste.
  • Ido-mode for completion in minibuffer.
  • A blue style color scheme.
  • Move window support (alt + arrow).
  • Many key bindings.
  • OSC 52 (which allows you to copy text from your emacs to your client OS, this works even you are in a remote emacs).
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Basic setup
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(setq auto-save-default nil)

(cua-mode t)
(setq cua-keep-region-after-copy t)

(setq ido-enable-flex-matching t)
(setq ido-everywhere t)
(ido-mode t)

(menu-bar-mode -1)

(setq-default tab-width 4)
(setq tab-width 4)
(show-paren-mode t)

(setq make-backup-files nil)

(setq mode-require-final-newline nil)

(defalias 'yes-or-no-p 'y-or-n-p)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Color scheme
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(set-background-color "#000000")
(set-foreground-color "#b0b0b0")
(set-mouse-color "#000000")
(set-cursor-color "#d4d4d4")
(set-border-color "#000000")

(custom-set-faces
 ;; custom-set-faces was added by Custom.
 ;; If you edit it by hand, you could mess it up, so be careful.
 ;; Your init file should contain only one such instance.
 ;; If there is more than one, they won't work right.
 '(antlr-keyword ((t (:foreground "#808080" :weight bold))))
 '(antlr-ruledef ((t (:foreground "#00cd00" :weight bold))))
 '(antlr-ruleref ((t (:foreground "#875f00" :weight bold))))
 '(diff-added ((t (:background "#262626" :foreground "#87ff00"))))
 '(diff-context ((t (:foreground "#8a8a8a"))))
 '(diff-file-header ((t (:background "#121212" :foreground "#ffaf00"))))
 '(diff-header ((t (:background "#262626" :foreground "#ff5f00"))))
 '(diff-removed ((t (:background "#262626" :foreground "#ff0000"))))
 '(error ((t (:foreground "#cd0000" :weight bold))))
 '(font-lock-builtin-face ((t (:foreground "#ffa500"))))
 '(font-lock-comment-face ((t (:foreground "#c08040"))))
 '(font-lock-constant-face ((t (:foreground "#00cd00"))))
 '(font-lock-function-name-face ((t (:foreground "#4186be"))))
 '(font-lock-string-face ((t (:foreground "#cdcd00"))))
 '(font-lock-type-face ((t (:foreground "#ff7f50"))))
 '(font-lock-variable-name-face ((t (:foreground "#ffffff" :weight bold))))
 '(ido-first-match ((t (:foreground "#00ffff"))))
 '(ido-only-match ((t (:foreground "#00ffff"))))
 '(ido-subdir ((t (:foreground "#2080ff"))))
 '(isearch-fail ((t (:background "#540000"))))
 '(linum ((t (:background "#121212" :foreground "#478FB3"))))
 '(match ((t (:foreground "#d70000"))))
 '(minibuffer-prompt ((t (:foreground "#87d7ff"))))
 '(mode-line ((t (:background "#0a2533" :foreground "#33bbff"))))
 '(mode-line-highlight ((t (:background "#0a2533"))))
 '(mode-line-inactive ((t (:inherit mode-line :background "#05131a" :foreground "#2483b3" :weight light))))
 '(org-table ((t (:foreground "#0000ee"))))
 '(region ((t (:background "#303030"))))
 '(vertical-border ((t (:background "#202020" :foreground "#4DC4FF" :weight bold))))
 '(web-mode-block-face ((t (:background "#121212"))))
 '(whitespace-space ((((class color) (background dark)) (:background "none" :foreground "#666666")) (((class color) (background light)) (:background "#1c1c1c" :foreground "#585858")) (t (:inverse-video t)))))


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Escape code decoder
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(add-hook 'terminal-init-xterm-hook
          (lambda ()
            (define-key input-decode-map "\e[48;5u" [?\C-0])
            (define-key input-decode-map "\e[49;5u" [?\C-1])
            (define-key input-decode-map "\e[50;5u" [?\C-2])
            (define-key input-decode-map "\e[51;5u" [?\C-3])
            (define-key input-decode-map "\e[52;5u" [?\C-4])
            (define-key input-decode-map "\e[53;5u" [?\C-5])
            (define-key input-decode-map "\e[54;5u" [?\C-6])
            (define-key input-decode-map "\e[55;5u" [?\C-7])
            (define-key input-decode-map "\e[56;5u" [?\C-8])
            (define-key input-decode-map "\e[57;5u" [?\C-9])

            (define-key input-decode-map "\e[38;6u" [?\C-&])
            (define-key input-decode-map "\e[42;6u" [?\C-*])
            (define-key input-decode-map "\e[60;6u" [?\C-<])
            (define-key input-decode-map "\e[62;6u" [?\C->])

            (define-key input-decode-map "\e[127;5u" [C-backspace])))


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Move Window Support.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(require 'windmove)

(defun swap-buffer (other-win)
  (if (or (null other-win)
            (string-match "^ \\*Minibuf" (buffer-name (window-buffer other-win))))
      (error "Cannot move window.")
    (let ((curr-window-buffer (window-buffer (selected-window))))
      (set-window-buffer (selected-window) (window-buffer other-win))
      (set-window-buffer other-win curr-window-buffer)
      (select-window other-win))))

(defun move-buffer-up ()
  (interactive)
  (swap-buffer (windmove-find-other-window 'up)))

(defun move-buffer-down ()
  (interactive)
  (swap-buffer (windmove-find-other-window 'down)))

(defun move-buffer-left ()
  (interactive)
  (swap-buffer (windmove-find-other-window 'left)))

(defun move-buffer-right ()
  (interactive)
  (swap-buffer (windmove-find-other-window 'right)))


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Delete Support.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defun hungry-delete-backward()
  (interactive)
  (delete-region
   (point)
   (save-excursion
     (skip-chars-backward " \t\n")
     (point))))

(defun hungry-delete-forward()
  (interactive)
  (delete-region
   (point)
   (save-excursion
     (skip-chars-forward " \t\n")
     (point))))

(defun backward-delete-char-or-space()
  (interactive)
  (let ((ch (make-string 1 (preceding-char))))
    (if (or (string= ch " ") (string= ch "\t") (string= ch "\n"))
        (hungry-delete-backward)
      (backward-kill-word 1))))

(defun forward-delete-char-or-space()
  (interactive)
  (let ((ch (make-string 1 (following-char))))
    (if (or (string= ch " ") (string= ch "\t") (string= ch "\n"))
        (hungry-delete-forward)
      (kill-word 1))))


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Key bindings
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(global-set-key (kbd "RET") 'newline-and-indent)

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

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

(global-set-key (kbd "<M-S-up>") 'move-buffer-up)
(global-set-key (kbd "<M-S-down>") 'move-buffer-down)
(global-set-key (kbd "<M-S-left>") 'move-buffer-left)
(global-set-key (kbd "<M-S-right>") 'move-buffer-right)

(global-set-key (kbd "C-0") 'delete-window)
(global-set-key (kbd "C-1") 'delete-other-windows)
(global-set-key (kbd "C-2") 'split-window-vertically)
(global-set-key (kbd "C-3") 'split-window-horizontally)

(global-set-key (kbd "<C-kp-0>") 'delete-window)
(global-set-key (kbd "<C-kp-1>") 'delete-other-windows)
(global-set-key (kbd "<C-kp-2>") 'split-window-vertically)
(global-set-key (kbd "<C-kp-3>") 'split-window-horizontally)

(global-set-key (kbd "<C-backspace>") 'backward-delete-char-or-space)
(global-set-key (kbd "<C-delete>") 'forward-delete-char-or-space)

(global-set-key (kbd "<select>") 'end-of-line)

(global-set-key (kbd "C-d") 'kill-whole-line)

(global-unset-key (kbd "C-k"))
(global-set-key (kbd "C-k C-c") 'comment-region)
(global-set-key (kbd "C-k C-u") 'uncomment-region)

(global-set-key (kbd "C-;") 'whitespace-mode)
(global-set-key (kbd "C-'") 'delete-trailing-whitespace)



;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Clipboard
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(defun write-to-remote-clipboard-via-osc-52 (text)
  "Write from the remote clipboard via osc 52."
  (send-string-to-terminal (format "\e]52;c;%s\07" (base64-encode-string text))))

(setq interprogram-cut-function 'write-to-remote-clipboard-via-osc-52)
(setq interprogram-paste-function nil)
(custom-set-variables
 ;; custom-set-variables was added by Custom.
 ;; If you edit it by hand, you could mess it up, so be careful.
 ;; Your init file should contain only one such instance.
 ;; If there is more than one, they won't work right.
 '(package-selected-packages (quote (multiple-cursors))))