Untangling Emacs

Abhishek Amralkar
6 min readNov 10, 2022

--

Photo by Karim MANJRA on Unsplash

Emacs

Emacs don't need any introduction. It’s in development since its inception and that was 47 years ago. I am using Emacs since last 7 years. I keep my all Emacs config in a single file and after 7 years I feel that its unmanageable.

Emacs is highly customisable and many Emacs user keeps their Emacs config in the source code. There are many many ways to manage the Emacs config and one to which I am leaning towards is managing the Emacs config using the org-mode as a Literate Programming.

Below is my Emacs config repository

Literate Programming

Literate Programming was invented by Donald Knuth. About Literate Programming you can read more here.

Basics of Org Mode and E-Lisp

Org Mode support many many languages and its highly flexible. To use org-mode just create file with .orgextenstion.

Source Code Block in Org-Mode

emacs-lisp is just an example, it can be any language like ruby, python etc etc

#+begin_src emacs-lisp
(defun org-babel-tangle-config()
(when (string-equal (buffer-file-name)
(expand-file-name "~/.emacs.d/emacs.org"))
(let ((org-config-babel-evaluate nil))
(org-babel-tangle))))
#+end_src

To create a code block there is a short cut too just type `<s + TAB`, but in newer version you may need to enable the org-tempo mode explicitly.

