Skip to content

Instantly share code, notes, and snippets.

@ericdallo
Last active November 15, 2024 08:10
Show Gist options
  • Save ericdallo/09217734a925148976e13b872b91e134 to your computer and use it in GitHub Desktop.
Save ericdallo/09217734a925148976e13b872b91e134 to your computer and use it in GitHub Desktop.
Doom-Emacs Clojure setup

Doom Emacs setup

Doom-Emacs is a Emacs framework, Emacs is a huge software with multiple packages and ways to do everything, doom-emacs try to make that a little bit easier for most new users offering a simpler way to install and manage most important packages and configs, under the hood it's a lot of elisp code that install most useful Emacs packages, manage packages state, improves performance with multiple tweaks and offer all of that in a easy opt-in/out way to final users via modules. Doom uses under the hood the evil-mode package, which is a package that tries to bring Vim's commands and motion to Emacs, so knowing vim helps a lot how to use Doom emacs, although you don't need to understand vim to start using it.

Pre-requisites

  • Emacs 27.1+ (Mac users: brew install emacs-plus@28 --with-native-comp), more details here.
  • ripgrep (for faster project search) (Mac users: brew install ripgrep).
  • fd (Mac users: brew install fd)

Installation

After emacs properly installed, install doom-emacs following the official instructions, after that, make sure you run doom sync and doom doctor and they don't return any errors (warnings are OK).

TLTR:

git clone --depth 1 https://github.com/hlissner/doom-emacs ~/.emacs.d
~/.emacs.d/bin/doom install

Configuration

All doom-emacs configurations are inside ~/.doom.d folder:

  • .doom.d/init.el: This is what doom reads when starting, it contains all the modules it should enable, every change requires a doom sync.
  • .doom.d/packages.el: All extra packages you want that should be installed that are not available via a module in init.el. every change requires a doom sync.
  • .doom.d/config.el: Your custom configuration for anything you want to change on your Emacs.

You can create any extra files for bindings, or functions and on config.el load it, for example: (load! "+bindings") for a .doom.d/+bindings.el file.

Tip: most changes on config.el don't need to restart emacs, a simple , e e should eval that config, even so if that doesn't work, you will need to restart emacs or , r r

General

Tip: Use SPC f p to open .doom.d files in doom-emacs.

Enable lsp module under :tools section in your ~/.doom.d/init.el. Enable (treemacs +lsp) module under :ui section in your ~/.doom.d/init.el.

Add base config which include some good defaults:

config.el

(setq read-process-output-max (* 1024 1024)
      doom-localleader-key "," ;; easier than <SPC m>
      ;; doom-font (font-spec :family "JetBrainsMono Nerd Font Mono" :size 18) ;; Make sure to use a font you have installed
      ;; doom-theme 'doom-dracula
      projectile-project-search-path '("~/YOUR_PROJECTS_BASE_PATH") ;; Change this to your base path for projects
      projectile-enable-caching nil)

(add-to-list 'default-frame-alist '(fullscreen . maximized))

