This repository contains my Emacs setup. Although these configurations are catered to my own taste, you might find a few things useful and improve the configuration of your Emacs too. For the complete experience, it does depend on a few other files available on my dotdfiles repository though. Make sure you check it out too.
Here’s a screenshot of me editing this file :)
Make sure you checkout @guilhermecomum’s Emacs Config. A whole lot of things present on this configuration were extracted from Guilherme’s config.
Despite its terrible name, straight does provide good value when it comes to installing and managing Emacs Lisp extension packages. This is the snippet for setting it up
(setq package-enable-at-startup nil)
(defvar bootstrap-version)
(let ((bootstrap-file
(expand-file-name "straight/repos/straight.el/bootstrap.el" user-emacs-directory))
(bootstrap-version 5))
(unless (file-exists-p bootstrap-file)
(with-current-buffer
(url-retrieve-synchronously
"https://github.com/raw/raxod502/straight.el/develop/install.el"
'silent 'inhibit-cookies)
(goto-char (point-max))
(eval-print-last-sexp)))
(load bootstrap-file nil 'nomessage))
(straight-use-package 'use-package)
(setq straight-use-package-by-default t)
(use-package use-package-ensure-system-package)
From Doom emacs: Contrary to what many Emacs users have in their configs, you don’t need more than this to make UTF-8 the default coding system
(set-language-environment "UTF-8")
Load environment variables from the shell
(use-package exec-path-from-shell)
(setq exec-path-from-shell-variables '("GOPATH" "PATH" "MANPATH"))
(exec-path-from-shell-initialize)
There’s no place like home
(setq default-directory "~/")
Store auto-save and backup files in a temporary directory. The default is to save these files in the same directory as the original file. Which doesn’t play very nicely with directories under a version control tool, like git, without an extra step of adding them to a .gitignore
file. Which is annoying, so we deal with it once and for all here
(setq custom-file
(if (boundp 'server-socket-dir)
(expand-file-name "custom.el" server-socket-dir)
(expand-file-name (format "emacs-custom-%s.el" (user-uid)) temporary-file-directory)))
(load custom-file t)
(setq backup-directory-alist
`((".*" . ,temporary-file-directory)))
(setq auto-save-file-name-transforms
`((".*" ,temporary-file-directory t)))
(setq tramp-auto-save-directory temporary-file-directory)
Use GNU screen as my default shell
(setq explicit-shell-file-name "/usr/bin/screen")
This is where I have some Emacs Lisp code I wrote myself and won’t really ever package it as these are very custom and personal features
(add-to-list 'load-path (expand-file-name "lisp" user-emacs-directory))
(require 'lc-defs)
(use-package dired
:straight (:type built-in)
:hook ((dired-mode . hl-line-mode))
:custom
(dired-recursive-copies 'always)
(dired-listing-switches "-fgGhLv"))
Get rid of some clutter, we don’t really do mouse here anyway…
(scroll-bar-mode 0)
(menu-bar-mode 0)
(tool-bar-mode 0)
Other misc user interface settings
(column-number-mode) ;; Basic config for columns
(setq ring-bell-function 'ignore) ;; No freaking bell
(setq inhibit-splash-screen t) ;; No splash screen
(setq inhibit-startup-screen t)
More reliable inter-window border. The native bordebr “consumes” a pixel of the fringe on righter-most splits
(setq window-divider-default-places t
window-divider-default-bottom-width 0
window-divider-default-right-width 1)
(window-divider-mode +1)
Configure dimming of the buffers that are not active.
(use-package dimmer
:config
(dimmer-mode t)
(setq dimmer-fraction 0.5))
Unique buffer names
(setq uniquify-buffer-name-style 'reverse)
Changing the frame title to show my host name and full path of file open on the current buffer. If `exwm’ is enabled, this won’t really do anything but won’t do any harm either.
(setq frame-title-format
(list (format "%s %%S: %%j " (system-name))
'(buffer-file-name "%f" (dired-directory
dired-directory "%b"))))
Start from a clean slate when loading another theme
(defadvice load-theme (before clear-previous-themes activate)
"Clear existing theme settings instead of layering them"
(mapc #'disable-theme custom-enabled-themes))
Define the default theme
(load-theme 'doom-tomorrow-night t)
(use-package nerd-icons)
(use-package nerd-icons-dired
:hook (dired-mode . nerd-icons-dired-mode))
(use-package treemacs-nerd-icons
:config
(treemacs-load-theme "nerd-icons"))
(use-package doom-modeline
:config
(setq doom-modeline-height 25)
(setq doom-modeline-bar-width 1)
(doom-modeline-mode 1))
(global-font-lock-mode 1) ;; Always do syntax highlighting
(transient-mark-mode 1) ;; Highlight mark region
(let ((myfont "Fira Code")) ;; Font face settings
(set-frame-font myfont t t)
(set-face-attribute 'default nil
:family myfont
:height 120
:weight 'normal
:width 'normal))
Setup fringe style. Notice that this must always happen after setting the theme, otherwise the fringe colors are set to the default
of the previously selected theme (in my case, the default theme).
;; enable the fringe mode
(fringe-mode 15)
;; Configure fringe colors
(set-face-attribute
'fringe nil
:foreground (face-foreground 'default)
:background (face-background 'default))
(set-face-attribute
'line-number nil
:foreground (face-foreground 'default)
:background (face-background 'default))
(defun lc/writing-hook ()
"Stuff that's gonna happen when I put the writting cap."
;; hipster-mode activate
(olivetti-mode)
;; Give that beautiful little top padding
(setq-local header-line-format " ")
;; Do away with line numbers, it's the
;; content that's important here, not
;; the quantity!!!
(setq-local display-line-numbers-type nil)
(display-line-numbers-mode nil))
(use-package olivetti
:custom (olivetti-body-width 100)
:hook ((markdown-mode . lc/writing-hook)
(org-mode . lc/writing-hook)))
;; Comments
(global-set-key [(ctrl c) (c)] #'comment-region)
(global-set-key [(ctrl c) (d)] #'uncomment-region)
;; join lines
(global-set-key [(ctrl J)] (lambda () (interactive) (join-line -1)))
;; scrolling without changing the cursor
(global-set-key [(meta n)] (lambda () (interactive) (scroll-up 1)))
(global-set-key [(meta p)] (lambda () (interactive) (scroll-down 1)))
;; scrolling other window
(global-set-key
[(meta j)] (lambda () (interactive) (scroll-other-window 1)))
(global-set-key
[(meta k)] (lambda () (interactive) (scroll-other-window -1)))
(define-key global-map "\C-cl" 'org-store-link)
(define-key global-map "\C-ca" 'org-agenda)
;; Do not wrap lines
(setq-default truncate-lines t)
;; spaces instead of tabs
(setq-default indent-tabs-mode nil)
;; Complain about trailing white spaces
(setq show-trailing-whitespace t)
;; Also highlight parenthesis
(show-paren-mode 1)
;; scroll smoothly
(setq scroll-conservatively 10000)
;; Clipboard shared with the Desktop Environment. I wonder if the
;; `exwm' integration would work without this line.
(setq select-enable-clipboard t)
(add-hook 'prog-mode-hook #'display-line-numbers-mode)
(add-hook 'conf-mode-hook #'display-line-numbers-mode)
(add-hook 'text-mode-hook #'display-line-numbers-mode)
Notice that the writing configuration disables the above settings for both org-mode
and markdown-mode
.
Company mode is a standard completion package that works well with lsp-mode
(use-package company
:hook (after-init . global-company-mode)
:config
(setq company-idle-delay .3)
(setq company-minimum-prefix-length 10)
(setq company-tooltip-align-annotations t)
(global-set-key (kbd "TAB") #'company-indent-or-complete-common))
(use-package company-box
:hook (company-mode . company-box-mode))
(use-package yasnippet
:init
:config
(setq yas-verbosity 1)
(yas-load-directory "~/.emacs.d/snippets")
(yas-reload-all)
(yas-global-mode 1))
(use-package rainbow-mode)
(use-package rainbow-delimiters
:hook (prog-mode . rainbow-delimiters-mode))
(use-package smartparens
:init
(smartparens-global-mode t))
(global-set-key (kbd "C-S-c C-S-c") 'mc/edit-lines)
(global-set-key (kbd "C->") 'mc/mark-next-like-this)
(global-set-key (kbd "C-<") 'mc/mark-previous-like-this)
(global-set-key (kbd "C-c C-<") 'mc/mark-all-like-this)
(when (fboundp 'define-fringe-bitmap)
(define-fringe-bitmap 'my-rounded-fringe-indicator
(vector #b00000000
#b00000000
#b00000000
#b00000000
#b00000000
#b00000000
#b00000000
#b00011100
#b00111110
#b00111110
#b00111110
#b00011100
#b00000000
#b00000000
#b00000000
#b00000000
#b00000000)))
(use-package sideline-flymake
:hook (flymake-mode . sideline-mode)
:custom
(flymake-error-bitmap '(my-rounded-fringe-indicator compilation-error))
(flymake-note-bitmap '(my-rounded-fringe-indicator compilation-info))
(flymake-warning-bitmap '(my-rounded-fringe-indicator compilation-warning)))
(use-package flyspell)
(use-package flyspell-correct-popup)
(setq ispell-program-name "aspell")
(ispell-change-dictionary "english")
(defun lc/flyspell/switch-dict ()
(interactive)
(let* ((dic ispell-current-dictionary)
(change (if (string= dic "pt_BR") "english" "pt_BR")))
(ispell-change-dictionary change)
(message "Dictionary switched from %s to %s" dic change)))
(global-set-key (kbd "<f5>") #'lc/flyspell/switch-dict)
(define-key flyspell-mode-map (kbd "C-;") 'flyspell-correct-wrapper)
(use-package projectile
:init
(projectile-mode +1)
:bind (("C-c p" . projectile-command-map)
("M-[" . projectile-previous-project-buffer)
("M-]" . projectile-next-project-buffer))
:config
(setq projectile-indexing-method 'hybrid
projectile-sort-order 'recently-active
compilation-read-command nil
projectile-comint-mode t)
(add-to-list 'projectile-globally-ignored-directories "node_modules")
(add-to-list 'projectile-globally-ignored-files "yarn.lock")
:custom
(projectile-globally-ignored-buffers
'("*scratch*" "*lsp-log*" "*xref*"
"*EGLOT" "*Messages*" "*compilation"
"*vterm*" "*Flymake")))
(use-package eglot
:hook
(go-mode . eglot-ensure)
(python-mode . eglot-ensure)
:bind (:map eglot-mode-map
("C-c ." . eglot-code-actions)
("C-c e r" . eglot-rename)
("C-c e f" . eglot-format)
("M-?" . xref-find-references)
("M-." . xref-find-definitions)
("C-c x a" . xref-find-apropos)
("C-c f n" . flymake-goto-next-error)
("C-c f p" . flymake-goto-prev-error)
("C-c f d" . flymake-show-project-diagnostics))
:custom
(eglot-autoshutdown t)
(eglot-menu-string "LSP")
:config
;; Just `rust-analyzer` won't do it if it is installed via `rustup`.
;; Check out https://rust-analyzer.github.io/manual.html#rustup for
;; more details
(add-to-list 'eglot-stay-out-of 'eldoc)
(setf (alist-get 'rustic-mode eglot-server-programs)
(split-string
(shell-command-to-string
"rustup which --toolchain stable rust-analyzer"))))
(use-package protobuf-mode
:hook (protobuf-mode . (lambda ()
;; extend CC mode with my config
(c-add-style "protobuf"
'((c-basic-offset . 4)
(indent-tabs-mode . nil)))
(c-set-style "protobuf")
;; enable line numbers
(display-line-numbers-mode))))
(use-package rustic
:config
(setq rustic-format-on-save t)
(setq rustic-lsp-client 'eglot))
(setq js-indent-level 2)
(use-package js2-mode
:config
(setq js2-basic-offset 2)
(setq js2-strict-trailing-comma-warning nil)
(setq js2-strict-missing-semi-warning nil)
(add-to-list 'auto-mode-alist '("\\.js\\'" . js2-mode)))
(use-package jinja2-mode)
The all mighty and magical magit
(use-package magit)
Builtins that need to be required
(require 'dired-x)
(require 'uniquify)
(require 'tramp) ;; ssh and local `sudo' and `su'
Extensions installed from the external world
(use-package password-store)
(use-package neotree
:bind ([f8] . neotree-toggle)
:config
(setq neo-autorefresh nil)
(setq neo-smart-open t)
(with-eval-after-load 'neotree
(define-key neotree-mode-map (kbd "h") 'neotree-hidden-file-toggle)))
(use-package vterm)
This is the path where I copy Emacs extensions that aren’t available in any pre-packaged repository, like melpa etc.
(add-to-list 'load-path (expand-file-name "site-lisp" user-emacs-directory))
And these are the modules themselves
(require 'peg-mode)
(use-package org
:straight (:type built-in)
:config
(setq org-fontify-whole-heading-line t
org-fontify-done-headline t
org-fontify-quote-and-verse-blocks t)
(setq-local line-spacing 1))
(custom-theme-set-faces
'user
'(org-document-title
((t (:inherit default :weight bold :underline nil :background nil)))))
(font-lock-add-keywords
'org-mode
'(("^ +\\([-*]\\) "
(0 (prog1 ()
(compose-region (match-beginning 1) (match-end 1) "•"))))))
(custom-set-faces
'(org-level-1 ((t (:inherit outline-1 :height 1.50 :weight extra-bold))))
'(org-level-2 ((t (:inherit outline-2 :height 1.30 :weight extra-bold))))
'(org-level-3 ((t (:inherit outline-3 :height 1.20 :weight extra-bold))))
'(org-level-4 ((t (:inherit outline-4 :height 1.15 :weight bold))))
'(org-level-5 ((t (:inherit outline-5 :height 1.10 :weight bold))))
'(org-level-6 ((t (:inherit outline-6 :height 1.05 :weight semi-bold)))))
I saw this beautiful style implemented for the Markdown Mode in this reddit post, then I wanted to do the same with Org Mode. After quite a bit of digging, I got this idea from this stackoverflow answer, threw some more lisp on it and ended up with the following snippet
(font-lock-add-keywords
'org-mode '(("^\\(*+\\)"
(0 (prog1 ()
(let* ((start (match-beginning 0))
(end (match-end 0))
(length (- end start)))
(add-face-text-property start end '(:foreground "#616161" :height 0.8))
(dotimes (i length)
(compose-region (+ i start) (+ i start 1) "#"))))))))
Load some Org Mode extensions
(require 'org-tempo)
(require 'org-agenda)
(require 'ob-ditaa)
(require 'ob-plantuml)
Set a kanban-ish workflow for managing TODO items
(setq org-todo-keywords
'((sequence "TODO" "DOING" "BLOCKED" "|" "DONE" "ARCHIVED")))
(setq org-todo-keyword-faces
'(("TODO" . "red")
("DOING" . "yellow")
("BLOCKED" . org-warning)
("DONE" . "green")
("ARCHIVED" . "blue")))
(setq org-ditaa-jar-path "~/.emacs.d/contrib/ditaa/ditaa0_9.jar")
(setq org-plantuml-jar-path "~/.emacs.d/contrib/plantuml/plantuml.jar")
(setq org-confirm-babel-evaluate nil)
(eval-after-load 'org
(add-hook 'org-babel-after-execute-hook 'org-redisplay-inline-images))
(org-babel-do-load-languages
'org-babel-load-languages
'((ditaa . t)
(dot . t)
(gnuplot . t)
(latex . t)
(plantuml . t)
(python . t)
;; (R . t)
(ruby . t)))
The following code will list all the Org Mode files within my directory of choice and feed it into the `org-agenda-files'
variable.
(let ((directory-with-my-org-files "~/org"))
(setq org-agenda-files
(condition-case err
(directory-files directory-with-my-org-files t
directory-files-no-dot-files-regexp)
(file-missing nil))))
(setq org-log-done t
org-agenda-sticky t)
Emacs can compile its lisp flavor into native code. This is powerful indeed, but it requires some settings to feel a little nicer. First, we want to compile the Emacs Lisp code asynchronously to continue to operate smoothly, then we want to make it a bit less noisy in case the compilation wants to report progress its or warnings.
(when (fboundp 'native-compile-async)
(setq comp-deferred-compilation t))
(setq native-comp-async-report-warnings-errors nil
warning-minimum-level :error)
(when (eq system-type 'darwin)
(setq mac-option-modifier 'alt)
(setq mac-command-modifier 'meta)
;; Keys for visiting next & previous windows
(global-set-key (kbd "<A-tab>") #'other-window)
(global-set-key (kbd "<A-S-tab>")
#'(lambda () (interactive) (other-window -1)))
;; Keys for visiting next & previous frame
(global-set-key (kbd "M-`") #'other-frame)
(global-set-key (kbd "M-~") #'(lambda () (interactive) (other-frame -1)))
;; sets fn-delete to be right-delete
(global-set-key [kp-delete] 'delete-char)
(menu-bar-mode 1))