Skip to content

clarete/emacs.d

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

My Emacs Setup

Intro

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

./screenshot.png

Make sure you checkout @guilhermecomum’s Emacs Config. A whole lot of things present on this configuration were extracted from Guilherme’s config.

Package Management

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://raw.githubusercontent.com/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)

General

Input Encoding

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

Paths

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)

Shell

Use GNU screen as my default shell

(setq explicit-shell-file-name "/usr/bin/screen")

Extra Emacs Lisp files

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)

Filesystem Navigation

(use-package dired
  :straight (:type built-in)
  :hook ((dired-mode . hl-line-mode))
  :custom
  (dired-recursive-copies 'always)
  (dired-listing-switches "-fgGhLv"))

Overall Look & Feel

User Interface

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

Theme

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)

Icons

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

Modeline

(use-package doom-modeline
  :config
  (setq doom-modeline-height 25)
  (setq doom-modeline-bar-width 1)
  (doom-modeline-mode 1))

Fonts

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

Fringe

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

Writing

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

Key Bindings

Text Editing

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

Text Navigation

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

Globally accessible Org Mode features

(define-key global-map "\C-cl" 'org-store-link)
(define-key global-map "\C-ca" 'org-agenda)

Text Editing

General

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

Display Line Numbers

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

Autocomplete

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

Snippets

(use-package yasnippet
  :init
  :config
  (setq yas-verbosity 1)
  (yas-load-directory "~/.emacs.d/snippets")
  (yas-reload-all)
  (yas-global-mode 1))

Parenthesis

(use-package rainbow-mode)
(use-package rainbow-delimiters
  :hook (prog-mode . rainbow-delimiters-mode))
(use-package smartparens
  :init
  (smartparens-global-mode t))

Multicursor

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

Flymake

Custom Fringe Icon

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

Show errors with markers on the sideline

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

Flyspell

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

Projectile

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

Language Server Protocol

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

Programming Modes

Protobuf

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

Rust

(use-package rustic
  :config
  (setq rustic-format-on-save t)
  (setq rustic-lsp-client 'eglot))

JavaScript

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

Other Web Stuff

(use-package jinja2-mode)

Packages

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

Terminal

(use-package vterm)

Vendorized Modes

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)

Org Mode

Look & Feel

General

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

Customize font for the document title

(custom-theme-set-faces
 'user
 '(org-document-title
   ((t (:inherit default :weight bold :underline nil :background nil)))))

Swap * and - with nice looking UTF-8 bullets in unordered lists

(font-lock-add-keywords
 'org-mode
 '(("^ +\\([-*]\\) "
    (0 (prog1 ()
         (compose-region (match-beginning 1) (match-end 1) ""))))))

Change the default size of the headers

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

Swap * with # symbols in headers

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

Editting

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

Babel

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

Agenda & TODO

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

Misc

(setq org-log-done t
      org-agenda-sticky t)

Native Compilation

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)

macos

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