(require 'org-tempo)

Use C-c C-c to execute the command and you will see the result just below code block, something like this

#+RESULTS:
| #[0 \301\211\207 [imenu-create-index-function org-imenu-get-tree] 2] | org-bullets-mode | (lambda nil (display-line-numbers-mode 0)) | org-tempo-setup | #[0 \300\301\302\303\304$\207 [add-hook change-major-mode-hook org-show-all append local] 5] | #[0 \300\301\302\303\304$\207 [add-hook change-major-mode-hook org-babel-show-result-all append local] 5] | org-babel-result-hide-spec | org-babel-hide-all-hashes | (lambda nil (add-hook 'after-save-hook #'org-babel-tangle-config)) |

C-c ’ to edit the block in the language’s mode for example python. First lets load Python support in org-mode and we can keep adding languages to list.

#+begin_src emacs-lisp
(org-babel-do-load-languages
'org-babel-load-languages
'((python . t)))
#+end_src

After this we can execute Python code

#+begin_src python
i = 42
return f"The number is {i}."
#+end_src
To execute use `C-c C-c`#+RESULTS:
: The number is 42.

To move to next block in your org file with C-c M-f and back C-c M-b

E-LISP

  • Set Variable
(setq language "emacs-lisp")
  • Conditionals

If 35 == 35 print “I am number 35” else “I am not number 35”

#+begin_src emacs-lisp
(if (eq 35 35)
(princ "I am number 35")
(princ "I am not number 35"))
#+end_src
#+RESULTS:
: I am number 35

Or Compare strings

#+begin_src emacs-lisp
(setq language "emacs-lisp")
(if (string= language "emacs-lisp")
(princ "Yes its emacs-lisp")
(princ "FooBar"))
#+end_src
#+RESULTS:
: Yes its emacs-lisp
  • Function
#+begin_src emacs-lisp
(setq language "emacs-lisp")

(defun whichlanguage()
(insert "I am " language))
(whichlanguage)
#+end_src
#+RESULTS:
: I am emacs-lisp
  • List
#+begin_src emacs-lisp
(setq languages '("Python" "Clojure" "Golang"))
#+end_src

To get first element from list

#+begin_src emacs-lisp
(setq languages '("Python" "Clojure" "Golang"))
(car languages)
#+end_src
#+RESULTS:
: Python

To get whole list except the first element

#+begin_src emacs-lisp
(setq languages '("Python" "Clojure" "Golang"))
(cdr languages)
#+end_src
#+RESULTS:
| Clojure | Golang |
  • Set Key-Bindings
#+begin_src emacs-lisp
(global-set-key (kbd "C-l C-l") 'languages)
#+end_src

Emacs Config

By default raw Emacs UI is ugly at-least for me and too much cluttered, but we can fix that and that why Emacs is so powerful

  • Default Emacs Screen

This is how my Emacs looks on startup

How?

  • Make Emacs UI Elegant and beautiful
#+begin_src emacs-lisp
(setq inhibit-startup-message 1)
(set-buffer-modified-p 1)
(tool-bar-mode 0)
(tooltip-mode 0)
(fset 'yes-or-no-p 'y-or-n-p)
(menu-bar-mode 0)
(show-paren-mode 1)
(setq require-final-newline 1)
(setq display-time-24hr-format 1)
(display-time-mode +1)
(blink-cursor-mode -1)
#+end_src
  • Package Management

Tell Emacs where to look to download the packages when we want a package to be install. I use use-package macro to define package installation.

#+begin_src emacs-lisp
(require 'package)
(setq package-archives '(("melpa" . "https://melpa.org/packages/")
("org" . "https://orgmode.org/elpa/")
("elpa" . "https://elpa.gnu.org/packages/")))
(package-initialize)
(unless package-archive-contents
(package-refresh-contents))
;; Initialize use-package on non-Linux platforms
(unless (package-installed-p 'use-package)
(package-install 'use-package))
(require 'use-package)
(setq use-package-always-ensure t)
(require 'org-tempo)
#+end_src
  • Setup Theme and Fonts

Theme

Can use any theme also but I prefer zenburn or dooms.

#+begin_src emacs-lisp
(use-package zenburn-theme
:ensure t
:config (load-theme 'zenburn t))
#+end_src

Fonts

Fira code fonts supports ligature too.

#+begin_src emacs-lisp
(font-family-list)
(add-to-list 'default-frame-alist
(cond
((string-equal system-type "darwin") '(font . "Fira Code-14"))
((string-equal system-type "gnu/linux") '(font . "Fira Code-12"))))
#+end_src
  • Programming

To make Emacs your IDE for programming languages

  • Python
Make sure you have the pyls language server installed before trying lsp-mode!#+begin_src sh
pip3 install --user "python-language-server[all]"
#+end_src>
#+begin_src emacs-lisp
(use-package python-mode
:ensure t
:hook (python-mode . lsp-deferred)
:custom
;; NOTE: Set these if Python 3 is called "python3" on your system!
(python-shell-interpreter "python3")
(dap-python-executable "python3")
(dap-python-debugger 'debugpy)
:config
(require 'dap-python))
#+end_src

#+begin_src emacs-lisp
(use-package pyvenv
:after python-mode
:config
(pyvenv-mode 1))
#+end_src
  • Golang
#+begin_src sh 
# GO Path
export GOROOT=/usr/local/go
export GOPATH=$HOME/Code/golang
export PATH=$PATH:$GOROOT/bin:$GOPATH/bin
#+end_src>

#+begin_src sh
go install github.com/nsf/gocode@latest
go install github.com/rogpeppe/godef@latest
go install golang.org/x/tools/cmd/goimports@latest
go install golang.org/x/tools/gopls@latest
#+end_src
#+begin_src emacs-lisp
(setq exec-path (append exec-path '("/usr/local/go/bin/go")))
(setq exec-path (append exec-path '("/usr/bin/gopls")))
(defun lsp-go-install-save-hooks ()
(add-hook 'before-save-hook #'lsp-format-buffer t t)
(add-hook 'before-save-hook #'lsp-organize-imports t t))
(use-package go-mode
:ensure t
:config
(add-hook 'go-mode-hook #'lsp)
(require 'dap-dlv-go)

(add-hook 'before-save-hook 'gofmt-before-save) ; run gofmt on each save
(add-hook 'go-mode-hook #'lsp-go-install-save-hooks)
(add-hook 'go-mode-hook #'lsp-deferred))
#+end_src
#+begin_src emacs-lisp
(use-package go-eldoc
:ensure t
:config
(go-eldoc-setup))
#+end_src
#+begin_src emacs-lisp
(use-package exec-path-from-shell
:ensure t)
#+end_src
  • Company Mode — Complete Anything

Company is a text completion framework for Emacs. The name stands for “complete anything”. It uses pluggable back-ends and front-ends to retrieve and display completion candidates.

#+begin_src emacs-lisp
(use-package company
:after lsp-mode
:hook (lsp-mode . company-mode)
:bind (:map company-active-map
("<tab>" . company-complete-selection))
(:map lsp-mode-map
("<tab>" . company-indent-or-complete-common))
:custom
(company-minimum-prefix-length 1)
(company-idle-delay 0.0))
(use-package company-box
:hook (company-mode . company-box-mode))
#+end_src

That’s how my Emacs looks like when I write Golang

  • Terraform

Yes you can write Terraform code too in Emacs

#+begin_src emacs-lisp 
(use-package terraform-mode
:ensure t)
#+end_src
  • Docker

Yes Docker-files too

You see that little whale left bottom?

There are many many configurations possibles with Emacs, for more details check out my Emacs config which is still in WIP.

All the best for Emacs Hacking!

--

--