This is the Emacs config of Massimo Lauria (C) 2009-2024.
This configuration is under massive and continue change, and things break every day. I just put it online so you can cut & paste whatever you may find useful.
This configuration assume a modern Emacs installation (>=24). If your Emacs is less recent than that, the configuration will revert to a minimal version.
Literate configuration: this Emacs configuration is on the way to be
a literate configuration developed as an org-mode
file. The plan
is that the very document you are reading right now will be the main
part of the configuration itself. At this point this is not true but
I am putting in place the infrastructure for it. I will slowly move
part of the main config inside this README.org
file.
The variable base-config-path
keeps the path where the
configuration is installed. I add that to the load-path
, and we
also add the path of 3rd parties packages, which are packages I keep
in the repository because they are not on melpa
(yet).
(setq 3rdparties-packages-path (concat base-config-path "3rdparties/"))
(add-to-list 'load-path base-config-path)
(add-to-list 'load-path 3rdparties-packages-path)
The best way to read Info files is in emacs. The client merges all info dir files in a single index. Therefore among other things I make the index to show my local info documents as well. For example my copy of “Structure and Interpretation of Computer Programs”.
(if (not (boundp 'Info-directory-list))
(setq Info-directory-list nil))
(add-to-list 'Info-directory-list (concat base-config-path "/info"))
When Emacs is lauched as an app (on MacOSX) or from a gui command (in linux) there is often the chance that the running environment does not contain some environment variables or does not set them up appropriately. These functions allow me to care of such issues as long as they arise in my setup. I don’t claim any generality here.
First I have two functions to manage variables for the runtime environment where Emacs has been executed from.
(defun environment-variable-add-to-list (varname element &optional append)
"Add ELEMENT from the list in the enviroment variable VARNAME.
VARNAME is considered as a list of elements like \"aa:bb:cc\". If
VARNAME is undefined of empty, it defines it. If ELEMENT is
already in the list, the function won't do anything.
There is no guarantee on the actual order of the elements in the
list."
(let ((separator (if (eq system-type 'windows-nt) ";" ":"))
tmplist)
(if (getenv varname)
(setq tmplist (split-string
(getenv varname)
separator)))
(add-to-list 'tmplist element append 'string-equal)
(setenv varname (mapconcat 'identity tmplist ":"))))
(defun environment-variable-rm-from-list (varname element)
"Remove ELEMENT from the list in the enviroment variable VARNAME.
VARNAME is considered as a list of elements like \"aa:bb:cc\". If
ELEMENT is not in the list, the function won't do anything.
There is no guarantee on the actual order of the elements in the
list."
(let ((separator (if (eq system-type 'windows-nt) ";" ":"))
tmplist)
(if (getenv varname)
(setq tmplist (split-string
(getenv varname)
separator)))
(setenv varname (mapconcat 'identity (remove element tmplist) ":"))))
I use this functions primarily to fix PATH
.
(defun environment-add-path (newpath &optional append)
"Add NEWPATH to the PATH environment variable and to exec-path,
Ignore if the path does not exists."
(when (file-directory-p newpath)
(add-to-list 'exec-path newpath append 'string-equal)
(environment-variable-add-to-list "PATH" newpath append)))
Some useful paths to add to the environment. In particular tools
like Cask
, cabal
, cargo
, install their executables files in
a corresponding hidden folder inside $HOME
.
(environment-add-path "/usr/local/bin") ;; Homebrew (MacOS)
(environment-add-path (concat (getenv "HOME") "/.cask/bin")) ;; Cask (for Elisp)
(environment-add-path (concat (getenv "HOME") "/.local/bin")) ;; Local/bin (GNU/Linux)
Actually the right way to set system-wide exec-paths on Mac is to use `/etc/paths.d’ files. I pick up these paths as well.
(with-temp-buffer
(condition-case nil
(dolist (file (cons "/etc/paths" (directory-files "/etc/paths.d/" t)))
(if (not (file-directory-p file))
(insert-file-contents file)))
(error nil))
(dolist (path (split-string (buffer-string) "\n" t))
(if (file-directory-p path)
(environment-add-path path))))
Some basic personal information. I used to keep these in a separate file but I believe it does not achieve any further privacy.
;; ;; My personal stuff, that I don't want to share.
(setq user-full-name "Massimo Lauria")
(setq user-mail-address "[email protected]")
(setq user-organisation "Università degli Studi di Roma \"La Sapienza\"")
(setq user-mail-signature "\
Massimo Lauria
http://www.massimolauria.net
Sapienza University of Rome
Dipartimento di Scienze Statistiche
Piazzale Aldo Moro, 5
00185 Roma, Italy
")
Basic editing setup. For example start scrolling few lines from top of bottom.
(setq scroll-margin 8)
(setenv "LANG" "it_IT.UTF-8")
I have my keyboard setup in a minor mode. This is mostly for editing, movements and things like that. Commands for specific tools are setup in the appropriate sections.
;; My keyboard configuration
(use-package mxl-keyboard
:commands (mxl-keyboard-mode mxl-keyboard-global-mode)
:diminish ""
:config
(mxl-keyboard-global-mode)
:demand t)
I often type C-x C-z
by mistake, which hides the current Emacs frame
via susped-frame
. I hate it.
(global-unset-key (kbd "C-x C-z"))
Having previous-buffer
and next-buffer
on the arrow keys is
a trap. I tend to use reach for these command automatically and then
I find myself going around buffer until I find the one I was looking
for. Too inefficient. I’d rather disable such keys and use some
version of switch-to-buffer
.
(global-unset-key (kbd "C-x <left>"))
(global-unset-key (kbd "C-x <right>"))
(global-unset-key (kbd "C-x C-<left>"))
(global-unset-key (kbd "C-x C-<right>"))
When I delete the rest of a line via C-k
, I want it to remove the
newline in the end as well. But only when the cursor is at the
first column.
(setq kill-whole-line t)
Function keys: easy to reach commands that are not immediately related to editing.
(global-set-key [f1] 'help)
;; (global-set-key [f2] 'ispell-buffer) ;; in the spellcheck setup
(global-set-key [f3] 'kmacro-start-macro-or-insert-counter)
(global-set-key [f4] 'kmacro-end-or-call-macro)
I work mostly with UTF-8 file, Hence UTF-8 is the default coding for buffers.
(prefer-coding-system 'utf-8)
(set-default-coding-systems 'utf-8)
(set-terminal-coding-system 'utf-8)
(set-keyboard-coding-system 'utf-8)
(setq default-buffer-file-coding-system 'utf-8)
The same setting seems to be needed for the clipboard.
(setq x-select-request-type '(UTF8_STRING COMPOUND_TEXT TEXT STRING))
(set-clipboard-coding-system 'utf-8)
I often need to write greek or math symbols (e.g. α, ⊕), and since
I know LaTeX it is convenient to use the same markup to insert them
in regular text. Nevertheless it is inconvenient to have that on by
default. I activate it when needed typing C-\
(toggle-input-method
).
(setq default-input-method 'TeX)
(set-input-method nil)
For me it is useful to have the agenda to remind of italian holidays, especially if they corresponds to vacation days. I override the original values of these variables because I don’t care to know about holidays that do not affect me.
;; Non-religious holidays
(setq holiday-general-holidays
'((holiday-fixed 1 1 "Capodanno")
(holiday-fixed 3 8 "Giornata internazionale della donna")
(holiday-fixed 5 1 "Festa dei Lavoratori")
(holiday-fixed 4 25 "Liberazione dal Nazifascismo")
(holiday-fixed 6 2 "Festa della Repubblica")))
;; Catholics holidays that induce vacations
(setq holiday-christian-holidays
'((holiday-fixed 12 8 "Immacolata Concezione")
(holiday-fixed 12 25 "Natale")
(holiday-fixed 12 26 "Santo Stefano")
(holiday-fixed 1 6 "Epifania")
(holiday-easter-etc -52 "Giovedì grasso")
(holiday-easter-etc -47 "Martedì grasso")
(holiday-easter-etc 0 "Pasqua")
(holiday-easter-etc 1 "Pasquetta")
(holiday-fixed 8 15 "Ferragosto")
(holiday-fixed 11 1 "Ognissanti")))
;; No other religious holidays induce vacation days in Italy.
(setq holiday-bahai-holidays nil)
(setq holiday-hebrew-holidays nil)
(setq holiday-islamic-holidays nil)
In Italy the weekly calendar starts from Monday, hence I set
calendar-week-start-day
accordingly. It is also nice to have the
names of months and weekdays translated.
(setq calendar-week-start-day 1
calendar-day-name-array ["Domenica" "Lunedì" "Martedì" "Mercoledì"
"Giovedì" "Venerdì" "Sabato"]
calendar-month-name-array ["Gennaio" "Febbraio" "Marzo" "Aprile" "Maggio"
"Giugno" "Luglio" "Agosto" "Settembre"
"Ottobre" "Novembre" "Dicembre"])
Several packages (e.g. Org-mode) need to recognize what a weekday
name or a month name is. Package parse-time
provides this
functionality, but it only knows about English words. I can add the
Italian ones to parse-time-weekdays
and parse-time-months
when
package parse-time
is loaded. I also add the timezones.
(defconst parse-time-weekdays
'(("dom" . 0) ("lun" . 1) ("mar" . 2) ("mer" . 3) ("gio" . 4) ("ven" . 5) ("sab" . 6)
("domenica" . 0) ("lunedì" . 1) ("martedì" . 2) ("mercoledì" . 3)
("giovedì" . 4) ("venerdì" . 5) ("sabato" . 6))
"Italian weekdays to add to `parse-time-weekdays'.")
(defconst parse-time-months-ita
'(("gen" . 1) ("feb" . 2) ("mar" . 3) ("apr" . 4) ("mag" . 5) ("giu" . 6)
("lug" . 7) ("ago" . 8) ("set" . 9) ("ott" . 10) ("nov" . 11) ("dic" . 12)
("gennaio" . 1) ("febbraio" . 2) ("marzo" . 3) ("aprile" . 4)
("maggio" . 5) ("giugno" . 6) ("luglio" . 7) ("agosto" . 8)
("settembre" . 9) ("ottobre" . 10) ("novembre" . 11) ("dicembre" . 12))
"Italian manths to add to `parse-time-months'.")
(use-package parse-time
:config
(setq parse-time-months (append parse-time-months parse-time-months-ita))
(setq parse-time-weekdays (append parse-time-weekdays parse-time-weekdays-ita))
(add-to-list 'parse-time-zoneinfo '("cet" 3600 t) t) ;; Central European Time
(add-to-list 'parse-time-zoneinfo '("cest" 7200) t) ;; Central European Summer Time
)
The most important visual setup for a text editor is the font. We set
the default font. The symbol-font
is the fallback needed for some
math symbols. I use nerd-fonts
for icons and symbols.
(setq default-font "DejaVu Sans Mono")
(setq symbol-font "DejaVu Sans Mono")
(set-face-attribute 'default nil
:family default-font
:width 'normal
:height 180)
(use-package nerd-icons
:commands (nerd-icons-faicon nerd-icons-octicons)
:custom
(nerd-icons-font-family "Symbols Nerd Font Mono"))
(use-package nerd-icons-dired
:hook
(dired-mode . nerd-icons-dired-mode))
In GUI emacs we use a specific font family for the nerd icons. To make
this nerd-fonts
to work in terminal we need to setup a single merged
font like the ones at https://www.nerdfonts.com. I setup alacritty
to use DejaVu Sans Mono Nerd Font
and nerd fonts work fine
there too.
I like Emacs to open in a wide frame at the center of the screen, on startup, at least on these systems with floating window managers. No internal border except for a small fringe on the left side. I disable any other decorations. Text is more readable with some additional space between lines.
(setq initial-frame-alist '((top . 0.5) ;; center vertical position
(left . 0.5))) ;; center horizontal position
(setq default-frame-alist `((height . 64)
(width . 120)
(tool-bar . nil)
(line-spacing . 0.2)
(internal-border-width . 0)
(border-width . 0)
(vertical-scroll-bars . nil)
(horizontal-scroll-bars . nil)
(left-fringe . 8)
(right-fringe . 0)
(tool-bar-lines . 0)
(menu-bar-line . 0)
))
Hint: To discover the properties of some text on the screen the
command C-u C-x =
gives them all: font, style, font-lock, char code.
The color theme I usually go for is Zenburn, but now I am trying modus-vivendi for a while.
(defun mxl/tune-modus-theme ()
"Make some changes to the theme"
(custom-set-faces
`(org-block-begin-line
((t
(:slant italic
:background ,(modus-themes-get-color-value 'bg-inactive)
:foreground ,(modus-themes-get-color-value 'bg-active)))))))
(use-package modus-themes
:init
(setq modus-themes-disable-other-themes t
modus-themes-italic-constructs t
modus-themes-bold-constructs nil
modus-themes-mixed-fonts t
modus-themes-variable-pitch-ui nil ) ;; basic customizations
(setq modus-themes-org-blocks 'gray-background) ;; org source blocks
(setq modus-themes-mode-line '(accented borderless) ;; customizations for
modus-themes-region '(accented bg-only)) ;; version <4 shipped with
;; with Emacs 29.1
;; change defaults in modus themes
(setq modus-themes-common-palette-overrides
'((border-mode-line-active unspecified) ;; borderless mode-line (1)
(border-mode-line-inactive unspecified) ;; borderless mode-line (2)
(fg-region unspecified))) ;; syntax visible in region
;; change defaults in modus vivendi specifically
(setq modus-vivendi-palette-overrides
'((bg-region bg-cyan-subtle) ;; region color
(bg-mode-line-active bg-cyan-subtle)
(fg-heading-1 keyword))) ;; org-heading
;; change defaults in modus operandi specifically
(setq modus-operandi-palette-overrides
'((bg-region bg-cyan-intense) ;; region color
(bg-mode-line-active bg-cyan-intense)
(fg-heading-1 keyword))) ;; org-heading
:config
(add-hook 'modus-themes-after-load-theme-hook #'mxl/tune-modus-theme)
(modus-themes-load-theme 'modus-vivendi)
(global-set-key (kbd "M-<f5>") 'modus-themes-toggle))
Syntax/Spell checkers add decorations to the text to signal mistakes. The defaults decorations are either too intrusive or too faint, so I customize them.
- Violet for spelling/grammar mistakes
- Red for syntax mistakes in programming languages
- Yellow for syntax/style warnings
- Blue for syntax/style notes
(custom-set-faces
'(flycheck-error ((t (:underline "Red"))))
'(flycheck-warning ((t (:underline "Yellow"))))
'(flycheck-info ((t nil)))
'(flymake-error ((t (:underline "Red1"))))
'(flymake-warning ((t (:underline "yellow"))))
'(flymake-note ((t (:underline "deep sky blue"))))
'(flyspell-duplicate ((t (:underline (:color "magenta" :style wave)))))
'(flyspell-incorrect ((t (:underline (:color "magenta" :style wave)))))
'(writegood-duplicates-face ((t nil)))
'(writegood-passive-voice-face ((t (:underline (:color "magenta" :style wave)))))
'(writegood-weasels-face ((t (:underline (:color "magenta" :style wave))))))
We use Doom Emacs Modeline (see here). Doom Modeline is minimal, well organized and nice to look at (especially with icons enabled). This also makes obsolete other customizations hacks I did, that often were incompatible with other emacs setup.
Note that Doom Emacs Modeline requires Nerd fonts. They are already be in my setup.
(use-package doom-modeline
:ensure t
:init
(setq doom-modeline-icon t)
(setq doom-modeline-major-mode-icon t)
(setq doom-modeline-buffer-state-icon t)
(setq doom-modeline-enable-word-count t)
(setq doom-modeline-buffer-encoding t)
(setq doom-modeline-env-enable-python nil)
(setq doom-modeline-bar-width 6)
(doom-modeline-mode 1))
Sometimes I want to hide my mode-line, maybe in conjunction with
something like olivetti-mode
.
(defun mxl/toggle-mode-line ()
"Hide/show mode-line in the current buffer"
(interactive)
(if (local-variable-p 'mode-line-format)
(kill-local-variable 'mode-line-format)
(setq-local mode-line-format nil))
(force-mode-line-update))
(defun mxl/hide-mode-line ()
"Hide mode-line in the current buffer"
(interactive)
(setq-local mode-line-format nil))
(defun mxl/show-mode-line ()
"Hide mode-line in the current buffer"
(interactive)
(if (local-variable-p 'mode-line-format)
(kill-local-variable 'mode-line-format)))
The file specified on the command line argument should be the first to
be presented to the user, of course. But, as it is often the case,
Emacs is launched without arguments and the startup buffer is usually
*scratch*
, which is rarely what I need. I’d rather start with some
*Untitled*
text file.
(setq initial-buffer-choice
(lambda ()
(if (buffer-file-name) ;; if an actula file is already open,
(current-buffer) ;; leave it as it is.
(new-untitled-file))))
(defun new-untitled-file ()
"Create/Return an anonymous text buffer"
(let ((buffer)
(default-name "*Untitled*"))
(or (get-buffer default-name)
(with-current-buffer (get-buffer-create default-name)
(text-mode)
(current-buffer)))))
(defun open-untitled-file ()
"Create/Select an anonymous text buffer"
(interactive)
(switch-to-buffer (new-untitled-file)))
(global-set-key (kbd "C-n") #'open-untitled-file)
Emacs definitely can improve in term of usability. Here we setup few packages in order to make it more polished and nice to use.
The completion framework used to be based on Helm
but now I am
moving toward the vertico
, marginalia
, orderless
, consult
,
embark
mechanism. It integrates better with Emacs and does not
require a specific implementation for every use case. Let’s see how it
goes. Package vertico
provides a basic vertical choice in the
minibuffer, with filtering and completion, while marginalia
shows
documentation on the side of the candidate. The stuff typed at the
minibuffer is used to complete/filter the choices, and orderless
allows some kind of fuzzy matching.
;; Ignore case in completion.
(setq read-file-name-completion-ignore-case t
read-buffer-completion-ignore-case t
completion-ignore-case t
enable-recursive-minibuffers t)
(use-package orderless
:custom
(completion-styles '(orderless basic)) ; Use orderless
(completion-category-defaults nil) ; I want to be in control!
(completion-category-overrides
'((file (styles basic ; For `tramp' hostname completion with `vertico'
orderless)))))
(use-package vertico
:ensure t
:custom
(vertico-count 10) ; Number of candidates to display
(vertico-resize nil)
:init
(require 'orderless) ; should be loaded before
:config
;; (vertico-reverse-mode) ;; if I want prompt on the bottom line
(require 'vertico-directory)
(vertico-mode 1)
(marginalia-mode 1))
(use-package marginalia
:commands (marginalia-mode)
:custom
(marginalia-max-relative-age 0)
(marginalia-align 'left))
(use-package embark
:commands (embark-act embark-dwim)
:bind (:map vertico-map
("M-q" . embark-act)
:map embark-general-map
("M-q" . abort-recursive-edit)
("M-SPC" . abort-recursive-edit)
)
:custom
;; Show keys at the bottom (and above the minibuffer)
(embark-indicators '(embark-verbose-indicator embark-highlight-indicator embark-isearch-highlight-indicator))
(embark-verbose-indicator-display-action '(display-buffer-at-bottom)))
Package consult
allows to have different categories of completions.
It also shows file preview but honestly I don’t like that. It builds
on top of vertico
and has a lot of tools like consult-ripgrep
.
I use it to search in files and to switch buffer. I load an extra package to add all files in the current project to buffer switcher.
(use-package consult
:bind (("C-M-s" . consult-ripgrep)
("C-x C-b" . consult-buffer)
("M-SPC" . consult-buffer)
("C-f" . mxl/consult-find-file)
("M-y" . consult-yank-from-kill-ring)
:map vertico-map
("M-SPC" . abort-recursive-edit))
:custom
(consult-preview-key 'nil) ; no preview
:config
(defun mxl/consult-find-file ()
(interactive)
(consult-find "~/"))
(consult-customize
consult-goto-line
consult-line
:preview-key ' any) ;; immediate preview just for these tools
)
(use-package consult-project-extra
:after consult
:config
(defvar consult-project-el-files
`(:name "Project Files (via project.el)"
:category file
:face consult-file
:history file-name-history
:action ,#'consult-project-extra--find-with-concat-root
:enabled ,#'project-current
:items
,(lambda () (consult-project-extra--project-files (project-root (project-current))))))
(add-to-list 'consult-buffer-sources 'consult-project-el-files 'append))
I find convenient to have the choice of minibuffer action displayed in
the minibuffer as helm
does. Apparently this recipe works.
(defun embark-act-with-completing-read (&optional arg)
(interactive "P")
(let* ((embark-prompter 'embark-completing-read-prompter)
(embark-indicators '(embark-minimal-indicator)))
(embark-act arg)))
I use Emacs to write technical papers about math, code documentation, lecture notes for my courses , blog posts, and to edit my websites… and sometimes to prepare slides. Therefore I need to setup a proper environment. I often see many emacs user writing LaTeX with for a tool which is barely setup for writing code, and definitely not right for writing prose.
Emacs has a lot of potential in prose writing, especially if you ditch LaTeX and write in Org Mode or Markdown. In this regard I suggest the following reading.
Too few people on Emacs have a decent setup for syntax checking, and even fewer have a decent setup for grammar checking. I don’t claim that my setup is especially clever, but at least it includes spell and grammar check in
- Italian;
- American English;
- British English.
In particular I often write papers with colleagues who prefer the British spelling rather than American one (which I favor), therefore I keep them both.
The entry points of my setup are three functionalities
- Syntax/Grammar check, activated with
mxl/language-check
- Switch between languages with
mxl/language-switch
- Fix interactively the typos
(global-set-key [f2] 'mxl/language-check)
(global-set-key (kbd "M-<f2>") 'mxl/language-switch)
(global-set-key (kbd "M-s") 'flyspell-correct-at-point)
The setup revolves on few packages.
- Ispell (actually Hunspell ) for spell checking;
- Flyspell for spelling errors highlighting and fixing of typos;
- LanguageTool and langtool.el for grammar checking.
Flyspell requires a working setup of Ispell. I setup the latter to
make use of Hunspell, which is the default spellchecker of
Libre/Openoffice and Firefox. Notice that I usually need to place
my dictionaries for hunspell in a non standard directory.
Hunspell look them in the directories in the DICPATH
env
variable. Be careful to have dictionaries for all three languages,
otherwise the setup will fail. Here’s I set up the path.
;; I have hunspell dictionaries installed in my personal folder.
(let ((dicpath-pers (concat (getenv "HOME")
"/personal/dictionaries/hunspell")))
(when (file-directory-p dicpath-pers)
(setenv "DICPATH" dicpath-pers)))
Flyspell highlights typos and strikes out words that are repeated
within a certain distance flyspell-duplicate-distance
, which is
set to 0 because I only want to signal adjacent repetitions.
Notice that I activate flyspell using the first of my preferred
languages, and that I use flyspell-prog-mode
for programming.
Flyspell allows a more interactive interface for fixing typos,
contrary to the default ispell-word
.
(use-package flyspell
:commands (flyspell-mode flyspell-prog-mode flyspell-correct-at-point)
:bind (:map flyspell-mode-map ("C-;" . nil))
:hook ((prog-mode . flyspell-prog-mode)
(text-mode . flyspell-mode))
:custom
(flyspell-duplicate-distance 0) ;; signal as repetitions only adjacent pairs
(flyspell-highlight-flag t) ;; mark mispelled words
(flyspell-issue-message-flag nil) ;; silent checking
(flyspell-persistent-highlight nil) ;; only highlight one word
(flyspell-use-meta-tab nil) ;; disable M-<tab>
:custom-face
(flyspell-duplicate ((t (:underline (:color "magenta" :style wave)))))
(flyspell-incorrect ((t (:underline (:color "magenta" :style wave)))))
:init
(setq ispell-program-name (executable-find "hunspell"))
(setq ispell-dictionary "english") ;; last language by default
(if (not ispell-program-name)
(message "Spell checking disabled: impossible to find correctly installed 'Hunspell'."))
:config
(require 'flyspell-correct)) ;; the command I bind to M-s
This is the code for chosing the preferred languages.
(defun mxl/language-switch ()
"Switch between spell checking languages, in the current buffer."
(interactive)
(let* ((lang (completing-read "Pick the language: "
'("english" "british" "italiano"))))
(ispell-change-dictionary lang)
(setq langtool-default-language
(cond
((string-equal lang "italiano") "it")
((string-equal lang "english") "en")
((string-equal lang "british") "en-GB")
(t "")))
))
Grammar check with Langtool is reasonably easy to setup. The only
caveat is that it need to be installed. When installed, we use
langtool-disabled-rules
to deactivate some checks (e.g.
whitespaces) which generates too many false positives.
(setq langtool-path '("/usr/local/share/languagetool";
"~/.local/share/LanguageTool"
"/snap/languagetool/current/usr/bin"))
(setq langtool-tmp "")
(setq langtool-language-tool-jar (concat (dolist (elt langtool-path langtool-tmp)
(setq elt (expand-file-name elt))
(if (file-directory-p elt) (setq langtool-tmp elt)))
"/languagetool-commandline.jar"))
(when (file-exists-p langtool-language-tool-jar)
(use-package langtool
:config
(setq langtool-mother-tongue "it")'
(setq langtool-disabled-rules "WHITESPACE_RULE")
:commands (langtool-check langtool-check-buffer langtool-switch-default-language)))
The last component is a single function mxl/language-check
.
Allows to
- pick between syntax and grammar check in the current language;
- stop grammar check if one is running;
;; Spell/Grammar check command
(defun mxl/language-check ()
"Launch either spell check or grammar check
Offer a choice between spell checking the buffer, or grammar
checking it. It a region is active the spell check will be
performed on that region. If some grammar checking session is
open, the command will just close it.
"
(interactive)
;; If grammar check is active, close it
(if (and (boundp 'langtool-mode-line-message)
langtool-mode-line-message)
(langtool-check-done)
;; otherwise offer a choice
(let* ((choices '("spelling" "grammar" "none"))
(selection (ido-completing-read "Check for " choices )))
(pcase selection
("spelling"
(if (region-active-p)
(call-interactively 'ispell-region)
(ispell-buffer)))
("grammar" (langtool-check-buffer))
(otherwise nil))
)))
I have few dictionaries of various quality set to be used with sdcv
command line utility. There is an emacs package for it but it is quite
inconvenient to use. Therefore I just extracted from the dictionaries a wordlist that
I use do write a little helper for dictionary search, set to a key.
(defvar sdcv-word-cache nil
"Caches all words from the wordfile.")
(defvar sdcv-word-file "~/personal/dictionaries/wordlists/english_and_italian.txt"
"File containing all words to be looked up.")
(use-package sdcv
:commands (sdcv-search-completion)
:bind ("C-c d" . sdcv-search-completion)
:init
(defun sdcv-search-completion (&optional word)
(interactive)
(unless sdcv-word-cache
(setq sdcv-word-cache
(with-temp-buffer
(insert-file sdcv-word-file)
(split-string (buffer-string) "\n"))))
(sdcv-search-input (or word
(completing-read "Lookup in dictionary: " sdcv-word-cache nil t (word-at-point))
))))
I like when files are, to some extent, of limited width. Unfortunately it is not always possible for technical reasons. There are many options to wrap a text a specific width, either hard coding newlines, or wrapping on the display without modifying the file.
auto-fill-mode
(hard insert line breaks automatically)visual-fill-mode
(visual wrap at window margin)visual-fill-column-mode
(visual wrap at fixed margin)
Some behavior of text filling can be controlled.
(setq sentence-end-double-space nil) ;; no double space after period.
(add-hook 'fill-nobreak-predicate 'fill-single-char-nobreak-p) ;; never break at single char words
(add-hook 'fill-nobreak-predicate 'fill-single-word-nobreak-p) ;; never leave a single word on a row
I am on the fence about auto-fill-mode
. In general I don’t like text
to automatically change while I am writing. I’d rather reformat the
text willingly.
;; Fill/unfill paragraph
(use-package unfill
:bind (:map text-mode-map ( "M-q" . unfill-toggle)))
To me, prose text files should have at most 70 columns. And when word wrapping is hard-coded, the text should be justified. This makes sense at least on pure text files, i.e., ones that are not meant to be interpreted as code or special markup.
(defun mxl/text-mode-setup ()
"Setup for text-mode files
This applies to pure text mode files and not to modes derived
from `text-mode'."
(interactive)
(when (eq 'text-mode major-mode)
(setq fill-column 70)
(setq default-justification 'full)))
(add-hook 'text-mode-hook 'mxl/text-mode-setup)
For text files like org-mode files it is better to enable
auto-fill-mode
. For email messages it is better to use something
like visual-fill-column-mode
. The standard visual-line-mode
matches the text width with the window width, which is annoying on
large horizontal screen.
Notice that it is possible for visual-fill-column-mode
to center
the text setting visual-fill-column-center-text
to t
.
(use-package visual-fill-column
:disabled
:init
(setq-local fill-column 70)
(setq visual-fill-column-center-text nil)
:config
(advice-add 'text-scale-adjust :after #'visual-fill-column-adjust)
:hook ((visual-line-mode . turn-on-visual-fill-column-mode)
(visual-line-mode . turn-off-auto-fill)
(message-mode . visual-line-mode)))
Writing prose is easier in a no-distraction setup. What did Virginia Woolf say? “A Room of One’s Own”.
(use-package writeroom-mode
:init
(setq writeroom-bottom-divider-width 0))
My main TeX/LaTeX setup is based on its own file, but there is
a nice utility which allows to conveniently type the depecated
dollas signs. By typing a dollar, the inline pair \( \)
is
inserted. It toogles between pair \( \)
and \[ \]
everytime the
dollar is typed while at the end of a pair or inside an empty pair.
Recall you can type $
by preceding it with C-q
.
(use-package math-delimiters
:commands (math-delimiters-insert)
:ensure nil)
(with-eval-after-load 'org
(define-key org-mode-map "$" #'math-delimiters-insert))
(with-eval-after-load 'tex ; for AUCTeX
(define-key TeX-mode-map "$" #'math-delimiters-insert))
(with-eval-after-load 'tex-mode ; for the built-in TeX/LaTeX modes
(define-key tex-mode-map "$" #'math-delimiters-insert))
I have quite a setup in init-bibliography
, but I am trying to move
it to this file. A key passage is to go from helm-bibtex
to citar
.
First let’s tell citar
where is the bibtex database and the files.
We also setup some default action to use with embark
. My selection
of actions does not inherit the default embark
actions and therefore
is more focused.
(use-package citar
:bind ("C-c b" . citar-open)
:commands (citar-open citar-open-files citar-open-links)
:init
(setq citar-library-paths '("~/cloud/Papers/")) ;; used outside citar
(setq citar-notes-paths '("~/cloud/Papers/")) ;; notes next to papers
:custom
;; setup for citar-inset-citation
(citar-latex-default-cite-command "cite")
(citar-latex-prompt-for-cite-style nil)
(citar-latex-prompt-for-extra-arguments nil)
;; main bibliography
(citar-bibliography '("~/lavori/latex/bibliografia.bib"))
;; find pdf and org notes for papers
(citar-file-variable "file")
:config
(citar-embark-mode))
;; Actions available
(use-package citar-embark
:after (embark citar)
:config
(setq citar-embark-map
(let ((map (make-sparse-keymap)))
(define-key map (kbd "RET") #'citar-run-default-action)
(define-key map (kbd "c") #'citar-insert-citation)
(define-key map (kbd "f") #'citar-open-files)
(define-key map (kbd "l") #'citar-open-links)
(define-key map (kbd "e") #'citar-open-entry)
(define-key map (kbd "n") #'citar-open-notes)
(define-key map (kbd "o") #'citar-open)
(define-key map (kbd "r") #'citar-insert-reference)
(define-key map (kbd "M-q") #'abort-recursive-edit)
map)))
Setup the look and icons for the bibliography selection. We use
nerd-icons
icons.
(defvar citar-indicator-files-icons
(citar-indicator-create
:symbol (nerd-icons-faicon
"nf-fa-file_o"
:face 'nerd-icons-green
:v-adjust -0.1)
:function #'citar-has-files
:padding " " ; need this because the default padding is too low for these icons
:tag "has:files"))
(defvar citar-indicator-links-icons
(citar-indicator-create
:symbol (nerd-icons-faicon
"nf-fa-link"
:face 'nerd-icons-orange
:v-adjust 0.01)
:function #'citar-has-links
:padding " "
:tag "has:links"))
(defvar citar-indicator-notes-icons
(citar-indicator-create
:symbol (nerd-icons-faicon
"nf-fa-file_text"
:face 'nerd-icons-blue
:v-adjust -0.1)
:function #'citar-has-notes
:padding " "
:tag "has:notes"))
(setq citar-indicators
(list citar-indicator-files-icons
citar-indicator-links-icons
citar-indicator-notes-icons
))
The fight to have email working inside emacs is a stink. I tried
notmuch
and mu4e
but they all fail to work well with gmail in,
mostly because the IMAP interface that gmail exposes is insufficient
to implement many good features of GMAIL. For example, notmuch tags
are not synced between machine and that’s a serious issues.
In the end I just want something to quickly go and write email drafts. This function setup a buffer in textmode, fills it with some template and info mostly useful for bookkeeping. In the end I cut and paste the buffer into gmail client.
(defun mxl/create-mail-draft ()
"Create a mail draft buffer in text-mode
Drafts are stored in hardcoded path '~/personal/maildrafts/'
with timestamped filename.
"
(interactive)
(let ((buffer)
(spos)
(loc "~/personal/maildrafts/")
(name (format-time-string "%Y%m%d-%H%M%S-maildraft.txt" (current-time))))
(switch-to-buffer (find-file-noselect (concat loc name) nil nil nil))
(setq fill-column 70)
(text-mode)
(auto-fill-mode)
(setq default-justification 'left)
(insert "Date: " (format-time-string "%Y/%m/%d at %H:%M:%S" (current-time)) "\n")
(insert "From: " user-full-name " <" user-mail-address ">")
(insert "\n" (make-string fill-column ?—) "\n")
(setq spos (point))
(insert "\n\n\n-- \n")
(insert (or (and (boundp 'user-mail-signature) user-mail-signature) ""))
(goto-char spos)
(save-buffer)))
Using some extension is it possible to write email using emacsclient inside thunderbird. My setup uses the custom editor choice which is setup as
emacsclient -c -F '((name . "floatemacs") (width . 100) (height . 40))' "/path/to/eml"
A new frame for emacsclient opens up. The frame has instance
floatemacs
, so that my i3
configuration makes it floating and puts
it at the center of the screen. (See my i3
configuration file)
Thunderbird uses eml
files for email, hence I need to set them up
(add-to-list 'auto-mode-alist '("\\.eml\\'" . message-mode))
Of course I use 4 spaces for indentation
(setq-default indent-tabs-mode nil) ;; Expand tabs as spaces
(setq-default tab-width 4)
(setq default-tab-width 4)
Removal of trailing whitespace is useful in code. I would do it in text files as well, but working in parallel on LaTeX files makes such removals problematic with co-authors. It makes merges more difficult.
But in code this is not usually an issue. So let’s set automatic deletion of trailing whitespace, but just in programming buffers.
(defun mxl/delete-trailing-whitespace-on-save-on ()
(interactive)
(add-hook 'before-save-hook 'delete-trailing-whitespace 0 t))
(defun mxl/delete-trailing-whitespace-on-save-off ()
(interactive)
(remove-hook 'before-save-hook #'delete-trailing-whitespace t))
(add-hook 'prog-mode-hook #'mxl/delete-trailing-whitespace-on-save-on)
Of course every mode include syntax highlighting for the corresponding type of file. There are more “semantic” ways to highlight pieces of codes. Many of these syntax highlight packages are described in
The first package color parenthesis with dirrerent colors, so that matching parenthesis have the same color. Of course after some nesting the colors repeat. This is especially useful for lisp programming.
(use-package rainbow-delimiters
:diminish ""
:init (setq rainbow-delimiters-max-face-count 4)
:commands rainbow-delimiters-mode
:hook ((emacs-lisp-mode . rainbow-delimiters-mode)
(lisp-interaction-mode . rainbow-delimiters-mode)))
The next package colors each identifier, so that the occurrences of that identifier have the same color than the definition.
(use-package rainbow-identifiers
:diminish ""
:commands rainbow-identifiers-mode)
Next package sets a different background color for each nested code block.
(use-package highlight-blocks
:diminish ""
:commands highlight-blocks-mode)
Next package highlights occurrences of the symbol under the point.
(use-package highlight-symbol
:diminish ""
:commands highlight-symbol-mode)
Next package highlight lisp quotes, so it is not useful for other languages (maybe Scheme?).
(use-package highlight-quoted
:diminish ""
:commands highlight-quoted-mode)
Next package is useful for emacs lisp. It highlight names that are defined in emacs lisp, differentiating functions, variables, builtins, …
(use-package highlight-defined :ensure t
:commands highlight-defined-mode)
This code was stolen by Casey Muratori aka Molly Rocket and highlight some words in code to make annotations. I implement
- TODO (Red)
- NOTE (Green)
- CITE (Blue)
- ALERT (Yellow)
- WARNING (YELLOW)
(setq mxl/annotation-prog-modes
'(python-mode
c -mode
c-mode
go-mode
rust-mode
sh-mode
latex-mode
LaTeX-mode
emacs-lisp-mode))
(make-face 'font-lock-fixme-face)
(make-face 'font-lock-note-face)
(make-face 'font-lock-cite-face)
(make-face 'font-lock-alert-face)
(mapc (lambda (mode)
(font-lock-add-keywords
mode
'(("\\<\\(TODO\\)" 1 'font-lock-fixme-face t)
("\\<\\(NOTE\\)" 1 'font-lock-note-face t)
("\\<\\(ALERT\\)" 1 'font-lock-alert-face t)
("\\<\\(WARNING\\)" 1 'font-lock-alert-face t)
("\\<\\(CITE\\)" 1 'font-lock-cite-face t))))
mxl/annotation-prog-modes)
(modify-face 'font-lock-fixme-face "Red" nil nil t nil nil nil nil)
(modify-face 'font-lock-note-face "Light Green" nil nil t nil nil nil nil)
(modify-face 'font-lock-cite-face "Light Blue" nil nil t nil nil nil nil)
(modify-face 'font-lock-alert-face "Yellow" nil nil t nil nil nil nil)
The compile
, project-compile
and recompile
commands are useful
starting points. I run compilation commands using compile-multi
that
allows me to setup nice build commands. The compilation buffer does
not like ansi color sequences by default. We fix that.
(use-package compile
:config
(add-hook 'compilation-filter-hook 'ansi-color-compilation-filter)
;; error regexp for pyright
(add-to-list 'compilation-error-regexp-alist-alist
'(pyright "^[[:blank:]] \\(. \\):\\([0-9] \\):\\([0-9] \\).*$" 1 2 3))
(add-to-list 'compilation-error-regexp-alist 'pyright))
We do the least possible setup for project
. A remark: I prefer to
use build using the innermost Makefile from the current directory, in
case there are many. This is not necessarily a universal good thing
but works for some of me.
(use-package project
:init
(setq project-vc-merge-submodules nil)
:config
(add-to-list 'project-vc-extra-root-markers "Makefile")
(add-to-list 'project-vc-extra-root-markers ".latexmkrc"))
With that I can do the basic setup of my compilation process.
(setq compile-command nil) ;; no default command
(setq compilation-ask-about-save nil)
(setq compilation-read-command t) ;; ask the command when using
;; `compile' explicitly.
Now we setup compile-multi
to always launch compilations from the
project root. We also setup a default action that asks the compile
command explicitly, and we adapt the recompile
command to work with
this setup.
(defun mxl/compile-default-directory ()
"Find the directory from which to run compilation commands
Uses project.el to find project root and if none, uses the
current directory.
"
(or (and (project-current nil)
(project-root (project-current nil)))
default-directory))
(use-package compile-multi
:commands compile-multi
:bind
(("<f9>" . 'mxl/compile-multi-recompile)
("M-<f9>" . 'compile-multi))
:init
(setq compile-multi-config nil)
(setq compile-multi-default-directory #'mxl/compile-default-directory)
(defun mxl/compile-multi-recompile ()
(interactive)
(if (and compile-command
(equal compilation-directory
(funcall compile-multi-default-directory)))
(recompile)
(compile-multi)))
:config
(push `(t
("Fallback:Edit command" . ,#'(lambda ()
(call-interactively 'compile))))
compile-multi-config))
Here we have an example on how to add compile actions for LaTeX. If the project
for a latex document has a .latexmkrc
file, then we can use
latexmk
to produce that document. These actions won’t be available
in projects which do not contain .latexmkrc
.
(push `(latex-mode
("LaTeX:Run pdflatex" "pdflatex"
TeX-command-extra-options
"-file-line-error"
"-interaction=nonstopmode"
"--synctex=1"
(concat (TeX-master-file) ".tex"))
("LaTeX:Run xelatex" "xelatex"
TeX-command-extra-options
"-file-line-error"
"-interaction=nonstopmode"
"--synctex=1"
(concat (TeX-master-file) ".tex"))
("LaTeX:Run bibtex" "bibtex"
(concat (TeX-master-file)))
("LaTeX:Lint with LaCheck" "lacheck" (concat (TeX-master-file) ".tex"))
("LaTeX:Lint with ChkTeX" "chktex -v6" (concat (TeX-master-file) ".tex"))
("LaTeX:AUCTex Command" . ,#'TeX-command-master))
compile-multi-config)
(push `((file-exists-p ".latexmkrc")
("LatexMK:compile document" . "latexmk")
("LatexMK:clean" . "latexmk -C")
("LatexMK:view externally" . "latexmk -pv"))
compile-multi-config)
To “build” org files there is the org-export-dispatch
command.
I could bind the command directly to F9 but I want to experiment the
integration with compile-multi
. Maybe it will just be one more key
to press most of the time, but it is useful when the org file is
inside some project that has more build options. Those options would
be unavailable when org-export-dispatch
is bound to F9.
Nevertheless I use org export mostly to build PDFs via latex and
beamer, therefore I setup direct actions for those as well.
(push `(org-mode
("Org:PDF via latex" . ,#'(lambda () (org-open-file (org-latex-export-to-pdf nil))))
("Org:PDF via beamer" . ,#'(lambda () (org-open-file (org-beamer-export-to-pdf nil))))
("Org:export (choose)" . ,#'org-export-dispatch)
("Org:export (last)" . ,#'(lambda () (org-export-dispatch t))))
compile-multi-config)
Useful compile/build commands are the ones built into makefiles. I have asked chat-gpt to write me a function that parses Makefiles (I don’t know much emacs-lisp). This example was inspired by https://github.com/mohkale/compile-multi tutorial.
(defun mxl/parse-makefile-for-targets (makefile)
"Parse the MAKEFILE and return a list of valid build targets.
Written by chat-gpt"
(interactive "fMakefile: ")
(let ((targets '()))
(with-temp-buffer
(insert-file-contents makefile)
(goto-char (point-min))
(while (re-search-forward "^\\([a-zA-Z0-9_\\-] \\)\\s-*:[^=]" nil t)
(let ((target (match-string 1)))
;; Ensure it's not a special target or a variable
(unless (string-match-p "^\\." target)
(push target targets))))
(reverse targets))))
(defun mxl/extract-compile-rules-from-makefile ()
(mapcar
#'(lambda (target) (cons (concat "Make:" target) (concat "make " target)))
(mxl/parse-makefile-for-targets "Makefile")))
(push `((file-exists-p "Makefile")
("Make:make" . "make")
,#'mxl/extract-compile-rules-from-makefile)
compile-multi-config)
Sometimes the simplest and most convenient way to build a project is just to run a shell script. This setup makes it possible to run the shell scripts that are in the root folder.
(defun mxl/extract-compile-shell ()
(mapcar
#'(lambda (target) (cons (concat "Shell script:" target) target))
(seq-filter 'file-regular-p (directory-files "./" nil "\\.sh$"))))
(push `(t
,#'mxl/extract-compile-shell)
compile-multi-config)
We all know most of development time is spend looking for error and
fixing them. Here we up set the basic function that Emacs has to
compile and navigate the errors previous-error
and next-error
.
The function keys from F9
to F12
are reserved for development
needs, and the functionality strongly depends on the current mode.
In particular I would like
F9
compile/recompile;F10
debug/interact;F11
previous error;F12
next error.
(global-set-key [f11] 'previous-error)
(global-set-key [f12] 'next-error)
My default behavior is that the compilation window stays visible if there are errors, but will disappear if there are no errors reported by the compilation process. It should be noticed that unfortunately some lint tools exit with zero even in presence of warnings or errors.
(defvar mxl/compilation-window-stays-visible nil
"Whether the compilation windows should stay visible after
a successful compilation.")
(defun mxl/compilation-exit-autoclose (status code msg)
;; If M-x compile exists with a 0
(if (and (eq status 'exit)
(zerop code) ;; check errors
(not mxl/compilation-window-stays-visible))
(progn ;; bury and delete compilation windows
(let ((cmpl-buffername (buffer-name)))
(bury-buffer cmpl-buffername)
(delete-window (get-buffer-window (get-buffer cmpl-buffername)))))
(setq mxl/compilation-window-stays-visible t))
;; Always return the anticipated result of compilation-exit-message-function
(cons msg code))
;; Specify my function (maybe I should have done a lambda function)
(setq compilation-exit-message-function 'mxl/compilation-exit-autoclose)
Sometimes I want to see the compilation window anyway, and for that
I use M-F11
and M-F12
to show it and make it stay visible
across compilations.
(defun mxl/toggle-compilation-window ()
"Show/Hide the window containing the '*compilation*' buffer."
(interactive)
(when-let ((buffer next-error-last-buffer))
(if (get-buffer-window buffer 'visible)
(progn
(setq mxl/compilation-window-stays-visible nil)
(delete-windows-on buffer))
(display-buffer buffer)
(setq mxl/compilation-window-stays-visible t))))
(global-set-key (kbd "M-<f11>") #'mxl/toggle-compilation-window)
(global-set-key (kbd "M-<f12>") #'mxl/toggle-compilation-window)
I like the inferior shell to scroll to bottom when I send code to be executed.
(setq comint-scroll-to-bottom-on-input t)
(setq comint-scroll-to-bottom-on-output t)
(setq comint-move-point-for-output t)
The most convenient way to integrate language features nowadays is to use a language server that implements LSP (Language Server Protocol). I am currenly using Eglot which should provide, and set up on its own, several features as
- completion using
corfu
- integrated help using
eldoc
- jump to/from definitions using
xref
(tryM-,
M-.
keybindings) - code snippets (if
yasnippet
is enabled in advance) - code checking using
flymake
(that I disable) - other features…
(defun mxl/eglot-format-region-or-defun ()
(interactive)
(save-mark-and-excursion
(if (not (region-active-p))
(mark-defun))
(call-interactively 'eglot-format)))
(use-package eglot
:config
(add-to-list 'eglot-stay-out-of 'flymake)
(diminish 'eldoc-mode))
We configure basic keybindings for the modes that use eglot
Python is the programming language I am using the most, nowadays. My setup is not very sophisticated right now. Earlier it was more complex but it was breaking quite often at each change of technology, Ipython, and so on…
My configuration is based around the default python.el
included
with Emacs (there are indeed other python modes around).
Furthermore it uses
eglot
as a LSP client, providing help, completion, symbol search;- requires to run
pip install pyright
to install a decent language server; pyenv
to manage the virtual environments.- standard python as interactive shell (Ipython prompt and completion tend to confuse Emacs if not well configured).
yapfify
to format code.
Binaries will be found in the pyenv
paths.
(add-to-list 'exec-path (concat (getenv "HOME") "/.pyenv/shims"))
(add-to-list 'exec-path (concat (getenv "HOME") "/.pyenv/bin"))
(environment-add-path (concat (getenv "HOME") "/.pyenv/shims"))
(environment-add-path (concat (getenv "HOME") "/.pyenv/bin"))
Setup of compile commands for python. In particular syntax checks via
Flake8
or pylint
, and pytest
commands.
(push `(python-mode
;; pytest
("Python:pytest (all tests)" . "pytest")
("Python:pytest (last failed)" . "pytest --lf")
("Python:pytest (last failed first)" . "pytest --ff")
("Python:pytest (single file)" "pytest --doctest-modules" (file-relative-name buffer-file-name))
;; Syntax/Lint checks
("Python:basic syntax check"
"python" "-m" "py_compile" (file-relative-name buffer-file-name))
("Python:Lint with pylint"
"pylint" (file-relative-name buffer-file-name))
("Python:Lint with flake8"
"flake8" (file-relative-name buffer-file-name))
("Python:Lint with ruff"
"ruff check" (file-relative-name buffer-file-name))
("Python:Lint with pyright"
"pyright" (file-relative-name buffer-file-name)))
compile-multi-config)
Using the key F10 I can toggle the prompt shell buffer connected to this python buffer.
By default C-c C-c
uses python-shell-send-buffer
which does not
execute the content of main by default. I usually need it, therefore
I remap the key.
To format code I use yapfify
. I use this custom function to either
format regions or function definitions.
(defun mxl/yapfify-region-or-defun ()
(interactive)
(if (python-syntax-comment-or-string-p)
(python-fill-paragraph t)
(save-mark-and-excursion
(if (not (region-active-p))
(python-mark-defun))
(call-interactively 'yapfify-region))))
(use-package python
:init
(setq python-indent-guess-indent-offset nil
python-indent-offset 4)
:hook ((python-mode . eglot-ensure))
:bind (:map python-mode-map
("M-q" . mxl/yapfify-region-or-defun)
("C-M-q" . yapfify-buffer)
("<f10>" . python-shell-switch-to-shell)
("M-<f10>" . run-python)
;; Force the execution of the main block
("C-c C-c" . (lambda () (interactive) (python-shell-send-buffer t)))
:map inferior-python-mode-map
("<f10>" . delete-window)))
Now I use Eglot which provides eglot-format
with calls Yapf to
enforce code formatting. I would like to run it automatically
before saving.
Emacs is able to find the appropriate pyenv
Python Environment and
to load it automatically. Which is useful for running syntax
checkers or the Python Shell. Notice that by default pyenv-mode
sets key bidings on C-c C-s
and C-c C-u
, which conflicts with
tons of stuff, including org-schedule
. Hence I disable such
bindings, since pyenv-mode-auto
sets the python environment
automatically according .python-version
. In the rare cases where
I need to switch environment I usually run the command from
the minibuffer.
(use-package pyenv-mode
:bind (:map pyenv-mode-map
("C-c C-s" . nil)
("C-c C-u" . nil))
:commands (pyenv-mode-set pyenv-mode-unset pyenv-mode))
(use-package pyenv-mode-auto
:hook (find-file . pyenv-mode-auto-hook)
:config (pyenv-mode))
Setup C and C modes, using Eglot which in turn uses ccls
by default.
(defun mxl/cc-mode-setup ()
"Setup for C/C modes"
(set-fill-column 78))
(use-package cc-mode
:config
(setq c-basic-offset 4)
(setq c-block-comment-prefix "")
:bind (:map c-mode-map
("RET" . newline-and-indent)
:map c -mode-map
("RET" . newline-and-indent))
:hook ((c-mode . eglot-ensure)
(c -mode . eglot-ensure)
(c-mode . mxl/cc-mode-setup)
(c -mode . mxl/cc-mode-setup)))
One annoyance I had when I started using more complex programming environments than Turbo C/C was that, sure we need a project file, or a Makefile, and so on… but sometimes I just want to write a single program and compile it and run it.
(push `(c-mode
("C programming:compile"
"gcc"
(file-relative-name buffer-file-name)
"-Wall -Werror" "-g" "-o"
(file-name-sans-extension (file-relative-name buffer-file-name)))
("C programming:compile and run"
"gcc"
(file-relative-name buffer-file-name)
"-Wall -Werror" "-g" "-o"
(file-name-sans-extension (file-relative-name buffer-file-name))
"&&"
(concat "./" (file-name-sans-extension (file-relative-name buffer-file-name)))))
compile-multi-config)
(push `(c -mode
("C programming:compile"
"g "
(file-relative-name buffer-file-name)
"-Wall -Werror" "-g" "-o"
(file-name-sans-extension (file-relative-name buffer-file-name)))
("C programming:compile and run"
"g "
(file-relative-name buffer-file-name)
"-Wall -Werror" "-g" "-o"
(file-name-sans-extension (file-relative-name buffer-file-name))
"&&"
(concat "./" (file-name-sans-extension (file-relative-name buffer-file-name)))))
compile-multi-config)
I don’t know much about go programming, but it could be useful to
setup compile-multi
for it.
(push `((file-exists-p "go.mod")
("Go project:run " "go run ./...")
("Go project:format" "gofmt ./...")
("Go project:build " "go build ./...")
("Go project:lint " "golint ./...")
("Go project:vet " "go vet ./..."))
compile-multi-config)
(push `(go-mode
("Go file:run" "go run" (file-relative-name (buffer-file-name)))
("Go file:format" "gofmt" (file-relative-name (buffer-file-name)))
("Go file:build" "go build" (file-relative-name (buffer-file-name)))
("Go file:lint" "golint" (file-relative-name (buffer-file-name)))
("Go file:vet" "go vet" (file-relative-name (buffer-file-name))))
compile-multi-config)
It is nice to have documentation for the stuff we do and read in
elisp code. So it is a good idea to turn on eldoc
if possible.
(add-hook 'emacs-lisp-mode-hook 'turn-on-eldoc-mode)
(add-hook 'lisp-interaction-mode-hook 'turn-on-eldoc-mode)
(add-hook 'ielm-mode-hook 'turn-on-eldoc-mode)
I am not a scheme programmer but just in case I want to finally read “Structure and Interpretation of Computer Programs” I have some minimal setup ready.
(use-package xscheme
:config
(setq scheme-program-name
(or
(executable-find "mit-scheme")
(executable-find "/usr/local/bin/mit-scheme")
(executable-find "/Applications/MITScheme.app/Contents/Resources/mit-scheme")
"scheme"
))
(if (file-name-directory scheme-program-name)
(add-to-list 'exec-path (file-name-directory scheme-program-name) 'append))
:commands (run-scheme scheme-mode))
I don’t have a particularly clever setup for shell programming, even though I should probably take it more seriously. There many bad patterns in shell programming and few good ones. I should massively use snippets. One thing that is needed is to activate shell mode automatically for Zsh scripts.
I don’t know muc shell programming. Maybe I should make templates for good bash programming practices as in
(add-to-list 'auto-mode-alist '("\\.zsh" . sh-mode))
Furthermore it is possible to edit command lines while
using terminal. This setup activates sh-mode
there as well
(add-to-list 'auto-mode-alist '("zsh[a-zA-Z0-9]*" . sh-mode))
(add-to-list 'auto-mode-alist '("bash-fc-[0-9]*" . sh-mode))
There is a nice syntax checker for shell script called
shellcheck
, which is a godsend considering how brittle is
the language. One day I will set it up.
Automatic update of the copyright notices and time stamps. Useful to keep additional track about files age, regardless of git history.
(setq copyright-query nil) ; do not ask before a copyright
(add-hook 'before-save-hook 'copyright-update) ; update copyright if present
(add-hook 'before-save-hook 'time-stamp) ; insert timestamp
I don’t use math packages often, and I should probably use them way more. Anyway there is some support for them in Emacs, so why not having a basic setup?
Mathematica, Octave and Matlab all use files with .m
extension,
which is the same extension of Objective-C files. Since I do not use
either much, I don’t need to fiddle with auto-mode-alist
.
There is run-octave
so I guess run=matlab
should exists.
(use-package matlab
:init
(setq matlab-shell-command
(or
(executable-find "matlab")
(executable-find "/usr/local/bin/matlab")
(executable-find "/Applications/Matlab.app/bin/matlab")))
(setq matlab-indent-function-body t)
:commands (matlab-mode matlab-shell))
(defalias 'run-matlab 'matlab-shell)
Nowadays Mathematica is a super-cool software that works on the network and has great AI. I guess it is nicer to use via the appropriate GUI or on the web. Still, emacs rocks.
(use-package wolfram-mode
:init
(setq wolfram-program
(or
(executable-find "math")
(executable-find "/usr/local/bin/math")
(executable-find "/Applications/Mathematica.app/Contents/MacOS/MathKernel")))
:commands (run-wolfram wolfram-mode))
(defalias 'run-mathematica 'run-wolfram)
In the PDF view, I cat use g
to open the outline, therefore I like
to use the same key to quit it, having a toogle key.
(use-package pdf-outline
:commands (pdf-outline)
:bind (:map pdf-outline-buffer-mode-map
("g" . quit-window)
("RET" . pdf-outline-display-link) ))
;; Setup PDF tools
(use-package pdf-tools
:mode ("\\.pdf" . pdf-view-mode)
:magic ("%PDF" . pdf-view-mode)
:init
(setq pdf-view-use-unicode-ligther nil) ;; make loading faster
:config
(delete 'pdf-outline-minor-mode pdf-tools-enabled-modes)
(pdf-tools-install :no-query)
(pdf-loader-install t)
(add-hook 'pdf-view-mode-hook (lambda ()
(auto-revert-mode 1)
(beacon-mode -1)
))
(setq pdf-view-use-scaling t)
(setq revert-without-query '(".pdf"))
(setq-default pdf-view-display-size 'fit-page)
;; Highlights do not open the annotation window when created
(setq pdf-annot-activate-created-annotations nil)
(defun mxl/pdf-annot-add-text-annotation ()
"Add text annotation but forces activation which is off
by default."
(interactive)
(let ((pdf-annot-activate-created-annotations t))
(call-interactively 'pdf-annot-add-text-annotation)))
;; more fine-grained zooming
(setq pdf-view-resize-factor 1.1)
;; use isearch instead of swiper
(define-key pdf-view-mode-map (kbd "C-s") 'isearch-forward)
;; keyboard shortcut for zooming
(define-key pdf-view-mode-map (kbd " ") 'pdf-view-enlarge)
(define-key pdf-view-mode-map (kbd "=") 'pdf-view-enlarge)
(define-key pdf-view-mode-map (kbd "-") 'pdf-view-shrink)
(define-key pdf-view-mode-map (kbd "0") 'pdf-view-scale-reset)
(define-key pdf-view-mode-map (kbd "W") 'pdf-view-fit-width-to-window)
(define-key pdf-view-mode-map (kbd "H") 'pdf-view-fit-height-to-window)
(define-key pdf-view-mode-map (kbd "P") 'pdf-view-fit-page-to-window)
;; Movements
(define-key pdf-view-mode-map (kbd "u") 'pdf-history-backward)
(define-key pdf-view-mode-map (kbd "o") 'pdf-history-forward)
(define-key pdf-view-mode-map (kbd "i") 'pdf-view-previous-line-or-previous-page)
(define-key pdf-view-mode-map (kbd "k") 'pdf-view-next-line-or-next-page)
(define-key pdf-view-mode-map (kbd "j") 'pdf-view-previous-page)
(define-key pdf-view-mode-map (kbd "l") 'pdf-view-next-page)
(define-key pdf-view-mode-map (kbd "M-u") 'pdf-history-backward)
(define-key pdf-view-mode-map (kbd "M-o") 'pdf-history-forward)
(define-key pdf-view-mode-map (kbd "M-i") 'pdf-view-previous-line-or-previous-page)
(define-key pdf-view-mode-map (kbd "M-k") 'pdf-view-next-line-or-next-page)
(define-key pdf-view-mode-map (kbd "M-j") 'pdf-view-previous-page)
(define-key pdf-view-mode-map (kbd "M-l") 'pdf-view-next-page)
;; Links
(define-key pdf-links-minor-mode-map (kbd "f") 'pdf-links-action-perform)
;; History
(define-key pdf-history-minor-mode-map (kbd "B") nil)
(define-key pdf-history-minor-mode-map (kbd "N") nil)
(define-key pdf-history-minor-mode-map (kbd "l") nil)
(define-key pdf-history-minor-mode-map (kbd "r") nil)
(define-key pdf-history-minor-mode-map (kbd "u") #'pdf-history-backward)
(define-key pdf-history-minor-mode-map (kbd "o") #'pdf-history-forward)
(define-key pdf-history-minor-mode-map (kbd "M-u") #'pdf-history-backward)
(define-key pdf-history-minor-mode-map (kbd "M-o") #'pdf-history-forward)
;; Outline
(define-key pdf-view-mode-map (kbd "g") 'pdf-outline)
;; Open in external apps
(defun mxl/pdf-open-in-xournal()
"Open the current PDF in Xournal, for editing"
(interactive)
(start-process "open-pdf-in-xournal" nil "xournalpp" (buffer-file-name)))
(defun mxl/pdf-open-in-default-viewer()
"Open the current PDF in default viewer"
(interactive)
(let ((viewer
(cond
((eq system-type 'gnu/linux) "xdg-open")
((eq system-type 'darwin) "open")
(t "xdg-open"))))
(start-process "open-pdf-in-viewer" nil viewer (buffer-file-name))))
(define-key pdf-view-mode-map (kbd "X") 'mxl/pdf-open-in-xournal)
(define-key pdf-view-mode-map (kbd "O") 'mxl/pdf-open-in-default-viewer)
;; Margin removal
(defun mxl/pdf-view-toggle-crop()
"Crop/Uncrop according to the bounding box"
(interactive)
(if (pdf-view-current-slice)
(pdf-view-reset-slice)
(pdf-view-set-slice-from-bounding-box)))
(define-key pdf-view-mode-map (kbd "c") 'mxl/pdf-view-toggle-crop)
;; keyboard shortcuts for annotations
(define-key pdf-view-mode-map (kbd "h") 'pdf-annot-add-highlight-markup-annotation)
(define-key pdf-view-mode-map (kbd "t") 'mxl/pdf-annot-add-text-annotation)
(define-key pdf-view-mode-map (kbd "L") 'pdf-annot-list-annotations)
(define-key pdf-view-mode-map (kbd "D") 'pdf-annot-delete))
I manage books via Calibre, which I use to store and manage my PDFs, Ebook, and so on. The package calibredb allows to access and open the books from inside Emacs.
(use-package calibredb
:commands (calibredb)
:init
(setq calibredb-id-width 5)
(setq calibredb-format-all-the-icons nil)
(setq calibredb-format-character-icons nil)
:config
(setq calibredb-root-dir (expand-file-name "~/cloud/Books/"))
(setq calibredb-db-dir (concat calibredb-root-dir "/metadata.db")))
I am not very happy with this setup, but it fits with the fact that I don’t like completion and snippet expansion to step on each other toes.
In my workflow completion should only be about stuff I am typing,
while templates and snippets should trigger only when explicitly
asked. In my configuration templates (actually snippets) all start
with characters '<'
followed by some text. This is unlikely to be
in the text by accident. For this reason I don’t use
yasnippet-snippets
because it fills by typing space with snippets
which trigger out of the blue.
I have a simple setup based on corfu
, which complements vertico
,
which fits the same role in the minibuffer. Contrary to tools like
company
, corfu
uses the same logic and technology of vertico
(e.g. orderless
matching of candidates), and whatever is returned by
completion-at-point
. This setup is very basic but functional, and we
use cape
to add some extensions to it.
(use-package corfu
:custom
(corfu-auto t)
(corfu-auto-delay 0.1)
(corfu-separator ?\s) ;; M-s by default. This inserts orderless
;; field separator
(corfu-quit-no-match 'separator)
(corfu-bar-width 0)
(corfu-left-margin-width 0.1)
(corfu-right-margin-width 0.1)
(corfu-preview-current nil)
(corfu-quit-no-match t) ;; i hate the "no match" warning
:bind
(:map corfu-map ("RET" . nil) ;; RET does not complete
("M-g" . corfu-info-location) ;; show definition
("M-h" . corfu-info-documentation) ;; show help info
("M-o" . corfu-insert)) ;; insert completion candidate
:init
(global-corfu-mode)
:config
(corfu-popupinfo-mode))
;; Usa cape-dabbrev only with prefix length 4, and only in text modes
(defun mxl/add-dabbrev-completion ()
(add-to-list 'completion-at-point-functions (cape-capf-prefix-length #'cape-dabbrev 5)))
(add-hook 'text-mode-hook #'mxl/add-dabbrev-completion)
(use-package cape
:custom
(cape-dabbrev-check-other-buffers 'cape--buffers-major-mode) ;; only look for words in buffers with the same major modes
(cape-dabbrev-min-length 5)
:init
;; (add-to-list 'completion-at-point-functions #'cape-dabbrev)
(add-to-list 'completion-at-point-functions #'cape-file) ;; file paths
(add-to-list 'completion-at-point-functions #'cape-emoji) ;; emoji
;; (add-to-list 'completion-at-point-functions #'cape-tex) ;; Unicode chars from latex commands
)
I use Yasnippet both for code snippets and for file template. Previously I set up Yasnippet so that it would insert a template for new files. It would not work well for LaTeX files since I need many types of templates for them, and in general whenever some tools would create new empty files. I trigger the file template explicitly.
I do not use yasnippet-snippets
. I only use my own sparse
collection of snippets (some copied and edited from that package
anyway).
(setq mxl/local-snippet-dir (concat base-config-path "snippets/"))
Basic setup of Yasnippet.
(use-package yasnippet
:commands (yas-minor-mode yas-global-mode yas-expand)
:diminish (yas-minor-mode . "")
;; edit the snippets
:mode ("\\.yasnippet" . snippet-mode)
:mode ("\\.snippet" . snippet-mode)
:init
(add-to-list 'warning-suppress-types '(yasnippet backquote-change))
(yas-global-mode 1)
:config
(setq yas-snippet-dirs (list mxl/local-snippet-dir))
(setq yas-indent-line 'fixed)
(progn
(yas-reload-all)))
We need to be careful about whitespace, while editing snippet specs.
(add-hook 'snippet-mode-hook (lambda ()
(whitespace-mode)
(make-local-variable 'require-final-newline)
(setq require-final-newline nil)
))
There are two access point to templates/snippets.
- One is by expanding a template chosen from a menu. I map the
menu to
M-t
keybind, since I’ve never used it to transpose words.
(use-package consult-yasnippet
;; M-t is usually reserved to transpose words, but I've never
;; used it.
:bind ("M-t" . consult-yasnippet))
- By expanding during typing. All my snippets either do not have
a key, hence are accessible only from the menu, or the key
starts with
'<'
.
Emacs can help with inserting pairs of parenthesis, brackets, and so
on… Even more complex pairs like \left{
and \right}
. It should
manage automatic wrapping of text, and stuff like that. Of couse, as
usual with emacs, there are multiple functions and packages trying to
do the same thing and therefore fight with each other. I use
smartparens
, but there are other options like autopair
or
electric-pair
.
(use-package smartparens
:commands (smartparens-mode smartparens-global-mode)
:diminish ""
:custom
(sp-base-key-bindings nil)
(sp-highlight-pair-overlay nil)
(sp-wrap-repeat-last 2)
:config
(require 'smartparens-config)
(sp-local-pair 'text-mode "“" "”"))
Emacs manages multiple backups for files, and furthermore it remembers to save stuff from time to time. I’d rather put all my backups and autosaves in a specific folder.
(defvar mxl/autosave-dir "~/.emacs.d/autosaves/")
(defvar mxl/backup-dir "~/.emacs.d/backups/")
(make-directory mxl/autosave-dir t)
(make-directory mxl/backup-dir t)
First I setup the autosave feature
(setq tramp-auto-save-directory mxl/autosave-dir)
(setq auto-save-file-name-transforms
`((".*" ,mxl/autosave-dir t)))
then I setup the backup features, where copies of old versions are kept. Disable backups for remote files.
(setq make-backup-files t)
(setq version-control nil)
(setq delete-old-versions t) ;; silently delete old versions
(setq kept-new-versions 3) ;; number of newest versions to keep
(setq kept-old-versions 2) ;; number of oldest versions to keep
(setq backup-by-copying t)
(setq backup-by-copying-when-linked t)
(setq backup-directory-alist
`(("." . ,mxl/backup-dir) ;; location for local files
(,tramp-file-name-regexp . nil))) ;; disable for remote files
If you are like me you have several files you visit on a regular bases, for some periods of time. E.g., lecture folders, course journal, the tex files for the paper you are currently working on. It is convenient to bookmark such locations, but the bookmark file should be in some private versioned folder.
(setq bookmark-default-file "~/personal/conf/emacs.bookmarks")
I use consult-buffer
to access and set bookmarks, but it is possible
to edit and delete them with command bookmark-bmenu-list
, bound
to C-x r l
.
The list of recent files is saved on Emacs exit and kill by default. To err on safety, let’s write the file
(use-package recentf
:demand t
:config
(recentf-mode 1)
(run-at-time nil (* 60 60) 'recentf-save-list))
I set the combos M-0 M-1
up to M-0 M-9
for saving the point in the
respective register, and I use the corresponding M-1
… M-9
to
jump back to it.
(global-unset-key (kbd "M-0")) ; I need it as a prefix key for next keybinds.
(dotimes (i 9)
(let ((num ( 1 i))
(str (number-to-string ( 1 i))))
(global-set-key
(kbd (concat "M-0 M-" str))
`(lambda () (interactive) (point-to-register ,num)
(message (concat "Position saved to register " ,str))))
(global-set-key
(kbd (concat "M-" str))
`(lambda () (interactive) (jump-to-register ,num)
(message (concat "Position loaded from register " ,str))))))
Calling replace
and query-replace
on regexp(s) is quite tricky.
It is difficult to understand what the matched text is, especially
when using the emacs regexp syntax. The visual-regexp
package helps.
(use-package visual-regexp
:commands (vr/replace vr/mc-mark vr/query-replace)
:bind
(("M-%" . vr/replace)
("C-M-%" . vr/query-replace)
))
This is the default setup of regexp builder.
(use-package re-builder
:init
(setq reb-re-syntax 'string)
)