If you are reading this, I finally got around to writing my .emacs config using Org mode. Why? Because it makes is easy to document the whys of my config. I also think it will make it fun to re-read my comments and text after some time :P
The configuration will change with time, based upon programming languages used and so on. For some languages I just use try to use them in the current session (see below), because I use them so rarely :P
When starting my configuration, it will look like the following (see below for configuration of dashboard, modeline etc. :) ):
- .emacs configuration
- Basic setup
- Keep packages up-to-date
- EXWM - Emacs Window manager
- Behaviorial settings
- Editing settings
Before we begin, we have to do some basic setup. For the rest of the config, I will use use-package
to install packages. I want newer packages, and packages not available in the gnu repos, so adding MELPA and the org repo (to get newest org mode). Then we install use-package
and set te always ensure variable to make sure it always tries to download the packages we want.
;;; -*- lexical-binding: t; -*-
;; sets lexical bindings so we get real closres with let etc. like in Scheme
;; TODO: do we need both melpa and melpa-stable. Isn't melpa enough?
(require 'package)
(add-to-list 'package-archives
'("melpa" . "https://melpa.org/packages/"))
(add-to-list 'package-archives
'("melpa-stable" . "https://stable.melpa.org/packages/"))
(add-to-list 'package-archives
'("org" . "http://orgmode.org/elpa/") t) ;; for newest version of org mode
(package-initialize)
;; only refresh package contents if we havent downloaded it yet.
(unless package-archive-contents
(package-refresh-contents))
(unless (package-installed-p 'use-package)
(package-install 'use-package))
;; always download packages when we don't have them locally
(require 'use-package)
(setq use-package-always-ensure 't)
Some packages are also not in a package manager, and I may want to install it directly from a repo. Straight is good for this type of scenario!
(defvar bootstrap-version)
(let ((bootstrap-file
(expand-file-name
"straight/repos/straight.el/bootstrap.el"
(or (bound-and-true-p straight-base-dir)
user-emacs-directory)))
(bootstrap-version 7))
(unless (file-exists-p bootstrap-file)
(with-current-buffer
(url-retrieve-synchronously
"https://raw.githubusercontent.com/radian-software/straight.el/develop/install.el"
'silent 'inhibit-cookies)
(goto-char (point-max))
(eval-print-last-sexp)))
(load bootstrap-file nil 'nomessage))
;; Avoid straight.el fucking with org-mode
(use-package org :straight (:type built-in))
I’m a bit lazy when it comes to updating packages manually, so automating this is a welcome addition :)
(use-package auto-package-update
:custom
(auto-package-update-interval 31)
(auto-package-update-delete-old-versions t)
(auto-package-update-prompt-before-update t)
(auto-package-update-show-preview t)
:config
(auto-package-update-maybe))
On my GNU/Linux machines I have used EXWM as a window manager for quite a while. It makes navigating windows easier, and makes it possible to do everything in Emacs <3 To set it up, I set up a custom X-session with a .xinitrc file for my user:
# various settings like sound applets depending on distro and tooling
# examples include nm-applet, xfsettingsd & etc. to be able to piggyback on some xcfe tooling etc.
# define this environment variable to enable tangling and running of exwm config
export USE_EXWM="yes"
exec dbus-launch --exit-with-session emacs
Then on launch it will run the following setup for using EXWM:
(defun themkat/setup-exwm ()
;; Shrink fringes to 1 pixel
(fringe-mode 1)
;; Display the time in the modeline
(setq display-time-default-load-average nil)
(setq display-time-day-and-date t display-time-24hr-format t)
(display-time-mode t)
;; Emacs server is not required to run EXWM but it has some interesting uses
;; (see next section)
(server-start)
;; Load EXWM
(require 'exwm)
;; Set the initial number of workspaces.
(setq exwm-workspace-number 2)
;; Buffer names for EXWM
(add-hook 'exwm-update-class-hook
(lambda ()
(unless (or (string-prefix-p "sun-awt-X11-" exwm-instance-name)
(string= "gimp" exwm-instance-name))
(exwm-workspace-rename-buffer exwm-class-name))))
(add-hook 'exwm-update-title-hook
(lambda ()
(when (or (not exwm-instance-name)
(string-prefix-p "sun-awt-X11-" exwm-instance-name)
(string= "gimp" exwm-instance-name))
(exwm-workspace-rename-buffer exwm-title))))
;; + Bind "s-0" to "s-3" to switch to the corresponding workspace.
(dotimes (i 4)
(exwm-input-set-key (kbd (format "s-%d" i))
`(lambda ()
(interactive)
(exwm-workspace-switch-create ,i)
(message (concat "Switched to workspace: "
(number-to-string ,i))))))
;; + Application launcher
(exwm-input-set-key (kbd "s-&")
(lambda (command)
(interactive (list (read-shell-command "$ ")))
(start-process-shell-command command nil command)))
;; quickly switch between line and char modes
(exwm-input-set-key (kbd "s-o") #'exwm-input-toggle-keyboard)
;; quickly change keyboard layout
(let ((currLayout "no"))
(exwm-input-set-key (kbd "s-k")
(lambda ()
(interactive)
(setq currLayout (if (string-equal currLayout "no") "us" "no"))
(start-process-shell-command ""
nil
(concat "setxkbmap -layout " currLayout))
(message (concat "Changed keyboard layout to: " currLayout)))))
;; system tray for Dropbox, Skype volume control, wireless manager etc.
(require 'exwm-systemtray)
(exwm-systemtray-enable)
;; turn on multimonitor support
;; TODO: probably needs to be tuned for each machine as the randr screen identifiers will be different
(require 'exwm-randr)
(setq exwm-randr-workspace-output-plist '(1 "DP-1-1"))
(add-hook 'exwm-randr-screen-change-hook
(lambda ()
(start-process-shell-command
"xrandr" nil "xrandr --output DP-1-1 --right-of eDP-1-1 --auto")))
(exwm-randr-enable)
(exwm-enable))
(if (getenv "USE_EXWM")
(use-package exwm
:init
(themkat/setup-exwm)))
Desktop environment can also be very useful when Emacs blocks the function keys (volume etc.). I have this issue with EXWM, and desktop-environment fixes it.
(use-package desktop-environment
:requires exwm
:init
(desktop-environment-mode))
These settings control how Emacs behaves in general (i.e, for all modes); what UI elements to show, theming, what meta-key to use on Mac OS X, Helm to navigatge etc. :)
I use some general settings to make Emacs feel better.
;; TODO: do these, the clipboard settings belong in editor settings instead?
;; Get PATH from session instead of whatever idiotic things are done before
(use-package exec-path-from-shell
:init
(when (memq window-system '(mac ns x))
(exec-path-from-shell-initialize)))
;; set default coding of buffers
(setq default-buffer-file-coding-system 'utf-8-unix)
;; switched from tabs to spaces for indentation
;; also set the indentation level to 4.
(setq-default indent-tabs-mode nil)
(setq-default tab-width 4)
;; Don't autosave.
(setq auto-save-default nil)
;; GUI related settings
(if (display-graphic-p)
(progn
;; Removed annoying UI elements
(menu-bar-mode -1)
(tool-bar-mode -1)
(scroll-bar-mode -1)
;; shows battery status (useful when using EXWM)
(display-battery-mode 1)))
;; disable the C-z sleep/suspend key
;; rarely use emacs in terminal mode anymore and that is the only place it can be useful
;; see http://stackoverflow.com/questions/28202546/hitting-ctrl-z-in-emacs-freezes-everything
;; for a way to have both if I ever want that again.
(global-unset-key (kbd "C-z"))
;; disable the C-x C-b key, because I use helm (C-x b) instead
(global-unset-key (kbd "C-x C-b"))
(setq display-time-default-load-average nil)
(setq display-time-day-and-date t display-time-24hr-format t)
(display-time-mode t)
;; make copy and paste use the same clipboard as emacs.
(setq select-enable-primary t
select-enable-clipboard t)
;; Ensure I can use paste from the Mac OS X clipboard ALWAYS (or close)
(when (memq window-system '(mac ns))
(setq interprogram-paste-function (lambda () (shell-command-to-string "pbpaste"))))
;; sets monday to be the first day of the week in calendar
(setq calendar-week-start-day 1)
;; save emacs backups in a different directory
;; (some build-systems build automatically all files with a prefix, and .#something.someending breakes that)
(setq backup-directory-alist '(("." . "~/.emacsbackups")))
;; Don't create lockfiles. Many build systems that continously monitor the file system get confused by them (e.g, Quarkus). This sometimes causes the build systems to not work anymore before restarting
(setq create-lockfiles nil)
;; Enable show-paren-mode (to visualize paranthesis) and make it possible to delete things we have marked
(show-paren-mode 1)
(delete-selection-mode 1)
;; don't show the emacs splash screen as I use a custom dashboard instead
(setq inhibit-startup-screen t)
;; use y or n instead of yes or no
(defalias 'yes-or-no-p 'y-or-n-p)
;; Instead of making annoying beeps, blink the modeline on various errors.
;; Taxed/Stolen from:
;; http://whattheemacsd.com/appearance.el-02.html
(setq ring-bell-function (lambda ()
(invert-face 'mode-line)
(run-with-timer 0.1 nil 'invert-face 'mode-line)))
;; Make me confirm before exiting Emacs
;; I have become a clumsy piece of shit with my age, and sometimes exit Emacs by mistake
(setq confirm-kill-emacs 'yes-or-no-p)
;; Use Monaspace Radon if available
;; https://monaspace.githubnext.com/
;; (install pls!)
(let ((desired-font "-*-Monaspace Radon-regular-normal-normal-*-12-*-*-*-p-0-iso10646-1"))
(unless (null (find-font (font-spec :name desired-font)))
(set-frame-font desired-font nil t)))
;; More comfortable smooth scrolling
;; (also showing whats ahead with scroll-margin. That means that it starts smoothly scrolling 15 lines before it reaches top or bottom)
(setq scroll-conservatively 10
scroll-margin 15)
;; Start Emacs server so I don't have to do it manually anymore
;; (useful for emacsclient -n filename from other processes)
(server-start)
This one only applies to Mac, but makes my life easier. The different brackets became almost impossible to use without this :P Controlling which key is the actual meta key.
(setq mac-command-modifier 'meta)
(setq mac-option-modifier nil)
It is nice to open images in Emacs. Make ImageMagick or similar convert images that are not supported natively for me:
(setq image-use-external-converter t)
Sometimes I forget a hotkey-sequence I don’t use that often, or a better case just remember the beginning of a longer sequence. Then which-key comes in handy! which-key shows possible continuations of a key-sequence. If you type C-x with your keyboard, it will suggest many continuations like C-+, C–, h etc.
(use-package which-key
:custom
(which-key-idle-delay 5)
:config
(which-key-mode))
I use helm because i prefer it to ido or alternatives. It is simple to use, has a great UI, and to me it makes Emacs even more powerful as both a text editor and window manager (to switch windows). It will install after projectile (which makes project handling a breeze), which is found with the git and project handling setup *git and project handling. Here I simply activate it, make the search less rigid (not just beginning of strings, but anywhere in them), remove certain buffers from the buffer list and activate some key bindings globally to do various operations.
(use-package helm
:after (projectile)
:config
(helm-mode 1)
(projectile-mode +1)
(helm-projectile-on)
(helm-adaptive-mode 1)
;; hide uninteresting buffers from buffer list
(add-to-list 'helm-boring-buffer-regexp-list (rx "magit-"))
(add-to-list 'helm-boring-buffer-regexp-list (rx "*helm"))
:custom
(helm-M-x-fuzzy-match t)
(projectile-completion-system 'helm)
(helm-split-window-in-side-p t)
:bind
(("M-x" . helm-M-x)
("C-x C-f" . helm-find-files)
;; get the awesome buffer list instead of the standard stuff
("C-x b" . helm-mini)))
For years I navigated windows in Emacs with C-x o
because of habit. Not anymore! After starting using the Sway window manager, I realized how tedious it was. You can say that Sway cured my Stockholm syndrome, and now I can navigate with M-<arrow key>
to navigate to buffer windows based on position.
(use-package windmove
:ensure nil
:bind*
(("M-<left>" . windmove-left)
("M-<right>" . windmove-right)
("M-<up>" . windmove-up)
("M-<down>" . windmove-down)))
TODO: Check if this should be somewhere else in the config Having searches for system packages and installations directly in Emacs is pretty neat!
(use-package helm-system-packages)
Emacs is always open at my machine, so I really enjoy a friendly startup screen :) dashboard provides what I want with projects (from projectiles list), recently edited files and latest news from Hackernews. To make the experience even better I also install all-the-icons to get pretty icons.
NOTE: At first run, you should run M-x all-the-icons-install-fonts
to get the fonts needed for the icons to show properly.
;; Getting pretty icons
(use-package all-the-icons)
(use-package dashboard
:after (all-the-icons dashboard-hackernews helm-system-packages)
:init
(dashboard-setup-startup-hook)
;; seems like the latest versions do some fuckery with the project list or something.
;; Need an extra refresh after initialization for my own settings to show up now.
;; (did not need this before. Would rather keep the :custom block instead of setq spamming)
:hook
(dashboard-after-initialize . dashboard-refresh-buffer)
:custom
(dashboard-banner-logo-title "Welcome my queen! Make some kewl stuff today!")
(dashboard-startup-banner 'logo)
(dashboard-center-content t)
(dashboard-set-navigator t)
(dashboard-navigator-buttons '((("⤓" " Install system package" " Install system package" (lambda (&rest _) (helm-system-packages))))))
(dashboard-icon-type 'all-the-icons)
;; TODO: enable again when they work
;; https://github.com/emacs-dashboard/emacs-dashboard/issues/459
(dashboard-set-heading-icons nil)
(dashboard-set-file-icons t)
(dashboard-projects-backend 'projectile)
(dashboard-items '((projects . 5)
(recents . 5)
(hackernews . 5))))
(use-package dashboard-hackernews)
To make Emacs better looking, I use the leuven-theme. This theme improves org-mode readability and makes Emacs blue and pretty in general :) I used to use doom-themes, moe-themes and so on with a simple theme switcher function, but I mostly just use leuven so I decided to remove them. The modeline is made prettier and more modern with doom modeline to get a beautiful powerline :)
(use-package leuven-theme)
;; just to make sure dash, f and s are present.
(use-package dash)
(use-package s)
(use-package f)
;; TODO: use-package here eventually
(eval-after-load 's
(progn (require 's)
(add-to-list 'custom-theme-load-path (s-concat user-emacs-directory "themes/pink-bliss-uwu"))
(load-theme 'pink-bliss-uwu 't)))
(use-package doom-modeline
:init
(doom-modeline-mode 1))
Recently I also started using tabs with the centaur-tabs package:
;; Unset the default behavior of the C-x <left> and <right> arrow key navigation
(global-unset-key (kbd "C-x <left>"))
(global-unset-key (kbd "C-x <right>"))
(use-package centaur-tabs
:after (dashboard org)
:config
(centaur-tabs-mode 1)
;; Custom group function to get it the way _I_ want
(defun centaur-tabs-buffer-groups ()
"Groups tabs based on which project root they are in if possible"
(let ((get-closest-projectile-project
(lambda (path)
(let ((expanded-path (f-long path)))
(-first (lambda (proj)
(s-starts-with? proj
expanded-path))
(-map (lambda (proj)
(f-long proj))
projectile-known-projects))))))
(list (cond
;; Group as part of projectile project if directly part of it
((condition-case _err
(projectile-project-root)
(error nil))
(f-expand (projectile-project-root)))
;; Try to group as part of projectile project if indirectly part of it (started from the same directory, not yet tracked, or maybe temporary buffer)
(get-closest-projectile-project default-directory)
((string-equal "*" (substring (buffer-name) 0 1))
"proc-buffers")
;; ... other groupings ...
(t
"Other")))))
:custom
(centaur-tabs-set-icons t)
(centaur-tabs-plain-icons t)
(centaur-tabs-set-modified-marker t)
:bind
(("C-x <left>" . centaur-tabs-backward-tab)
("C-x <right>" . centaur-tabs-forward-tab)
("C-z <left>" . centaur-tabs-move-current-tab-to-left)
("C-z <right>" . centaur-tabs-move-current-tab-to-right))
;; TODO: EXWM buffers
;; Disable tab bars in the below modes
:hook
((dashboard-mode . centaur-tabs-local-mode)
(org-src-mode . centaur-tabs-local-mode)
(calendar-mode . centaur-tabs-local-mode)
(dap-ui-breakpoints-mode . centaur-tabs-local-mode)
(dap-ui-sessions-mode . centaur-tabs-local-mode)
(dap-ui-repl-mode . centaur-tabs-local-mode)
(lsp-treemacs-generic-mode . centaur-tabs-local-mode)))
man-pages are good, but sometimes I need a shorter version For that, tldr is great!
(use-package tldr)
devdocs are also pretty handy to have, especially pre-downloaded entries I can read directly in Emacs:
(use-package devdocs)
NOTE: Remember to download documentation with devdocs-install
before intended use.
Usage:
- devdocs-peruse opens a specific documentation (e.g, kotlin-1.9, rust book, Spring Boot etc.)
- devdocs-lookup lets me see a list of the topics in that specific doc.
Sometimes I like to try packages without having them as a permanent part of my Emacs setup. try does exactly that, where the packages are gone after Emacs is closed.
(use-package try)
goto-line waits until we hit enter to show us which line we went to. Sometimes I am off by a few digits, and preview helps me move faster.
(use-package goto-line-preview)
Line numbers
(add-to-list 'prog-mode-hook 'display-line-numbers-mode)
(custom-set-faces
'(line-number-current-line ((t (:inherit line-number :background "white" :foreground "black")))))
Rainbow mode
(use-package rainbow-mode
:hook prog-mode)
focus mode!!! Grays out the rest of the buffer, and only highlights the given function we are in.
(use-package focus)
Yasnippet makes boiler plate and other code snippets much faster to write with snippets that activates with small keywords. Just type the keyword and TAB, and yasnippet will fill in the snippet :) (you may have to fill in some names like class name or parameter names after TAB off course…).
(use-package yasnippet
:config
(yas-reload-all)
:hook
(sh-mode . yas-minor-mode))
;; install useful snippets
;; Thought I already had installed these, must have been an older setup I had :P Years pass by so fast
(use-package yasnippet-snippets
:after yasnippet)
Sometimes we want to edit multiple places in the file at the same time. Most of the time this is just adding the same characters multiple places in the file in places with the same pattern, other times it is inserting a sequence of numbers.
(use-package multiple-cursors
:bind
("C->" . mc/mark-next-like-this)
("C-<" . mc/mark-previous-like-this))
Paredit makes paranthesis handling a breeze in Lisp-languages :) Only setting I really need is to make it possible to select something and delete the selection (including the paranthesis).
(use-package paredit
:config
;; making paredit work with delete-selection-mode
;; found on the excellent place called what the emacs d.
(put 'paredit-forward-delete 'delete-selection 'supersede)
(put 'paredit-backward-delete 'delete-selection 'supersede)
(put 'paredit-open-round 'delete-selection t)
(put 'paredit-open-square 'delete-selection t)
(put 'paredit-doublequote 'delete-selection t)
(put 'paredit-newline 'delete-selection t)
:hook
((emacs-lisp-mode . paredit-mode)
(scheme-mode . paredit-mode)))
Certain strings should in my view be translated to unicode symbols, and so far I just set some defaults for all modes.
;; should I defaults? or maybe one for c-like languages, one for lisp etc.?
(setq-default prettify-symbols-alist '(("lambda" . 955)
("->" . 8594)
("!=" . 8800)))
(global-prettify-symbols-mode)
Undo-tree. I LOOOOVE undo-tree <3 Instead of having a linear line of operations we can undo and redo, I have a tree I can navigate :D
(use-package undo-tree
:config
(add-to-list 'undo-tree-incompatible-major-modes #'nxml-mode)
(global-undo-tree-mode)
:custom
(undo-tree-history-directory-alist '(("." . "~/.emacs.d/undo"))))
Emojis in comments, org mode text and other places are really fun and makes the text feel more alive (instead of showing codes for emojis where applicable). (sometimes I turn it off because it ends up emojifying too much, but that is easy with M-x emojify-mode
).
(use-package emojify
:init
(add-hook 'after-init-hook #'global-emojify-mode))
Acivate spell checking for some relevant modes, set some preferred languages and makes the correction prettier with helm.
NOTE: Remember to install aspell and relevant dictionaries. Seems like Homebrew package on OS X only contain the abomination known as Nynorsk, so the REAL Norwegian language known as Bokmål will have to be installed manually. I needed MacPorts to be able to do that on OSX. Linux distros at least have packages like MacPorts do for sane languages.
;; FlySpell (spell checking)
(dolist (flyspellmodes '(text-mode-hook
org-mode-hook
latex-mode-hook))
(add-hook flyspellmodes 'turn-on-flyspell))
;; strings in code
(add-hook 'prog-mode
'flyspell-prog-mode)
;; sets american english as defult
(setq ispell-dictionary "american"
ispell-program-name "aspell")
;; let us cycle american english (best written english) and norwegian
(defun themkat/change-dictionary ()
(interactive)
(ispell-change-dictionary (if (string-equal ispell-current-dictionary "american")
"norsk"
"american")))
;; helm functionality for flyspell. To make it more user friendly
(use-package helm-flyspell
:after (helm flyspell)
:init
;; Disable standard keys for flyspell correct, and make my own for helm.
:bind
(:map flyspell-mode-map
("C-." . themkat/change-dictionary)
("C-," . helm-flyspell-correct)))
company (COMPLete ANY) provides base functionality for completions (ui elements, searching for candidates etc). For many modes, company is sufficient, but for some languages it can be great to use with something like lsp-mode to provide more advanced completion (like for Java and Kotlin).
(use-package company
:config
(global-company-mode)
:custom
;; set the completion to begin at once
(company-idle-delay 0)
(company-echo-delay 0)
(company-minimum-prefix-length 1)
:bind
;; trigger company to see a list of choices even when nothing is typed. maybe it quit because we clicked something. or maybe we dont know what to type yet :P
;; CTRL-ENTER. Because C-RET does not work.
([(control return)] . company-complete))
;; a nicer way to show company completions with icons and doc popup where available (lsp etc.)
;; Also doesn't clutter up the screen with super-big multiline truncated lines
(use-package company-box
:after company
:if (display-graphic-p)
:custom
(company-box-frame-behavior 'point)
(company-box-show-single-candidate t)
(company-box-doc-delay 1)
:hook
(company-mode . company-box-mode))
;; little hack function to make company box frame bigger
(defun themkat/company-box-fix-size ()
(interactive)
(let* ((box-frame (company-box--get-frame)))
(when (not (null box-frame))
(set-face-attribute 'default
box-frame
:height 180))))
LSP = Language Server Protocol lsp-mode uses LSP servers to provides IDE functionality like code completion (intellisense like using company-capf), navigation (jump to symbol), refactoring functionality and so on. lsp-ui is used to get prettier boxes and more info visible in an easy way (like javadoc). Currently dap-mode is added because I play a bit with it, and my first impressions are great so far (for the few times I use a debugger, I know I’m weird for not needing it much at all).
(use-package lsp-mode
:bind
(:map lsp-mode-map ("M-RET" . lsp-execute-code-action))
;; Save automatically on lsp-rename etc. Usually it opens buffers in the background that contains the edits...
;; https://github.com/emacs-lsp/lsp-mode/issues/4087
:hook
(lsp-after-apply-edits . save-buffer))
;; helper boxes and other nice functionality (like javadoc for java)
(defun lsp-ui-show-doc-helper ()
(interactive)
(if (lsp-ui-doc--visible-p)
(lsp-ui-doc-hide)
(lsp-ui-doc-show)))
(use-package lsp-ui
:after lsp-mode
:custom
(lsp-ui-sideline-show-code-actions t)
(lsp-ui-doc-position 'at-point)
:bind
(:map lsp-mode-map ("M-s M-d" . lsp-ui-show-doc-helper)))
;; Additional helpers using treemacs
;; (symbols view, errors, dependencies for Java etc.)
(use-package lsp-treemacs
:after lsp-mode
:config
(lsp-treemacs-sync-mode 1))
;; debugger component (for the few times I need it)
(use-package dap-mode
:after lsp-mode
:init
(dap-auto-configure-mode))
Some modes uses flycheck to provide syntax correctness checks (e.g, red lines below errors).
(use-package flycheck
:custom
(flycheck-indication-mode nil)
(flycheck-highlighting-mode 'lines))
I also find it useful to be able to see cognitive complexity metrics while coding.
(use-package codemetrics
:straight (codemetrics :type git :host github :repo "jcs-elpa/codemetrics"))
;; some dependencies
;; (tree-sitter can be used for way more things, but I mainly use it for codemetrics atm)
(use-package tree-sitter-langs)
Some languages work great out of the box, some require a little tweaking.
C does not really need much auto completion, but it can be great to have it for projects that use some external libraries (like libogc for Nintendo GameCube development, where you have a SDK for the console). I used to just use company-c-headers and company-clang for this, but realized that some extra popups with documentation comments, error checking, completion etc. was most welcome! clangd is a language server that provides that for C, C++, CUDA C etc. While I REALLY HATE that it doesn’t auto include headers for DevkitPro if I don’t have them open in a source file in the project, it seems to be more feature rich than CCLS. Documentation shows better, signature help etc. (I’m too fucking old to remember all the headers, import statements etc. in many languages).
Prerequisites: To get all include paths and settings for a project correct, one should create a compile_commands.json
file that clangd will read. I use CompileDB to generate this file, as it seems to generate a useful file even for projects where tools like Bear have problems. For CMake projects (ugh), one simply adds -DCMAKE_EXPORT_COMPILE_COMMANDS=YES
to the cmake command.
;; configure built in here with ensure nil
(use-package cc-mode
:ensure nil
:after (lsp-mode)
:hook
((c-mode . lsp)
(c-mode . yas-minor-mode)))
Some projects also use CMake as a build system in the C and C++ world. Handy to have CMake syntax highlighting available:
(use-package cmake-mode)
(just activate lsp using M-x in a CMake buffer after this to get completion etc.! Requires installation of cmake-language-server)
6502 Assembly (especially for Commodore 64):
(use-package mos-mode)
(my own package lol)
Recently started experimenting more with Rust. rustic seems to be the best package for working with it.
(use-package rustic
:after (yasnippet)
:custom
(rustic-format-trigger nil)
(lsp-rust-all-features t)
:hook
((rustic-mode . yas-minor-mode)
;; keeping it here due to flyspell being built in, and prog-mode hook not activating it
(rustic-mode . flyspell-prog-mode)))
lsp-java :drool:
Java IDE-like functionality in Emacs. When we run this mode for the first time, the lsp server will be downloaded automatically. Works like a charm!
(use-package lsp-java
:hook
(java-mode . lsp)
:bind
(:map java-mode-map
("M-RET" . lsp-java-organize-imports)))
;; Java snippets for yasnippet. Found them very useful so far
(use-package java-snippets
:after yasnippet
:hook
(java-mode . yas-minor-mode))
lsp-mode works out of the box with Kotlin mode as long as kotlin-language-server is in the path :) So I only install Kotlin-mode :)
(defun themkat/kotlin-register-debug-templates ()
;; various debug templates for Kotlin will be put here
(dap-register-debug-template "Kotlin tests with launcher"
(list :type "kotlin"
:request "launch"
:mainClass "org.junit.platform.console.ConsoleLauncher --scan-class-path"
:enableJsonLogging nil
:noDebug nil)))
(use-package kotlin-mode
:after (lsp-mode dap-mode yasnippet)
:config
(require 'dap-kotlin)
;; should probably have been in dap-kotlin instead of lsp-kotlin
(setq lsp-kotlin-debug-adapter-path (or (executable-find "kotlin-debug-adapter") ""))
(themkat/kotlin-register-debug-templates)
:hook ((kotlin-mode . lsp)
(kotlin-mode . yas-minor-mode)))
I have written briefly on my blog about Kotlin in Emacs. Article 1 and Article 2. They contain some minor tips and tricks, as well as other links that might prove useful.
;; Function to activate tide by need
(defun themkat/activate-tide ()
(interactive)
(tide-setup)
(flycheck-mode 1)
(setq flycheck-check-syntax-automatically '(save mode-enabled))
(eldoc-mode 1)
(tide-hl-identifier-mode 1))
;; TODO: see if we can replace the web-mode stuff with lsp as well.
;; only used for the mixed content web mode stuff now.
(use-package tide
:after typescript-mode)
;; typescript-mode used to be included in another package (probably tide?), but not anymore it seems
(use-package typescript-mode)
Makes it more comfortable to edit mixed files (javascript + html in same document, jsx etc.).
(defun themkat/complete-web-mode ()
(interactive)
(let ((current-scope (web-mode-language-at-pos (point))))
(cond ((string-equal "javascript" current-scope)
(company-tide 'interactive))
((string-equal "css" current-scope)
(company-css 'interactive))
(t
(company-dabbrev-code 'interactive)))))
(defun themkat/eldoc-web-mode ()
(let ((current-scope (web-mode-language-at-pos (point))))
(cond ((string-equal "javascript" current-scope)
(tide-eldoc-function))
((string-equal "css" current-scope)
(css-eldoc-function))
(t
nil))))
(defun themkat/setup-web-mode-mixed ()
(web-mode)
(themkat/activate-tide)
(setq-local eldoc-documentation-function #'themkat/eldoc-web-mode))
(use-package web-mode
:after (tide css-eldoc)
:custom
(web-mode-enable-current-element-highlight t)
:init
(require 'web-mode)
:bind
(:map web-mode-map ([(control return)] . themkat/complete-web-mode))
:mode
(("\\.html?$" . themkat/setup-web-mode-mixed)
("\\.jsx?$" . web-mode)))
Having eldoc for CSS and SASS helps a lot for remembering input parameters without looking stuff up:
(use-package css-eldoc
:hook
(css-mode . turn-on-css-eldoc)
(scss-mode . turn-on-css-eldoc))
Emacs works great as a REST client (also used it for other HTTP requests, e.g, SOAP), mostly because of the amazing restclient(-mode):
(use-package restclient
:mode
("\\.http\\'" . restclient-mode))
I sometimes write Python code for various things, sometimes as a calculator :P (SymPy, NumPy and MatplotLib <3 ). I choose to start lsp manually due to sometimes not needing a language server for minor edits (which is what I mostly do with Python).
(use-package lsp-pyright
:after lsp-mode
:init
(require 'lsp-pyright))
Use geiser to make Scheme great to work with. Not really used much anymore, but still fun to write some small procdures in Scheme once in a while :)
(use-package geiser
:init
(setq geiser-active-implementations '(racket)))
(use-package dockerfile-mode
:mode "Dockerfile\\'")
Used in Cucumber, Karate and more :) Useful to have for the situations where you edit a file like that.
(use-package feature-mode)
(use-package markdown-mode)
(use-package yaml-mode)
(setq nxml-child-indent 4)
(setq nxml-attribute-indent 4)
(use-package wgsl-mode
:hook
((wgsl-mode . lsp)))
I have recently started writing more Zig code. Hopefully I will continue doing so, so this section is not just spam :)
(use-package zig-mode
:hook
((zig-mode . lsp)
(zig-mode . yas-minor-mode)))
This is almost a reason to use Emacs by itself! Magit is the best way to experience git in my view. Simple and quick to use, together with its connection with git-gutter-fringe makes it super awesome!
(use-package magit
:commands magit-status
:bind
("C-x g" . magit-status))
;; show todos in magit status buffer
(use-package magit-todos
:after (magit)
:hook
(magit-status-mode . magit-todos-mode)
:bind
("C-x t" . helm-magit-todos))
(use-package git-gutter
:ensure git-gutter-fringe
:after magit
:init
(global-git-gutter-mode 1)
(setq-default left-fringe-width 20)
:hook
(magit-post-refresh . git-gutter:update-all-windows))
;; TODO: maybe move it? Now it is very far down from where it is originally referenced (in helm)
(use-package projectile)
(use-package helm-projectile
:after helm
:config
(helm-projectile-on)
:bind
(("C-x C-b" . helm-projectile-switch-to-buffer)))
How to this look? In this Emacs repo with my local untracked file (should probably make a gitignore), todos and changes, it looks about like this in the magit status buffer:
It can sometimes be convenient to view the current project, or just a file system in general, as a tree structure much like many bigger IDEs does in a side bar.
(use-package treemacs
:bind
("<f8>" . themkat/treemacs-toggle))
;; caveat: only toggles on selection. selects the treemacs window if not
(defun themkat/treemacs-toggle ()
(interactive)
(if (treemacs-is-treemacs-window-selected?)
(window--delete)
(treemacs-add-and-display-current-project-exclusively)))
While term.el and shell are good enough for some use cases, they do not work well with interactive terminal processes. For Rust, it might be useful to have a terminal buffer with bacon in it, or a Quarkus dev session for a Quarkus project in Java/Kotlin. Also allows us to use standard Emacs keybindings to navigate buffers like C-x b
(helm mini in my setup), which term.el does not support.
;; Does not work on Windows, but we can just avoid compiling dependencies and using it
;; (if I'm ever forced to use Windows again for work or similar)
(use-package vterm
:commands vterm
:custom
(vterm-always-compile-module t)
:hook
(vterm-mode . (lambda ()
;; Settings to mimic dracula I use for zsh.
;; TODO: probably a better way
(setq-local buffer-face-mode-face '(:background "#000000" :foreground "#FFFFFF"))
(buffer-face-mode 1)
(text-scale-adjust 2)))
:bind
("<f7>" . vterm))
Emacs can also be a great editor for editing books, note sand other things. Some people might miss formatting like headers while editing, but that is what org mode is for :) Blogging with org mode is also a fantastic experience! (also, this configuration is written with org-mode!!!)
org mode (maybe move the intro from above?)
(use-package org
:straight nil
:demand t
:custom
(org-startup-with-inline-images t)
(org-startup-folded t)
(org-todo-keyword-faces '(("DONE" . "GREEN")))
(org-hide-emphasis-markers t)
(org-image-actual-width nil)
(org-support-shift-select t)
(org-pretty-entities t))
;; More modern styles in org mode
;; (prettier tables, less eyesores with visible hashes #, built in bullets etc.)
(use-package org-modern
:after org
:custom
(org-modern-fold-stars '(("▶" . "▼") ("▷" . "▽") ("☐" . "☐") ("▹" . "▿") ("▸" . "▾")))
:hook
((org-mode . org-modern-mode)))
;; add a table of contents to sections tagged with TOC on save (updates it by need)
(use-package toc-org
:after org
:hook
(org-mode . toc-org-mode))
Olivetti to improve readability. Olivetti centers the entire buffer like a sheet of paper and truncates the content. This helps my eyes when writing things that are more natural flowing text (articles, books, other org mode stuff).
(use-package olivetti
:if window-system
:after org
:custom
(olivetti-minimum-body-width 100)
(olivetti-body-width 0.8)
:hook
(org-mode . olivetti-mode))
Currently experimenting with presentations from Emacs as well:
;; hiding the mode line can be useful for presentations
(use-package hide-mode-line)
(defun org-tree-slide--start-handler ()
(hide-mode-line-mode 1)
(set-face-attribute 'org-meta-line nil
:foreground (face-attribute 'default :background)
:background (face-attribute 'default :background)))
(defun org-tree-slide--stop-handler ()
(hide-mode-line-mode nil)
(set-face-attribute 'org-meta-line nil
:foreground nil
:background nil))
(use-package org-tree-slide
:config
(add-hook 'org-tree-slide-play-hook #'org-tree-slide--start-handler)
(add-hook 'org-tree-slide-stop-hook #'org-tree-slide--stop-handler))
I sometimes also use LaTeX (or export org to latex and take it from there). Then auctex is useful.
;; Sets the zoom level of latex fragments (in Org Mode)
(defun update-org-latex-fragments ()
(with-current-buffer (current-buffer)
(when (derived-mode-p 'LaTeX-mode 'TeX-mode 'latex-mode 'tex-mode)
(set-default 'preview-scale-function text-scale-mode-amount)
(preview-buffer))))
(add-hook 'text-scale-mode-hook 'update-org-latex-fragments)
;; Issue with package name and providing it.
;; use-package auctex gives an error with "failed to provide feature auctex" because of older naming in files.
;; https://emacs.stackexchange.com/questions/41321/when-to-specify-a-package-name-in-use-packages-ensure-tag/41324#41324
;; (use-package tex
;; :ensure auctex
;; :defer t
;; :config
;; ;; Preview of LaTeX formulae, tables, tikz drawings etc.
;; (setq TeX-auto-save t)
;; (setq TeX-parse-self t)
;; ;; make C-. the button for preview in latex mode
;; (define-key LaTeX-mode-map (kbd "C-.") 'preview-buffer)
;; ;; let us use minted with the preview (minted fragments is not previewed :( )
;; (setcdr (assoc "LaTeX" TeX-command-list)
;; '("%`%l%(mode) -shell-escape%' %t"
;; TeX-run-TeX nil (latex-mode doctex-mode) :help "Run LaTeX")))