Skip to content

Latest commit

 

History

History
1009 lines (770 loc) · 26.4 KB

init.org

File metadata and controls

1009 lines (770 loc) · 26.4 KB

Emacs configuration

Introduction

Here is the source of my Emacs configuration. Create the files by saving by calling make setup.

Emacs setup

In this section, I keep track of the way I install Emacs on various environments.

Elementary

Download the tar sources, then

sudo apt install libjansson-dev libtree-sitter-dev
./configure --with-modules --tree-sitter
make
sudo make install

Mac OS

From scratch

Dependencies

-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

Install

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

From the brew formula

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

Package management

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)

Main script

Global variables

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))

Custom configuration

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)

Theme

Default settings

Remove GUI garbage and increase the font size.

(tool-bar-mode 0)
(set-face-attribute 'default nil :height 140 :family "Source code pro")

Theme

  • tango-dark
  • deeper-blue
(load-theme (intern (nth (random (length themes)) themes)))

Mode line

Powerline

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))

Emojis

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)))

General UX

Startup screen

Use my personal startup file instead of the default one.

(setq initial-buffer-choice (expand-file-name "welcome.org" my-init-dir))

Ivy

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)))

Subword

Using subword-mode is more convenient in PascalCase / camelCase languages

(use-package subword
  :hook (prog-mode . subword-mode))

Prompts

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

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))

Performances

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))

Editing

French keyboard setup

I use an AZERTY keyboard, which requires loading iso-transl to support all its keys.

(use-package iso-transl)

Mac special setup

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))

Parentheses

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)

Auto completion

(use-package company
  :ensure t
  :init
  (global-company-mode))

Code checking

(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))))

Auto formatting

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))

Tree sitter

(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)))

Backups

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)))

Starcoder

(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)))

Devdocs

(use-package devdocs
  :ensure t
  :commands (devdocs-install)
  :bind (("C-c C-h" . devdocs-peruse)))

Windmove

(use-package windmove
  :config (windmove-default-keybindings))

Navigation

imenu

(global-set-key (kbd "C-c i") #'imenu)

Treemacs

Treemacs is a nice tree layout file explorer for Emacs.

(use-package treemacs
  :ensure t
  :commands (treemacs)
  :bind (("<f5>" . treemacs)))

ripgrep

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)))

Project management

git

Use magit, OF COURSE

(use-package magit
  :ensure t
  :commands (magit-status))

Project

(use-package project)

Task runner

(use-package task-runner
  :bind ("<f4>" . task-runner-run-task))

Test watcher

(use-package test-watcher)

Shell

Environment variables

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

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)))

vterm

(use-package vterm
  :ensure t
  :no-require t
  :commands (vterm))

Org mode

Basic configuration

Clock table indentation

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 "-> "))))

Org initialization

(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})));"))

Package declaration

(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"))

Mouse

(use-package org-mouse
  :after (org))

Agenda

(use-package org-agenda
  :bind (("C-c o a" . org-agenda-list)))

Clock

(use-package org-clock
  :bind (("C-c o j" . org-clock-goto)))

Capture

(use-package org-capture
  :bind (("C-c o c" . org-capture)))

Async

(use-package ob-async
  :no-require t
  :after (org))

Lisp

(use-package elisp-mode
  :bind (:map emacs-lisp-mode-map
		 ("C-c C-b" . eval-buffer)))

LSP (eglot)

(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))])))))

Sonarlint

(autoload 'sonar-visit-file-page "sonar" nil t)

Web

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))

Javascript

Eglot

  (use-package js
    :mode (("\\.mjs\\'" . js-mode))
    :bind (:map js-mode-map
			("M-." . xref-find-definitions))
    :config
    (add-hook 'js-mode-hook #'eglot-ensure))

Typescript

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))

Eslint

   (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)))))

JSON

(use-package json-ts-mode
  :commands (json-ts-mode)
  :mode "\\.json\\'")

NVM

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))

Swagger

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)))

Prettier

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)))

Node modules support

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)))

Comint extras

(autoload 'comint-extras-node-repl "comint-extras" "" t)

PHP

php-mode

(use-package php-mode
  :mode "\\.php\\'")

eglot

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)))

Docker

dockerfile-mode

(use-package dockerfile-mode
  :ensure t)

docker

(use-package docker
  :ensure t
  :commands (docker))

TRAMP

(use-package tramp-container
  :after (tramp))

Markdown

(use-package markdown-mode
  :ensure t
  :mode "\\.md\\'")

YAML

(use-package yaml-mode
  :ensure t)

TRAMP

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))

Project libs

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)))

Processing

(autoload 'p5js-start-for-buffer "p5js" nil t)

Rest

(use-package restclient
  :ensure t
  :commands restclient-mode)

GraphQL

(use-package graphql-mode
  :ensure t)

Termux

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))

LLM

(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"))

Mongo

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)
                       ""))))

Local variables