(use-package! lsp-mode
  :commands lsp
  :config
  (setq lsp-semantic-tokens-enable t)
  (add-hook 'lsp-after-apply-edits-hook (lambda (&rest _) (save-buffer)))) ;; save buffers after renaming

Clojure

Enable clojure module with +lsp flag under :lang section: (clojure +lsp) in your ~/.doom.d/init.el.

Main packages that this module will install:

  • cider: Support for Clojure REPL on Emacs.
  • lsp-mode: Make Emacs more IDEish, adding multiple features using static analysis.

Make sure to install clojure-lsp via Emacs when prompted or following instructions.

Most relevant keybindings

Doom has lots of default keybindings, and has the feature of showing a popup with the available keybindigns when you press the first one, try pressing one time SPC, after a little bit, doom should show all keybindings you can access and its key, this helps finding a keybinding even not knowing the proper keybinding.

Doom keybindings could be global ones or specific by current buffer major modes (Clojure, Dart, etc), the global ones usually start with SPC, for example SPC p a which will call projectile-add-known-project, while keybindings for the current buffer/mode will start with the localleader key, ,, for example , t t to run the current Clojure test with cider.

Note: Some commands have multiple keybindings, this guide will try to present some of them.

Global

SPC f p Access your doom's config files

  • Project

    SPC SPC Find file in project

    SPC p a Add a folder as a known project

    SPC p p Change to a known project

    SPC s p Search on whole project

  • Buffer/Window

    SPC f f Find or create a new file

    SPC f s Save buffer

    SPC < Switch to recent buffer

    SPC b k Kill current buffer

    SPC w c Close current window

    SPC w v Split vertical

    SPC w s Split horizontal

    SPC w h Go to left window

    SPC w j Go to bottom window

    SPC w k Go to top window

    SPC w l Go to right window

    / + type to search on buffer (Use n and N to next/prev)

  • LSP

    SPC c d Find symbol definition

    SPC c D Find symbol references

    SPC c o Organize imports

    SPC c a List/Execute code action

    SPC c f Format buffer or region

    SPC c r Safe rename symbol in project

Clojure

, ' Connect to REPL (jack in), check this workaround for Mac users not being able to use '.

, r l Load buffer into REPL

, r c Clean REPL

, r q Disconnect REPL

, r b Show REPL window

, e e Eval current expression

, e d Eval current defun

, t t Run current test

, t n Run ns tests

, t a Run last test

Paredit

Add following to enable paredit and create keybindings

config.el

(use-package! paredit
  :hook ((clojure-mode . paredit-mode)
         (emacs-lisp-mode . paredit-mode)))
         
;; Fell free to move this to a bindings file you load from config.el
(after! paredit
  (define-key paredit-mode-map (kbd "C-<left>") nil)
  (define-key paredit-mode-map (kbd "C-<right>") nil)

  (map! :nvi

        :desc "Forward barf"
        "M-<left>" #'paredit-forward-barf-sexp

        :desc "Forward slurp"
        "M-<right>" #'paredit-forward-slurp-sexp

        :desc "Backward slurp"
        "M-S-<left>" #'paredit-backward-slurp-sexp

        :desc "Backward barf"
        "M-S-<right>" #'paredit-backward-barf-sexp

        :desc "Backward"
        "C-c <left>" #'paredit-backward

        :desc "Forward"
        "C-c <right>" #'paredit-forward))

Workflow tips

When you open a project for the first time, lsp-mode will prompt asking what is the project root, usually, it guesses correctly and you just need to input i. Make sure you answer that and not skip otherwise LSP won't work properly, if you miss it, you can SPC : lsp and it should ask it again, or SPC : lsp-workspace-folders-remove to remove a wrong project root.

Clojure REPL

Doom shows the current REPL state of Clojure at the modeline on the right, with a terminal/shell icon, each color represents a state:

  • Gray: REPL is not connected
  • Yellow: REPL connected but not evaluated
  • Green: REPL in sync.

Usually your workflow will be something like, open a project, connect to the repl with , ' and after connected load the file + deps with , r l. You can easily clean the REPL state with , r c and eval things with , e d or , e e, running tests with , t t.

If you lose your REPL, you can , r b to show on the window or , r q to quit if any.

Troubleshooting

  • SPC h d d toggle debug mode showing stacktraces of errors.

  • Reach #emacs slack channel.

Example configs

Feel free to link your doom emacs config =)

Related links

Extras

This section adds some information for people that want to understand a little bit better about Emacs, totally optional to read.

Basic Emacs concepts summary

A buffer is an interface between Emacs and a file or process. It doesn’t have to be visible on the screen. You can (and sometimes will) have hundreds of buffers open in Emacs, but you’ll probably only have windows open to one or two at a time. Buffers hold text and each one has a unique name.

A window is a view onto a buffer. It allows you, the user, to see what’s going on inside that buffer. If the buffer is associated with a file, you’ll see the text of the file. If the buffer is associated with a process, such as a shell, you’ll see some representation of that process. You can split the current frame, which leaves you looking at two windows.

A frame in Emacs is what you would call a window in most other contexts. They’re just windows in the normal sense of the word—you can drag them around the screen or close them with the X button or do whatever you do with windows. In the command line version of Emacs, you only ever have one frame. However, with the GUI (graphical) version you can open multiple frames.

minibuffer is the small rectangle on the bottom of your Emacs, it shows lots of information of the current executed command, it's important to pay attention on that when executing some command, usually the feedback or error is shown there. (It's clickable too which will open the *messages* buffer)

modeline is the rectangle right above the minibuffer, it can be totally customizable, usually contains the file name, line/row info and some information about LSP/REPL state on the right.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment