Here is the source of my Emacs configuration. Create the files by saving by calling make setup.
In this section, I keep track of the way I install Emacs on various environments.
Download the tar sources, then
sudo apt install libjansson-dev libtree-sitter-dev
./configure --with-modules --tree-sitter
make
sudo make install
-lemutls_w: d12frosted/homebrew-emacs-plus#554
(setenv "LIBRARY_PATH"
(string-join
'("/opt/homebrew/opt/gcc/lib/gcc/current"
"/opt/homebrew/opt/libgccjit/lib/gcc/current"
"/opt/homebrew/opt/gcc/lib/gcc/current/gcc/aarch64-apple-darwin23/14")
":"))
brew install imagemagick libgccjit
Clone repo
git clone git://git.savannah.gnu.org/emacs.git .emacs-build
Install
make configure
./configure --with-imagemagick --with-json --with-tree-sitter --with-xwidgets --with-native-compilation
brew tap railwaycat/emacsmacport
brew install emacs-mac --with-xwidgets --with-natural-title-bar --with-librsvg
ln -s /usr/local/opt/emacs-mac/Emacs.app ~/Applications
I use the default package manager with use-package
.
Setup vc-use-package
too:
(require 'package)
(add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") t)
(package-initialize)
These are common useful variables for getting the emacs init dir and the path to my personal local packages.
(eval-and-compile
(defconst my-init-dir (file-name-directory (or load-file-name (buffer-file-name))))
(defconst my-site-lisp (concat my-init-dir "site-lisp/"))
(add-to-list 'load-path my-site-lisp))
Move the custom configuration file outside of the init file to avoid blending custom configuration with the init sources.
(setq custom-file (concat my-init-dir "custom-file.el"))
(load custom-file 'no-error)
Remove GUI garbage and increase the font size.
(tool-bar-mode 0)
(set-face-attribute 'default nil :height 140 :family "Source code pro")
- tango-dark
- deeper-blue
(load-theme (intern (nth (random (length themes)) themes)))
I am using my own theme forked from powerline-default-theme
.
(defface my-mode-line-highlight
'((t :inherit mode-line
:background "gray32"))
"Mode line face for highlighted elements")
(defface my-mode-line-highlight-active
'((t :inherit 'mode-line
:background "gray50"))
"Mode line face for active highlighted elements")
(defun my-powerline-theme ()
"Setup the default mode-line."
(interactive)
(setq-default
mode-line-format
'("%e"
(:eval
(let* ((active (powerline-selected-window-active))
(mode-line-buffer-id (if active 'mode-line-buffer-id 'mode-line-buffer-id-inactive))
(mode-line (if active 'mode-line 'mode-line-inactive))
(lhs (list (powerline-raw
(if buffer-read-only "RO " "RW ")
(if (buffer-modified-p) 'my-mode-line-highlight-active 'my-mode-line-highlight)
'l)
(when (> (length (tab-bar-tabs)) 1)
(powerline-raw (thread-last (tab-bar-tabs)
(assq 'current-tab)
(assq 'name)
cdr)
mode-line 'l))
(powerline-buffer-id `(mode-line-buffer-id ,mode-line) 'l)
(powerline-raw " " mode-line)
(powerline-process mode-line)
(powerline-narrow mode-line 'l)
(powerline-raw " " mode-line)
))
(rhs (list
(powerline-vc mode-line 'r)
(powerline-raw (when flymake-mode (flymake--mode-line-counters)))
(unless window-system
(powerline-raw (char-to-string #xe0a1) mode-line 'l))
)))
(concat (powerline-render lhs)
(powerline-fill mode-line (powerline-width rhs))
(powerline-render rhs)))))))
(use-package powerline
:ensure t
:config
(setq powerline-default-separator 'bar
powerline-display-hud nil)
(my-powerline-theme))
emojify
displays ascii emojis using images. This is both prettier
and faster to render.
(use-package emojify
:ensure t
:hook (prog-mode . emojify-mode)
:custom (emojify-emoji-styles '(unicode)))
Use my personal startup file instead of the default one.
(setq initial-buffer-choice (expand-file-name "welcome.org" my-init-dir))
I use ivy instead of the basic read interface, because it has a good matching system and is lighter than helm.
Ivy comes with counsel and swiper, that implements a lot of common Emacs commands with the Ivy interface.
ivy-use-virtual-buffers
also includes recent files and bookmarks
in counsel’s buffer list.
enable-recursive-minibuffers
is not directly related to ivy, but
I set it up here as most of my interactions with the minibuffer
goes through ivy. It allows opening a new minibuffer while a
minibuffer is already opened, which I used at my job for finding
information on my current task while creating branches, for
example.
(defun init/setup-ivy ()
"Setup the ivy package."
(ivy-mode 1)
(setq ivy-use-virtual-buffers t)
(setq enable-recursive-minibuffers t))
(use-package ivy
:ensure t
:config (init/setup-ivy))
(use-package counsel
:ensure t
:after (ivy)
:config (counsel-mode 1))
(use-package swiper
:ensure t
:after (ivy)
:bind (("C-s" . swiper)))
Using subword-mode is more convenient in PascalCase / camelCase languages
(use-package subword
:hook (prog-mode . subword-mode))
Use y-or-n-p
instead of yes-or-no-p
to have a smoother experience.
(defalias 'yes-or-no-p 'y-or-n-p)
helpful improves the emacs help commands with more information.
(use-package helpful
:ensure t
:bind
("C-h k" . helpful-key)
("C-c C-d" . helpful-at-point)
("C-h C" . helpful-command)
("C-h o" . helpful-symbol)
:custom
(counsel-describe-function-function #'helpful-callable)
(counsel-describe-variable-function #'helpful-variable))
I use esup to profile my emacs startup from time to time.
(use-package esup
:ensure t
:commands (esup)
:init (setq esup-depth 0))
gcmh minimizes the interferences of the garbage collector with the user’s activity. There are more details on the package’s page.
(use-package gcmh
:ensure t
:config (gcmh-mode 1))
I use an AZERTY keyboard, which requires loading iso-transl
to
support all its keys.
(use-package iso-transl)
Rebind some MacOS keys to have proper super of control, alt gr, etc…
(when (eq system-type 'darwin)
(setq mac-option-modifier 'meta
mac-right-option-modifier nil
mac-command-modifier 'super))
Enable some core modes in order to get electric pairing and showing the parenthesis matching the one under the cursor.
(electric-pair-mode 1)
(show-paren-mode 1)
(use-package company
:ensure t
:init
(global-company-mode))
(use-package flymake
:ensure
:hook (prog-mode . flymake-mode)
:bind ((:map flymake-mode-map
("C-c ! l" . flymake-show-buffer-diagnostics)
("C-c ! p" . flymake-goto-prev-error)
("C-c ! n" . flymake-goto-next-error))))
I basically never want trailing whitespaces
(add-hook 'before-save-hook #'delete-trailing-whitespace)
I use editorconfig as much as possible so that I can share part my project config with my teammates.
(use-package editorconfig
:ensure t
:if (locate-library "editorconfig")
:hook (prog-mode . editorconfig-mode))
(use-package tree-sitter
:ensure t)
(use-package tree-sitter-langs
:ensure t
:hook ((php-mode . tree-sitter-hl-mode)
(js-mode . tree-sitter-hl-mode)
(typescript-mode . tree-sitter-hl-mode)))
Stop having backups files inside my projects and committing them by mistake.
(setq backup-directory-alist
`((".*" . ,temporary-file-directory)))
(setq auto-save-file-name-transforms
`((".*" ,temporary-file-directory t)))
(use-package starhugger
:ensure t
:bind (("C-c <tab>" . starhugger-trigger-suggestion)
:map starhugger-inlining-mode-map
("<M-return>" . starhugger-accept-suggestion)
("<M-S-down>" . starhugger-show-next-suggestion)
("<M-S-up>" . starhugger-show-next-suggestion)))
(use-package devdocs
:ensure t
:commands (devdocs-install)
:bind (("C-c C-h" . devdocs-peruse)))
(use-package windmove
:config (windmove-default-keybindings))
(global-set-key (kbd "C-c i") #'imenu)
Treemacs is a nice tree layout file explorer for Emacs.
(use-package treemacs
:ensure t
:commands (treemacs)
:bind (("<f5>" . treemacs)))
Ripgrep is my preferred way to search for occurences in a project. It is fast, and deadgrep offers a really nice interface for Emacs.
(use-package deadgrep
:ensure t
:bind (("C-c C-s" . deadgrep)))
Use magit, OF COURSE
(use-package magit
:ensure t
:commands (magit-status))
(use-package project)
(use-package task-runner
:bind ("<f4>" . task-runner-run-task))
(use-package test-watcher)
Use exec-path-from-shell to import shell’s environment variables into Emacs.
(use-package exec-path-from-shell
:ensure t
:custom ((exec-path-from-shell-variables '("PATH" "MANPATH" "NODE_OPTIONS")))
:config (exec-path-from-shell-initialize))
xterm-color is a replacement for ansi-color that is faster and has more feature.
Here is the comint / shell-mode configuration
(defun my-remove-ansi-from-comint ()
"Remove ansi-color from comint filters."
(setq comint-output-filter-functions
(remove 'ansi-color-process-output comint-output-filter-functions)))
(defun my-shell-mode-config-xterm-color ()
"Configure xterm-color for shell-mode."
;; Disable font-locking in this buffer to improve performance
(font-lock-mode -1)
;; Prevent font-locking from being re-enabled in this buffer
(make-local-variable 'font-lock-function)
(setq font-lock-function (lambda (_) nil))
(setq comint-output-filter-functions
(remove 'ansi-color-process-output comint-output-filter-functions))
(add-hook 'comint-preoutput-filter-functions 'xterm-color-filter nil t)
(setq-local comint-terminfo-terminal "xterm-256color"))
Then, we configure eshell:
(defun my-eshell-before-prompt-xterm-color ()
"Preserve text properties on eshell prompts."
(setq xterm-color-preserve-properties t))
(defun my-eshell-env-xterm-color ()
"Setup eshell environment for xterm-color."
(setenv "TERM" "xterm-256color"))
And compilation-mode:
(defun my-xterm-color-configure-compilation ()
"Setup xterm-color in compilation-mode"
(message "Loading xterm-colors for compilation")
(with-eval-after-load 'compile
(setq compilation-environment '("TERM=xterm-256color"))
(add-hook 'compilation-start-hook
(lambda (proc)
;; We need to differentiate between compilation-mode buffers
;; and running as part of comint (which at this point we assume
;; has been configured separately for xterm-color)
(when (eq (process-filter proc) 'compilation-filter)
;; This is a process associated with a compilation-mode buffer.
;; We may call `xterm-color-filter' before its own filter function.
(set-process-filter
proc
(lambda (proc string)
(funcall 'compilation-filter proc
(xterm-color-filter string)))))))))
Finally, we can import and configure the package:
(defun my-xterm-color-init ()
"First setup for xterm-color."
(my-remove-ansi-from-comint)
(my-xterm-color-configure-compilation))
(use-package xterm-color
:ensure t
:config (my-xterm-color-init)
:hook ((shell-mode . my-shell-mode-config-xterm-color)
(eshell-mode . my-eshell-env-xterm-color)
(eshell-before-prompt . my-eshell-before-prompt-xterm-color)
(compilation-mode . my-shell-mode-config-xterm-color)))
(use-package vterm
:ensure t
:no-require t
:commands (vterm))
The org clock table indents its entries using the LateX symbol
\emsp
, which renders badly in org buffers. I override it with my
own indent function extracted from a stackexchange discussion.
(defun my/org-clocktable-indent-string (level)
(if (= level 1)
""
(let ((str "+"))
(while (> level 2)
(setq level (1- level)
str (concat str "--")))
(concat str "-> "))))
(defun my/init-org ()
;; Override clock table ident function with mine
(advice-add 'org-clocktable-indent-string :override #'my/org-clocktable-indent-string)
;; Automatically add syntax coloration on org src blocks
(setq org-src-fontify-natively t)
(setq org-hide-emphasis-markers t)
(add-hook 'org-mode-hook #'(lambda () (org-indent-mode t)))
(org-babel-do-load-languages 'org-babel-load-languages
'((shell . t)
(sql . t)))
;; Allow using top-level await in js code blocks
(setq org-babel-js-function-wrapper "(async function(){%s
})().then(result => require('process').stdout.write(require('util').inspect(result, { maxArrayLength: Infinity})));"))
(use-package org
:mode ("\\.org\\'" . org-mode)
:bind (("C-c o t" . org-todo-list))
:config (my/init-org)
:custom
(org-startup-folded t "Start all org documents in overview mode"))
(use-package org-mouse
:after (org))
(use-package org-agenda
:bind (("C-c o a" . org-agenda-list)))
(use-package org-clock
:bind (("C-c o j" . org-clock-goto)))
(use-package org-capture
:bind (("C-c o c" . org-capture)))
(use-package ob-async
:no-require t
:after (org))
(use-package elisp-mode
:bind (:map emacs-lisp-mode-map
("C-c C-b" . eval-buffer)))
(defcustom my-eglot-typescript-args '()
""
:safe t)
(use-package eglot
:bind (:map eglot-mode-map
("C-c SPC" . eglot-code-actions))
:init
;; Unless I update my emacs, add a polyfill for project-name
;; (unless (fboundp 'project-name)
;; (cl-defgeneric project-name (project)
;; "A human-readable name for the project.
;; Nominally unique, but not enforced."
;; (file-name-nondirectory (directory-file-name (project-root project)))))
:config
(add-to-list 'eglot-server-programs
`((js-mode typescriptreact-mode typescript-mode) .
("typescript-language-server"
"--stdio"
:initializationOptions
(:preferences (:includeInlayParameterNameHints "none"
:includeInlayPropertyDeclarationTypeHints t
:includeInlayFunctionLikeReturnTypeHints t)
:plugins [(:name "typescript-eslint-language-service"
:location ,(expand-file-name "node_modules/typescript-eslint-language-directory" user-emacs-directory))])))))
(autoload 'sonar-visit-file-page "sonar" nil t)
Use web-mode for editing HTML files
(use-package web-mode
:ensure t
:mode "\\.html\\'")
Use rainbow-mode to get a preview of the hexa / rgb color we are reading.
(use-package rainbow-mode
:ensure t
:hook (js-mode css-mode web-mode))
(use-package js
:mode (("\\.mjs\\'" . js-mode))
:bind (:map js-mode-map
("M-." . xref-find-definitions))
:config
(add-hook 'js-mode-hook #'eglot-ensure))
Setup the basic typescript-mode:
(use-package typescript-mode
:ensure t
:mode (("\\.ts\\'" . typescript-mode)
("\\.tsx\\'" . typescriptreact-mode))
:config
;; Eglot uses the major mode name as the languageId to send to the LSP server.
;; However, typescript-language-server has a different langaugeId for typescript
(define-derived-mode typescriptreact-mode typescript-mode
"Typescript TSX")
(add-to-list 'tree-sitter-major-mode-language-alist '(typescriptreact-mode . tsx))
(add-hook 'typescript-mode-hook #'eglot-ensure))
(defun my-eslint-fix ()
"Run eslint --fix on the current buffer"
(interactive)
(let ((default-directory (project-root (project-current))))
(async-shell-command (format "npx eslint --fix %s" (buffer-file-name)))))
(use-package json-ts-mode
:commands (json-ts-mode)
:mode "\\.json\\'")
Setup the correct node version when opening a JS file.
(defun my-nvm-use-for ()
(interactive)
(condition-case error
(nvm-use-for-buffer)
(t (message "NVM error: %s" error))))
(use-package nvm
:ensure t
:hook ((js-mode json-mode typescript-mode dired-after-readin magit-mode) . my-nvm-use-for))
Setup a custom command to be able to edit yaml in multi-line comments.
(use-package yaml-comment
:after (typescript-mode)
:bind (:map js-mode-map
("C-c y" . yaml-comment-edit-at-point)
:map typescript-mode-map
("C-c y" . yaml-comment-edit-at-point)))
Enable prettier formatting at save for all the web files.
(use-package prettier-js
:ensure t
:hook ((js-mode . prettier-js-mode)
(typescript-mode . prettier-js-mode)
(web-mode . prettier-js-mode)
(css-mode . prettier-js-mode))
:custom ((prettier-js-show-errors . nil)))
add-node-modules-path
automatically adds the node_modules bin
folder to the path. This allows using the project tools when
opening a file (ex: eslint, prettier).
Make sure to add the hooks as late as possible, as some other packages relies on it.
(use-package add-node-modules-path
:ensure t
:hook ((js-mode . add-node-modules-path)
(typescript-mode . add-node-modules-path)))
(autoload 'comint-extras-node-repl "comint-extras" "" t)
(use-package php-mode
:mode "\\.php\\'")
This package requires php-language-server to work. Follow the instructions on the readme to do so.
(use-package eglot
:hook ((php-mode . eglot-ensure)))
(use-package dockerfile-mode
:ensure t)
(use-package docker
:ensure t
:commands (docker))
(use-package tramp-container
:after (tramp))
(use-package markdown-mode
:ensure t
:mode "\\.md\\'")
(use-package yaml-mode
:ensure t)
Make sure the remote PATH will be properly set when connecting with tramp on SSH:
(with-eval-after-load 'tramp
(add-to-list 'tramp-remote-path 'tramp-own-remote-path))
Load project libraries that are in the projects
folder. These are
not committed as it depends on the machine.
(let ((projects-dir (concat my-site-lisp "projects/")))
(message projects-dir)
(dolist (lib (directory-files projects-dir t "\.el$"))
(load-file lib)))
(autoload 'p5js-start-for-buffer "p5js" nil t)
(use-package restclient
:ensure t
:commands restclient-mode)
(use-package graphql-mode
:ensure t)
Configuration for termux environment
(when (getenv "ANDROID_DATA")
(xterm-mouse-mode 1)
(global-set-key (kbd "<mouse-5>") #'next-line)
(global-set-key (kbd "<mouse-4>") #'previous-line))
(use-package gptel
:ensure t
:commands (gptel)
:bind (("C-c g m" . gptel-menu)
("C-c g r" . gptel-rewrite-menu))
:init
(setq gptel-backend
(gptel-make-anthropic "Claude"
:key (auth-source-pick-first-password :host "anthropic"))
gptel-model "claude-3-haiku-20240307"))
A function that inserts a random ObjectId in the current buffer for easily writing mocks and tests.
(defun my-insert-object-id ()
"Write a random ObjectId at the current position."
(interactive)
(insert (let ((hex "0123456789abcdef"))
(mapconcat (lambda (_) (string (aref hex (random 16))))
(number-sequence 1 24)
""))))