1687 lines
53 KiB
Org Mode
1687 lines
53 KiB
Org Mode
#+TITLE: Emacs Configuration
|
|
#+AUTHOR: Camden Dixie O'Brien
|
|
#+ATTR_LATEX: :float t
|
|
|
|
* Customize
|
|
Hey, customize, leave my ~/.emacs.d/init.el alone!
|
|
|
|
#+begin_src emacs-lisp
|
|
(setq custom-file (concat user-emacs-directory "customize.el"))
|
|
(load custom-file t)
|
|
#+end_src
|
|
|
|
* Package Management
|
|
** MELPA
|
|
Let's be real here, all the good stuff's on MELPA.
|
|
|
|
#+begin_src emacs-lisp
|
|
(require 'package)
|
|
(add-to-list 'package-archives
|
|
'("melpa" . "https://melpa.org/packages/"))
|
|
#+end_src
|
|
|
|
** Initialization
|
|
We now need to run [[help:package-initialize][package-initialize]] to load and activate
|
|
packages. The documentation advises doing this early in
|
|
configuration.
|
|
|
|
#+begin_src emacs-lisp
|
|
(package-initialize)
|
|
#+end_src
|
|
|
|
We also need to fetch the package archives from ELPA and MELPA,
|
|
unless they have already been fetched:
|
|
|
|
#+begin_src emacs-lisp
|
|
(unless package-archive-contents
|
|
(package-refresh-contents))
|
|
#+end_src
|
|
|
|
** =use-package=
|
|
The rest of this config grabs packages via =use-package=, so that
|
|
needs to be installed:
|
|
|
|
#+begin_src emacs-lisp
|
|
(when (not (package-installed-p 'use-package))
|
|
(package-install 'use-package))
|
|
#+end_src
|
|
|
|
The wanted behaviour for =use-package= here is to ensure all used
|
|
packages are present.
|
|
|
|
#+begin_src emacs-lisp
|
|
(require 'use-package-ensure)
|
|
(setq use-package-always-ensure t)
|
|
#+end_src
|
|
|
|
** Hostname
|
|
It's useful to have the short hostname available in case we want to
|
|
configure something differently on certain machines. The full
|
|
hostname can be retrieved with [[help:system-name][system-name]], but this has the local
|
|
domain on the end; we want to cut that off.
|
|
|
|
#+begin_src emacs-lisp
|
|
(defun cut-at (delimeter string)
|
|
(substring string 0 (seq-position string delimeter)))
|
|
|
|
(defvar hostname (cut-at ?. (system-name)))
|
|
#+end_src
|
|
|
|
* Emacs Server
|
|
Start an Emacs server if one is not running already:
|
|
|
|
#+begin_src emacs-lisp
|
|
(require 'server)
|
|
(unless (server-running-p)
|
|
(server-start))
|
|
#+end_src
|
|
|
|
With this, files opened with =emacsclient= in a terminal will open
|
|
in Emacs. Most of the time I use Emacs itself for my file browsing
|
|
and terminal needs but =emacsclient= is still handy from
|
|
time-to-time.
|
|
|
|
* UI
|
|
The start-up message gets pretty annoying, so disable that.
|
|
|
|
#+begin_src emacs-lisp
|
|
(setq inhibit-startup-screen t)
|
|
#+end_src
|
|
|
|
Also, the menu-, tool- and scroll-bar are ugly, take up space and I
|
|
don't use them.
|
|
|
|
#+begin_src emacs-lisp
|
|
(menu-bar-mode -1)
|
|
(tool-bar-mode -1)
|
|
(scroll-bar-mode -1)
|
|
#+end_src
|
|
|
|
It's nice to have an inverse of =C-x o= for switching between
|
|
windows. =other-window=, the function that =C-x o= is bound to,
|
|
takes an argument =COUNT= that determines how many windows it skips
|
|
forwards so we can simply pass -1 to =other-window= in a lambda and
|
|
bind to that:
|
|
|
|
#+begin_src emacs-lisp
|
|
(global-set-key (kbd "C-x O")
|
|
(lambda ()
|
|
(interactive)
|
|
(other-window -1)))
|
|
#+end_src
|
|
|
|
** Font
|
|
I find serif fonts much nicer to read, so definitely change the
|
|
default face to a serif font. Font size seems inconsistent across
|
|
different systems for some reason, so need to determine the font
|
|
height based off the hostname.
|
|
|
|
#+begin_src emacs-lisp
|
|
(let ((font-height
|
|
(pcase hostname
|
|
("zora" 100)
|
|
("eddie" 100)
|
|
("mandarax" 115)
|
|
("valis" 80)
|
|
(_ 110))))
|
|
(set-face-attribute 'default nil
|
|
:family "Courier 10 Pitch"
|
|
:height font-height))
|
|
#+end_src
|
|
|
|
I also like a little more line spacing than default, again makes
|
|
code nicer to read.
|
|
|
|
#+begin_src emacs-lisp
|
|
(setq-default line-spacing 0.2)
|
|
#+end_src
|
|
|
|
** Colour Scheme
|
|
Currently using =spacemacs-theme='s light variant.
|
|
|
|
#+begin_src emacs-lisp
|
|
(use-package spacemacs-theme
|
|
:defer t)
|
|
(load-theme 'spacemacs-light t)
|
|
#+end_src
|
|
|
|
The first tweak I make is changing the borders around the mode line
|
|
to be two pixels thick and the same colour as the background (of
|
|
the active mode line that is).
|
|
|
|
#+begin_src emacs-lisp
|
|
(set-face-attribute 'mode-line nil
|
|
:box '(:line-width 2 :color "#e7e5eb" :style nil))
|
|
(set-face-attribute 'mode-line-inactive nil
|
|
:box '(:line-width 2 :color "#e7e5eb" :style nil))
|
|
#+end_src
|
|
|
|
I also set the right window divider to the same colour as the
|
|
header background.
|
|
|
|
#+begin_src emacs-lisp
|
|
(set-face-attribute 'window-divider nil :foreground "#efeae9")
|
|
(setq initial-frame-alist '((right-divider-width . 1)))
|
|
#+end_src
|
|
|
|
** Opening buffers in current window
|
|
There are several places where buffers open in different windows to
|
|
the currently selected one. I find this behaviour annoying and I
|
|
don't understand why anyone would like it. [[help:display-buffer-alist][display-buffer-alist]]
|
|
provides a mechanism for preventing this where there isn't a better
|
|
way, as seems to be the case with shell and help buffers.
|
|
|
|
#+begin_src emacs-lisp
|
|
(defun open-in-same-window-p (buffer-name action)
|
|
(or (string= (upcase buffer-name) "*SHELL*")
|
|
(string= (upcase buffer-name) "*HELP*")))
|
|
|
|
(setq display-buffer-alist
|
|
'((open-in-same-window-p . (display-buffer-same-window . nil))))
|
|
#+end_src
|
|
|
|
* Autocompletion
|
|
Enable =company-mode= globally, and hook it into =completion-at-point-functions=.
|
|
|
|
#+begin_src emacs-lisp
|
|
(use-package company
|
|
:config
|
|
(add-hook 'after-init-hook 'global-company-mode)
|
|
(add-to-list 'company-backends 'company-capf))
|
|
#+end_src
|
|
|
|
And enable =ido-mode= everywhere, with flexible matching.
|
|
|
|
#+begin_src emacs-lisp
|
|
(use-package ido
|
|
:config
|
|
(setq ido-enable-flex-matching t)
|
|
(add-hook 'after-init-hook
|
|
(lambda ()
|
|
(ido-everywhere)
|
|
(ido-mode t))))
|
|
#+end_src
|
|
|
|
** Snippets
|
|
YASnippet is the de facto snippet engine. There are two packages
|
|
for it: one for the engine itself and one with a library of
|
|
snippets:
|
|
|
|
#+begin_src emacs-lisp
|
|
(use-package yasnippet)
|
|
(use-package yasnippet-snippets)
|
|
#+end_src
|
|
|
|
I don't want it enabled all the time, only in certain modes. The
|
|
[[help:yas-minor-mode][yas-minor-mode]] function is provided for this, but it requires that
|
|
the snippet tables are loaded beforehand, so that has to be done
|
|
now.
|
|
|
|
#+begin_src emacs-lisp
|
|
(yas-reload-all)
|
|
#+end_src
|
|
|
|
* Calendar / Diary
|
|
Weeks start on Sunday by default, this can be changed to start on
|
|
Monday by setting [[help:calendar-week-start-day][calendar-week-start-day]] to 1:
|
|
|
|
#+begin_src emacs-lisp
|
|
(setq calendar-week-start-day 1)
|
|
#+end_src
|
|
|
|
Set latitute, longitude and location name to Bristol to get sunrise
|
|
and sunset times:
|
|
|
|
#+begin_src emacs-lisp
|
|
(setq calendar-latitude 51.45)
|
|
(setq calendar-longitude -2.58)
|
|
(setq calendar-location-name "Bristol, UK")
|
|
#+end_src
|
|
|
|
Emacs needs to be told where the diary file is, of course:
|
|
|
|
#+begin_src emacs-lisp
|
|
(setq diary-file "~/Documents/diary")
|
|
#+end_src
|
|
|
|
I want to use ISO-style dates in there:
|
|
|
|
#+begin_src emacs-lisp
|
|
(calendar-set-date-style 'iso)
|
|
#+end_src
|
|
|
|
* Org
|
|
** Code and Quote block shortcuts
|
|
I am a big fan of using =<s= for source blocks and =<q= for quotes;
|
|
these are enabled by the =org-tempo= module, which is included in
|
|
=org= but not loaded by default.
|
|
|
|
#+begin_src emacs-lisp
|
|
(use-package org :config (require 'org-tempo))
|
|
#+end_src
|
|
|
|
However, I have recently discovered, much to my despair, that these
|
|
shortcuts do not work if there are tabs in the line ahead of them!
|
|
Quite ridiculous. Easily worked around, however; I am going to
|
|
ensure that spaces are used for indentation when in org mode by
|
|
setting [[help:indent-tabs-mode][indent-tabs-mode]] to nil in a hook:
|
|
|
|
#+begin_src emacs-lisp
|
|
(add-hook 'org-mode-hook (lambda () (setq indent-tabs-mode nil)))
|
|
#+end_src
|
|
|
|
** Keybindings
|
|
A keybinding to add a new heading is super useful
|
|
|
|
#+begin_src emacs-lisp
|
|
(add-hook 'org-mode-hook
|
|
(lambda ()
|
|
(define-key org-mode-map
|
|
(kbd "<C-M-return>")
|
|
'org-insert-heading-after-current)))
|
|
#+end_src
|
|
|
|
** Journal Files
|
|
Sometimes I like to make a todo list for a day if I've a lot to do,
|
|
or write a little bit about a day if it's been particularly
|
|
eventful. In both of these cases, it would be nice to just be able
|
|
to hit a particular keybinding and have the right buffer pop up. I
|
|
was using =org-roam='s daily notes feature for this, but I ended up
|
|
getting annoyed with =org-roam= (too many features for my taste
|
|
lol).
|
|
|
|
The convention I'm going for is for is having a particular
|
|
directory for these journal entries and then give each file a name
|
|
like "2022-10-30.org". With that in mind, there are two obvious
|
|
variables to define:
|
|
|
|
#+begin_src emacs-lisp
|
|
(defvar journal-directory
|
|
"~/Documents/org/journal"
|
|
"Directory to store journal entries in.")
|
|
|
|
(defvar journal-filename-format
|
|
"%F"
|
|
"Date format to use for journal entries' filenames (not including
|
|
the \".org\" extension)")
|
|
#+end_src
|
|
|
|
I can't be bothered to handle [[help:journal-directory][journal-directory]] not existing in the
|
|
main code, so I'm just going to make sure it exists here. The
|
|
second argument to [[help:make-directory][make-directory]] specifies to create parent
|
|
directories too if necessary.
|
|
|
|
#+begin_src emacs-lisp
|
|
(unless (file-directory-p journal-directory)
|
|
(make-directory journal-directory t))
|
|
#+end_src
|
|
|
|
The next step is to define a function to determine the filename for
|
|
today's journal entry. This is fairly straightforward, using
|
|
[[help:format-time-string][format-time-string]] to get the current date in the right format,
|
|
then sticking that together with the directory, extension, etc.
|
|
|
|
#+begin_src emacs-lisp
|
|
(defun journal-entry-filename ()
|
|
"Returns the filename for today's journal entry."
|
|
(let ((date-string (format-time-string journal-filename-format)))
|
|
(concat journal-directory "/" date-string ".org")))
|
|
#+end_src
|
|
|
|
If the journal entry doesn't exist yet, I want it to be populated
|
|
with the long-form date as the title:
|
|
|
|
#+begin_src emacs-lisp
|
|
(defvar journal-title-date-format
|
|
"%A, %-e %B %+4Y"
|
|
"The date format to use for journal entries' titles.")
|
|
|
|
(defun insert-default-journal-entry-contents ()
|
|
"Insert the default journal entry contents (currently this is
|
|
just today's long-form date as a title) into the current buffer."
|
|
(insert "#+TITLE: "
|
|
(format-time-string journal-title-date-format)
|
|
"\n\n"))
|
|
#+end_src
|
|
|
|
We can now make a function to open today's journal entry fairly
|
|
trivially using [[help:find-file][find-file]], and the above utilities. It should be
|
|
interactive, as this is what we'll be calling in the key binding.
|
|
|
|
#+begin_src emacs-lisp
|
|
(defun open-journal-entry ()
|
|
"Opens today's journal entry, populating it with the default
|
|
contents if it does not already exist."
|
|
(interactive)
|
|
(let* ((filename (journal-entry-filename))
|
|
(new-entry (not (file-exists-p filename))))
|
|
(find-file filename)
|
|
(when new-entry
|
|
(insert-default-journal-entry-contents))))
|
|
#+end_src
|
|
|
|
Finally, [[help:open-journal-entry][open-journal-entry]] can be bound to a key:
|
|
|
|
#+begin_src emacs-lisp
|
|
(global-set-key (kbd "C-c t") 'open-journal-entry)
|
|
#+end_src
|
|
|
|
** Source Blocks
|
|
Pressing tab inside a source block should indent appropriately for its
|
|
language.
|
|
|
|
#+begin_src emacs-lisp
|
|
(setq org-src-tab-acts-natively t)
|
|
#+end_src
|
|
|
|
=babel= lets us evaluate Org documents containing source blocks!
|
|
I've left the enabling of this for most languages to the section
|
|
for that language, but I'll add Shell and Elisp here.
|
|
|
|
#+begin_src emacs-lisp
|
|
(org-babel-do-load-languages
|
|
'org-babel-load-languages
|
|
'((emacs-lisp . t)
|
|
(shell . t)))
|
|
#+end_src
|
|
|
|
The interface org-babel exposes is a little annoying as later in
|
|
this config we'll want to preserve the prior value of
|
|
[[help:org-babel-load-languages][org-babel-load-languages]] when enabling another language. However,
|
|
~org-babel-do-load-languages~ /sets the passed symbol to the passed
|
|
value/ with [[help:set-default][set-default]] before going through the pairs in
|
|
~org-babel-load-languages~. Don't ask me why, seems like an
|
|
obviously bad design. Workaround is to define a function here to
|
|
use later so at least it won't /look/ ugly.
|
|
|
|
#+begin_src emacs-lisp
|
|
(defun enable-org-babel-lang (lang)
|
|
"Enable executing source block in the passed language in
|
|
org-mode. Doesn't affect other enabled languages."
|
|
(org-babel-do-load-languages
|
|
'org-babel-load-languages
|
|
(append org-babel-load-languages `((,lang . t)))))
|
|
#+end_src
|
|
|
|
By default trying to execute a source block prompts you, which is
|
|
super annoying since I'm realistically not going to try to run any
|
|
code from Org documents I haven't written, so that needs
|
|
disabling. You can do that by setting [[help:org-confirm-babel-evaluate][org-confirm-babel-evaluate]] to
|
|
=nil=.
|
|
|
|
#+begin_src emacs-lisp
|
|
(setq org-confirm-babel-evaluate nil)
|
|
#+end_src
|
|
|
|
Another annoying thing that happens by default is the clobbering of
|
|
the window layout when you open a source block. You can change that
|
|
by setting [[help:org-src-window-setup][org-src-window-setup]].
|
|
|
|
#+begin_src emacs-lisp
|
|
(setq org-src-window-setup 'split-window-below)
|
|
#+end_src
|
|
|
|
*** Asyncronous Execution
|
|
=ob-async= makes source blocks with the ~:async~ keyword execute
|
|
asyncronously, super handy for long-running snippets etc.
|
|
|
|
#+begin_src emacs-lisp
|
|
(use-package ob-async)
|
|
#+end_src
|
|
|
|
** Exporting
|
|
I very rarely want a table of contents, as most of my org documents
|
|
are pretty short.
|
|
|
|
#+begin_src emacs-lisp
|
|
(setq org-export-with-toc nil)
|
|
#+end_src
|
|
|
|
Also don't want section numbering for similar reasons:
|
|
|
|
#+begin_src emacs-lisp
|
|
(setq org-export-with-section-numbers nil)
|
|
#+end_src
|
|
|
|
*** HTML
|
|
=htmlize= is needed for decent HTML exporting, but there is no need
|
|
for all that stuff at the bottom.
|
|
|
|
#+begin_src emacs-lisp
|
|
(use-package htmlize)
|
|
(setq org-html-postamble nil)
|
|
#+end_src
|
|
|
|
I like margins and line height:
|
|
|
|
#+begin_src emacs-lisp
|
|
(setq org-html-style
|
|
"<style>
|
|
body {
|
|
margin: 3em auto;
|
|
max-width: 42em;
|
|
padding: 0 2em;
|
|
}
|
|
</style>")
|
|
#+end_src
|
|
|
|
*** LaTeX
|
|
Use =minted= (LaTeX package) to do syntax highlighting in code blocks:
|
|
|
|
#+begin_src emacs-lisp
|
|
(add-to-list 'org-latex-packages-alist '("" "minted"))
|
|
(setq org-latex-listings 'minted)
|
|
#+end_src
|
|
|
|
=minted= actually calls =pygments= through the shell, which =pdflatex=
|
|
doesn't like; you have to tell it not to worry, and that everything is
|
|
going to be OK.
|
|
|
|
#+begin_src emacs-lisp
|
|
(setq org-latex-pdf-process
|
|
'("xelatex -shell-escape -interaction nonstopmode -output-directory %o %f"
|
|
"xelatex -shell-escape -interaction nonstopmode -output-directory %o %f"
|
|
"xelatex -shell-escape -interaction nonstopmode -output-directory %o %f"))
|
|
#+end_src
|
|
|
|
** Default Applications
|
|
It's all fun and games until =C-c C-e h o= opens the source code.
|
|
|
|
#+begin_src emacs-lisp
|
|
(setq org-file-apps
|
|
'(("html" . "firefox %s")
|
|
(auto-mode . emacs)))
|
|
#+end_src
|
|
|
|
** Workflow States
|
|
I like to have =IN-PROGRESS= and =CANCELLED= workflow states as
|
|
well as the standard =TODO= and =DONE=. Cancelled items also want a
|
|
note attached explaining why. All this can be added by setting
|
|
[[help:org-todo-keywords][org-todo-keywords]]:
|
|
|
|
#+begin_src emacs-lisp
|
|
(setq org-todo-keywords
|
|
'((sequence "TODO" "IN-PROGRESS" "|" "DONE" "CANCELLED(@)")))
|
|
#+end_src
|
|
|
|
The ="|"= separates /needs further action/ states (before it) from
|
|
/no further action needed/ states (after it).
|
|
|
|
I also want to log the date and time when a note is marked as done:
|
|
|
|
#+begin_src emacs-lisp
|
|
(setq org-log-done 'time)
|
|
#+end_src
|
|
|
|
** Agenda
|
|
Time to try org-mode's agenda feature again I think. Last time I
|
|
didn't end up using it much, but I am /much/ more of an Emacs
|
|
addict now so I do forsee it actually surviving (this will be funny
|
|
to read in the future if not).
|
|
|
|
I want to show all TODOs in =.org= files under my top-level
|
|
=~/Documents/org= directory and any in this config itself. This is
|
|
done by enumerating all files under =~/Documents/org= with
|
|
[[help:directory-files-recursively][directory-files-recursively]], then setting [[help:org-agenda-files][org-agenda-files]] to this,
|
|
along with this config's path.
|
|
|
|
#+begin_src emacs-lisp
|
|
(let ((org-docs
|
|
(directory-files-recursively "~/Documents/org" ".+\.org$")))
|
|
(setq org-agenda-files `("~/.emacs.d/config.org" ,@org-docs)))
|
|
#+end_src
|
|
|
|
Also I find it really very annoying that the the current window
|
|
layout is destroyed when you run =org-agenda=. That behaviour is
|
|
changed by setting [[help:org-agenda-window-setup][org-agenda-window-setup]]:
|
|
|
|
#+begin_src emacs-lisp
|
|
(setq org-agenda-window-setup 'current-window)
|
|
#+end_src
|
|
|
|
Include events from my diary:
|
|
|
|
#+begin_src emacs-lisp
|
|
(setq org-agenda-include-diary t)
|
|
#+end_src
|
|
|
|
Though I don't like the time grid being on by default.
|
|
|
|
#+begin_src emacs-lisp
|
|
(setq org-agenda-use-time-grid nil)
|
|
#+end_src
|
|
|
|
I primarily use the TODO list to keep track of un-scheduled tasks,
|
|
so I don't want those displayed in there:
|
|
|
|
#+begin_src emacs-lisp
|
|
(setq org-agenda-todo-ignore-scheduled t)
|
|
#+end_src
|
|
|
|
Finally, I want a keybinding for the weekly agenda and global TODO
|
|
list agenda view:
|
|
|
|
#+begin_src emacs-lisp
|
|
(defun org-weekly-agenda-and-todo-list ()
|
|
(interactive)
|
|
(org-agenda nil "n"))
|
|
|
|
(global-set-key (kbd "C-c a") 'org-weekly-agenda-and-todo-list)
|
|
#+end_src
|
|
|
|
** Habits
|
|
Habit tracking requires the habits org module to be loaded. This is
|
|
done by adding the symbol ~'habits~ to [[help:org-modules][org-modules]], if it's not in
|
|
there already. I originally didn't have the surrounding ~unless~,
|
|
but it causes problems when re-loading the config using
|
|
[[help:org-babel-load-file][org-babel-load-file]].
|
|
|
|
#+begin_src emacs-lisp
|
|
(unless (member 'habits org-modules)
|
|
(add-to-list 'org-modules 'org-habit)
|
|
(org-load-modules-maybe t))
|
|
#+end_src
|
|
|
|
The [[help:org-load-modules-maybe][org-load-modules-maybe]] call forces org to load the modules in
|
|
[[help:org-modules][org-modules]]. Not sure it's needed, but I ran into some weird issues
|
|
and I think it fixed them.
|
|
|
|
The consistency graph is very nice but overlaps a lot of the habit
|
|
names, so I want to move it to the right a little:
|
|
|
|
#+begin_src emacs-lisp
|
|
(setq org-habit-graph-column 42)
|
|
#+end_src
|
|
|
|
Also it displays days that you did a habit in red if the habit was
|
|
overdue on that day, which makes a sort of sense, but always
|
|
showing days you did things in green makes more sense to me. The
|
|
variable [[help:org-habit-show-done-always-green][org-habit-show-done-always-green]] controls this.
|
|
|
|
#+begin_src emacs-lisp
|
|
(setq org-habit-show-done-always-green t)
|
|
#+end_src
|
|
|
|
** Identation
|
|
Setting [[help:org-adapt-indentation][org-adapt-indentation]] to ~t~ ensures that Org will indent text
|
|
under a headline:
|
|
|
|
#+begin_src emacs-lisp
|
|
(setq org-adapt-indentation t)
|
|
#+end_src
|
|
|
|
* Language Integrations
|
|
** Generic
|
|
Generally, 8-character-wide tabs are not my thing.
|
|
|
|
#+begin_src emacs-lisp
|
|
(setq-default tab-width 4)
|
|
(setq-default basic-offset 4)
|
|
#+end_src
|
|
|
|
Use tabs as god intended:
|
|
|
|
#+begin_src emacs-lisp
|
|
(setq-default indent-tabs-mode t)
|
|
#+end_src
|
|
|
|
*** Language Server Protocol
|
|
LSP seems to be the way forward in terms of IDE-like features in
|
|
Emacs; grab =lsp-mode= and enable =lsp-deferred=:
|
|
|
|
#+begin_src emacs-lisp
|
|
(use-package lsp-mode
|
|
:init (setq lsp-keymap-prefix "C-c l")
|
|
:commands (lsp lsp-deferred))
|
|
#+end_src
|
|
|
|
=lsp-deferred= means that the LSP server will only be started once
|
|
a buffer is actually opened, which makes more sense to me.
|
|
|
|
Also going to give =lsp-ui= a shot, which displays a bunch of
|
|
information from the language server in the buffer. It looks like
|
|
it could be a bit much but we'll see.
|
|
|
|
#+begin_src emacs-lisp
|
|
(use-package lsp-ui :commands lsp-ui-mode)
|
|
#+end_src
|
|
|
|
For LSP's most excellent autocompletion to work properly
|
|
[[help:yas-minor-mode][yas-minor-mode]] must be enabled, so hook that into =lsp-mode=:
|
|
|
|
#+begin_src emacs-lisp
|
|
(add-hook 'lsp-mode-hook
|
|
(lambda ()
|
|
(yas-minor-mode)))
|
|
#+end_src
|
|
|
|
To enable Ido integration:
|
|
|
|
#+begin_src emacs-lisp
|
|
(require 'lsp-ido)
|
|
#+end_src
|
|
|
|
*** Smart Tabs
|
|
Indent with tabs and align with spaces. Installing the package
|
|
here but it's enabled on a per-language basis in the languages'
|
|
individual config sections.
|
|
|
|
#+begin_src emacs-lisp
|
|
(use-package smart-tabs-mode)
|
|
#+end_src
|
|
|
|
The package has this really annoying behaviour that it turns
|
|
itself off if [[help:indent-tabs-mode][indent-tabs-mode]] is nil, even when you just
|
|
explicitly turned it on. The solution on the Emacs wiki is to set
|
|
indent-tabs-mode to t in a =c-mode-common= hook, which is a bit of
|
|
a hack, but I tried my own approach and it didn't work for no
|
|
apparent reason so I'm just going to do as I'm told.
|
|
|
|
#+begin_src emacs-lisp
|
|
(add-hook 'c-mode-common-hook
|
|
(lambda () (setq indent-tabs-mode t)))
|
|
#+end_src
|
|
|
|
** C
|
|
For indenting style, I like BSD-style but with 4-char-wide indents
|
|
|
|
#+begin_src emacs-lisp
|
|
(add-hook 'c-mode-hook (lambda ()
|
|
(c-set-style "bsd")
|
|
(setq c-basic-offset 4)))
|
|
#+end_src
|
|
|
|
And we want to enable smart tabs:
|
|
|
|
#+begin_src emacs-lisp
|
|
(smart-tabs-insinuate 'c)
|
|
#+end_src
|
|
|
|
There's a lot of boilerplate in C, so I want YASnippet enabled.
|
|
|
|
#+begin_src emacs-lisp
|
|
(add-hook 'c-mode-hook (lambda () (yas-minor-mode)))
|
|
#+end_src
|
|
|
|
** C++
|
|
Essentially the same story as for C.
|
|
|
|
#+begin_src emacs-lisp
|
|
(add-hook 'c++-mode-hook #'lsp-deferred)
|
|
(add-hook 'c++-mode-hook (lambda ()
|
|
(c-set-style "bsd")
|
|
(setq c-basic-offset 4)))
|
|
(smart-tabs-insinuate 'c++)
|
|
#+end_src
|
|
|
|
** Haskell
|
|
My workflow with Haskell is very REPL-based, so I always want
|
|
=interactive-haskell-mode= on.
|
|
|
|
#+begin_src emacs-lisp
|
|
(use-package haskell-mode)
|
|
(require 'haskell-interactive-mode)
|
|
(add-hook 'haskell-mode-hook 'interactive-haskell-mode)
|
|
#+end_src
|
|
|
|
And, of course, that REPL needs to be taking advantage of parallelism!
|
|
|
|
#+begin_src emacs-lisp
|
|
(require 'haskell-process)
|
|
(set-variable 'haskell-process-args-ghci
|
|
'("-threaded" "+RTS" "-N8" "-RTS"))
|
|
#+end_src
|
|
|
|
** Idris
|
|
The only thing to change from the defaults here is to add a more
|
|
convenient way to case-split.
|
|
|
|
#+begin_src emacs-lisp
|
|
(use-package idris-mode)
|
|
(add-hook 'idris-mode-hook
|
|
(lambda ()
|
|
(define-key idris-mode-map (kbd "C-c SPC")
|
|
'idris-case-split)))
|
|
#+end_src
|
|
|
|
** Rust
|
|
|
|
=rust-mode= provides basic support:
|
|
|
|
#+begin_src emacs-lisp
|
|
(use-package rust-mode)
|
|
#+end_src
|
|
|
|
Then =rust-analyzer= via LSP does the rest :)
|
|
|
|
#+begin_src emacs-lisp
|
|
(add-hook 'rust-mode-hook #'lsp-deferred)
|
|
#+end_src
|
|
|
|
** Lisps
|
|
*** Common Lisp
|
|
Use SLIME and Quicklisp for Common Lisp (SBCL).
|
|
|
|
#+begin_src emacs-lisp
|
|
(use-package slime)
|
|
(setq inferior-lisp-program "sbcl")
|
|
(global-set-key (kbd "C-c s") 'slime-selector)
|
|
(let ((helper-filename "~/quicklisp/slime-helper.el"))
|
|
(when (file-exists-p helper-filename)
|
|
(load (expand-file-name helper-filename))))
|
|
#+end_src
|
|
|
|
This assumes that SBCL and Quicklisp, along with Quicklisp's SLIME
|
|
helper, are installed. Once SBCL is installed, Quicklisp and its
|
|
SLIME helper can be installed by grabbing [[https://beta.quicklisp.org/quicklisp.lisp][the installer]] and
|
|
loading it with SBCL.
|
|
|
|
#+begin_src shell :tangle no
|
|
curl -O https://beta.quicklisp.org/quicklisp.lisp
|
|
sbcl --load quicklisp.lisp
|
|
#+end_src
|
|
|
|
That will open a REPL with the =quicklisp-quickstart= system
|
|
loaded. At that REPL, run:
|
|
|
|
#+begin_src common-lisp :tangle no
|
|
(quicklisp-quickstart:install)
|
|
(ql:add-to-init-file)
|
|
(ql:quickload "quicklisp-slime-helper")
|
|
#+end_src
|
|
|
|
**** Source blocks
|
|
Enable execution of CL source blocks in Org mode:
|
|
|
|
#+begin_src emacs-lisp
|
|
(enable-org-babel-lang 'lisp)
|
|
#+end_src
|
|
|
|
**** ASDF
|
|
SLIME has a contrib for ASDF integration, =slime-asdf=. This is
|
|
enabled by adding it to [[help:slime-contribs][slime-contribs]]:
|
|
|
|
#+begin_src emacs-lisp
|
|
(add-to-list 'slime-contribs 'slime-asdf)
|
|
#+end_src
|
|
|
|
*** Paredit
|
|
=paredit= is generally very useful for balancing parenthesis so we
|
|
want that turned on for all the lisps. Additionally, it's nice to have
|
|
an entire expression highlighted when the cursor is on one of its
|
|
enclosing parens.
|
|
|
|
#+begin_src emacs-lisp
|
|
(use-package paredit)
|
|
(setq lispy-mode-hooks
|
|
'(emacs-lisp-mode-hook
|
|
lisp-mode-hook
|
|
racket-mode-hook
|
|
scheme-mode-hook
|
|
slime-repl-mode-hook))
|
|
(dolist (hook lispy-mode-hooks)
|
|
(add-hook hook (lambda ()
|
|
(setq show-paren-style 'expression)
|
|
(paredit-mode))))
|
|
#+end_src
|
|
|
|
*** Scheme and Racket
|
|
[[https://www.nongnu.org/geiser/][Geiser]] is a pretty complete collection of Scheme things for
|
|
Emacs. Only change from the defaults is to open the REPL in the
|
|
current window instead of creating a new one.
|
|
|
|
#+begin_src emacs-lisp
|
|
(use-package geiser
|
|
:config
|
|
(setq geiser-repl-use-other-window nil))
|
|
#+end_src
|
|
|
|
I commonly use Chez, Guile and Racket so we want the packages for those:
|
|
|
|
#+begin_src emacs-lisp
|
|
(use-package geiser-chez)
|
|
(use-package geiser-guile)
|
|
(use-package geiser-racket)
|
|
#+end_src
|
|
|
|
** YAML
|
|
I don't really like YAML if I'm honest, but it's used a lot so...
|
|
|
|
#+begin_src emacs-lisp
|
|
(use-package yaml-mode)
|
|
#+end_src
|
|
|
|
** Javascript
|
|
The first bit of this setup (=js2-mode=, =js2-refactor= and
|
|
=xref-js2=) is essentially copied from [[https://emacs.cafe/emacs/javascript/setup/2017/04/23/emacs-setup-javascript.html][this Emacs cafe post]].
|
|
|
|
First of all we want to grab =js2-mode= and enable it for
|
|
javascript buffers. It extends the default =js-mode= and builds an
|
|
AST which can be used by other packages.
|
|
|
|
#+begin_src emacs-lisp
|
|
(use-package js2-mode)
|
|
(add-to-list 'auto-mode-alist '("\\.js\\'" . js2-mode))
|
|
#+end_src
|
|
|
|
|
|
*** Refactoring
|
|
=js2-refactor= provides refactoring tools based of said AST, so
|
|
enable that and its keybindings:
|
|
|
|
#+begin_src emacs-lisp
|
|
(use-package js2-refactor)
|
|
(add-hook 'js2-mode-hook #'js2-refactor-mode)
|
|
(js2r-add-keybindings-with-prefix "C-c C-r")
|
|
#+end_src
|
|
|
|
It provides a kill function with nice semantics for Javascript --
|
|
we definitely want that instead of the generic kill.
|
|
|
|
#+begin_src emacs-lisp
|
|
(define-key js2-mode-map (kbd "C-k") #'js2r-kill)
|
|
#+end_src
|
|
|
|
*** Find references / jump to definition
|
|
Then we get to =xref-js2=, which adds stuff for jumping to
|
|
references and definitions (uses the =ag= tool, so that must be
|
|
installed in the environment):
|
|
|
|
#+begin_src emacs-lisp
|
|
(use-package xref-js2)
|
|
#+end_src
|
|
|
|
=js-mode= binds =M-.=, which conflicts with =xref-js2= so we need to unbind that:
|
|
|
|
#+begin_src emacs-lisp
|
|
(define-key js-mode-map (kbd "M-.") nil)
|
|
#+end_src
|
|
|
|
And hook it up to =js2-mode=:
|
|
|
|
#+begin_src emacs-lisp
|
|
(add-hook 'js2-mode-hook (lambda ()
|
|
(add-hook 'xref-backend-functions #'xref-js2-xref-backend nil t)))
|
|
#+end_src
|
|
|
|
*** Using local tools from NPM
|
|
To use tools locally by NPM, there is =add-node-modules-path=:
|
|
|
|
#+begin_src emacs-lisp
|
|
(use-package add-node-modules-path)
|
|
(eval-after-load 'js2-mode
|
|
'(add-hook 'js2-mode-hook #'add-node-modules-path))
|
|
#+end_src
|
|
|
|
*** Autoformatting
|
|
[[https://prettier.io/][Prettier]] seems low-effort to set up :D
|
|
|
|
#+begin_src emacs-lisp
|
|
(use-package prettier-js)
|
|
(add-hook 'js2-mode-hook 'prettier-js-mode)
|
|
#+end_src
|
|
|
|
** Prolog
|
|
=prolog-mode= comes with Emacs, but .pl files are assumed to be
|
|
Perl (which I never use, it scares me), so we need to change that.
|
|
|
|
#+begin_src emacs-lisp
|
|
(add-to-list 'auto-mode-alist '("\\.pl\\'" . prolog-mode))
|
|
#+end_src
|
|
|
|
** Dockerfiles
|
|
Grab =dockerfile-mode= for syntax highlighting etc in Dockerfiles:
|
|
|
|
#+begin_src emacs-lisp
|
|
(use-package dockerfile-mode)
|
|
#+end_src
|
|
|
|
** Zig
|
|
=zig-mode= provides basic language integration for Zig:
|
|
|
|
#+begin_src emacs-lisp
|
|
(use-package zig-mode)
|
|
#+end_src
|
|
|
|
There's a language server implementation for Zig so we'll be using
|
|
that via =lsp-mode= alongside =zig-mode=.
|
|
|
|
#+begin_src emacs-lisp
|
|
(add-hook 'zig-mode-hook #'lsp-deferred)
|
|
#+end_src
|
|
|
|
** GLSL
|
|
Firstly, =glsl-mode= provides basic support:
|
|
|
|
#+begin_src emacs-lisp
|
|
(use-package glsl-mode)
|
|
#+end_src
|
|
|
|
It's a C-like language, so I want =bsd= code style and
|
|
=smart-tabs=. The former is easy:
|
|
|
|
#+begin_src emacs-lisp
|
|
(add-hook 'glsl-mode-hook
|
|
(lambda ()
|
|
(c-set-style "bsd")
|
|
(setq c-basic-offset 4)))
|
|
#+end_src
|
|
|
|
Since =smart-tabs= doesn't support GLSL out of the box, we need to
|
|
add support with [[help:smart-tabs-add-language-support][smart-tabs-add-language-support]]. There's an
|
|
example of how to use it on [[https://www.emacswiki.org/emacs/SmartTabs#h5o-5][Emacs Wiki]]:
|
|
|
|
#+begin_src emacs-lisp :tangle no
|
|
(smart-tabs-add-language-support c++ c++-mode-hook
|
|
((c-indent-line . c-basic-offset)
|
|
(c-indent-region . c-basic-offset)))
|
|
#+end_src
|
|
|
|
[[help:c-indent-line][c-indent-line]] et al will do fine for GLSL too since its syntax is
|
|
very similar to C's, so adding support for it looks very similar to
|
|
that example:
|
|
|
|
#+begin_src emacs-lisp
|
|
(smart-tabs-add-language-support glsl glsl-mode-hook
|
|
((c-indent-line . c-basic-offset)
|
|
(c-indent-region . c-basic-offset)))
|
|
#+end_src
|
|
|
|
Now that support is added, [[help:smart-tabs-insinuate][smart-tabs-insinuate]] should do its job:
|
|
|
|
#+begin_src emacs-lisp
|
|
(smart-tabs-insinuate 'glsl)
|
|
#+end_src
|
|
|
|
** Mermaid
|
|
Mermaid is a diagramming language. First of all we need syntax
|
|
highlighting etc. This is provided by =mermaid-mode=:
|
|
|
|
#+begin_src emacs-lisp
|
|
(use-package mermaid-mode)
|
|
#+end_src
|
|
|
|
Also install =ob-mermaid= to add mermaid support to org-babel:
|
|
|
|
#+begin_src emacs-lisp
|
|
(use-package ob-mermaid)
|
|
#+end_src
|
|
|
|
And finally allow execution of mermaid source blocks (used to view the
|
|
diagrams):
|
|
|
|
#+begin_src emacs-lisp
|
|
(enable-org-babel-lang 'mermaid)
|
|
#+end_src
|
|
|
|
** crontab
|
|
Using =crontab-mode=, because it's called crontab-mode lol
|
|
|
|
#+begin_src emacs-lisp
|
|
(use-package crontab-mode)
|
|
#+end_src
|
|
|
|
** Python
|
|
Going to use LSP for Python:
|
|
|
|
#+begin_src emacs-lisp
|
|
(add-hook 'python-mode-hook #'lsp-deferred)
|
|
#+end_src
|
|
|
|
** Ada
|
|
*** Old ada-mode
|
|
Unfortunately, the =ada-mode= on ELPA is hot garbage. It requires
|
|
a custom parser to be built from its sources, and as far as I can
|
|
tell it's completely broken: every version I've tried to build has
|
|
produced multiple compile errors.
|
|
|
|
There was a more basic =ada-mode= built in to Emacs, and
|
|
thankfully someone else has already done the hard work of bundling
|
|
that up -- [[https://tkurtbond.github.io/posts/2022/07/09/using-the-old-version-of-ada-mode-for-emacs/][Using the old version of Ada Mode for Emacs]]. They've
|
|
provided a ZIP file with all the neccessary files bundled into it,
|
|
which can be grabbed with the following:
|
|
|
|
#+begin_src shell
|
|
cd ~/Downloads
|
|
curl -LO https://tkurtbond.github.io/emacs/old-ada-mode.zip
|
|
unzip -d ~/.emacs.d old-ada-mode.zip
|
|
#+end_src
|
|
|
|
The directory =~/.emacs.d/ada-mode= than has to be added to
|
|
[[help:load-path][load-path]] and autoloaded:
|
|
|
|
#+begin_src emacs-lisp
|
|
(let* ((home (getenv "HOME"))
|
|
(path (concat home "/.emacs.d/ada-mode")))
|
|
(add-to-list 'load-path path))
|
|
|
|
(autoload 'ada-mode "ada-mode")
|
|
#+end_src
|
|
|
|
*** LSP
|
|
=lsp-mode= with =ada_language_server= provides all the IDE-esque
|
|
niceties:
|
|
|
|
#+begin_src emacs-lisp
|
|
(add-hook 'ada-mode-hook #'lsp-deferred)
|
|
#+end_src
|
|
|
|
[[https://github.com/AdaCore/ada_language_server][ada_language_server]] has to be installed manually. There are linux
|
|
builds available on the [[https://github.com/AdaCore/ada_language_server/releases][GitHub releases page]].
|
|
|
|
*** Indentation
|
|
Set the indent width to 4:
|
|
|
|
#+begin_src emacs-lisp
|
|
(setq ada-indent 4)
|
|
#+end_src
|
|
|
|
*** GPRBuild files
|
|
GPRBuild files are pretty straightforward, but there doesn't seem
|
|
to be a major mode on ELPA for them so going to make a basic one.
|
|
|
|
**** Custom major mode
|
|
Start out by defining a list of keywords:
|
|
|
|
#+begin_src emacs-lisp
|
|
(defvar gpr-keywords
|
|
'("case" "end" "external" "for" "is" "null"
|
|
"package" "project" "use" "when" "with"))
|
|
#+end_src
|
|
|
|
And a list of builtins:
|
|
|
|
#+begin_src emacs-lisp
|
|
(defvar gpr-builtins
|
|
'("Compiler" "Default_Switches" "Exec_Dir" "Library_Dir"
|
|
"Library_Kind" "Library_Name" "Local_Configuration_Pragmas"
|
|
"Main" "Object_Dir" "Source_Dirs"))
|
|
#+end_src
|
|
|
|
Define some font lock regexes:
|
|
|
|
#+begin_src emacs-lisp
|
|
(defvar gpr-font-lock-defaults
|
|
(let ((string-regex (rx (sequence "\"" (*? (not "\"")) "\"")))
|
|
(constant-regex (rx (or (+ digit) "True" "False"))))
|
|
`((,string-regex . font-lock-string-face)
|
|
(,constant-regex . font-lock-constant-face)
|
|
(,(regexp-opt gpr-builtins 'words) . font-lock-builtin-face)
|
|
(,(regexp-opt gpr-keywords 'words) . font-lock-keyword-face))))
|
|
#+end_src
|
|
|
|
Create a variable for the indent width:
|
|
|
|
#+begin_src emacs-lisp
|
|
(defvar gpr-indent-width 4)
|
|
#+end_src
|
|
|
|
We then need to define a function for indentation, which is
|
|
non-trivial. A simple set of rules that gets us most of the way is:
|
|
|
|
1. Start at indentation level 0
|
|
2. Decrease indentation level if the line starts with "end"
|
|
3. Indent to same level as a previous "end" line
|
|
4. Increase indentation level if the previous line ends with "is"
|
|
5. Otherwise indent to level 0
|
|
|
|
#+begin_src emacs-lisp
|
|
(defconst gpr-block-start-regex
|
|
(rx (sequence line-start
|
|
(zero-or-more not-newline)
|
|
"is"
|
|
(zero-or-more blank)
|
|
line-end)))
|
|
|
|
(defconst gpr-block-end-regex
|
|
(rx (sequence line-start
|
|
(zero-or-more blank)
|
|
"end")))
|
|
|
|
(defun gpr-indent-line ()
|
|
"Indent the current line as GPRBuild code"
|
|
(interactive)
|
|
(beginning-of-line)
|
|
(indent-line-to (gpr-get-indent-level)))
|
|
|
|
(defun gpr-get-indent-level ()
|
|
(cond ((bobp) 0)
|
|
((looking-at-p gpr-block-end-regex)
|
|
(save-excursion
|
|
(forward-line -1)
|
|
(max (- (current-indentation) gpr-indent-width) 0)))
|
|
(t (gpr-get-indent-level-from-previous))))
|
|
|
|
(defun gpr-get-indent-level-from-previous ()
|
|
(save-excursion
|
|
(let (indent)
|
|
(while (not indent)
|
|
(forward-line -1)
|
|
(setq indent
|
|
(cond ((looking-at-p gpr-block-start-regex)
|
|
(+ (current-indentation) gpr-indent-width))
|
|
((looking-at-p gpr-block-end-regex)
|
|
(current-indentation))
|
|
((bobp) 0))))
|
|
indent)))
|
|
#+end_src
|
|
|
|
Define the mode, inheriting from =prog-mode=:
|
|
|
|
#+begin_src emacs-lisp
|
|
(define-derived-mode gpr-mode prog-mode "GPRBuild"
|
|
"GPR Mode is a major mode for editing GPRBuild files"
|
|
(set (make-local-variable 'comment-start) "--")
|
|
(set (make-local-variable 'comment-end) "")
|
|
|
|
(set (make-local-variable 'font-lock-defaults)
|
|
'(gpr-font-lock-defaults))
|
|
|
|
(set (make-local-variable 'indent-line-function)
|
|
'gpr-indent-line))
|
|
#+end_src
|
|
|
|
Finally, add an [[help:auto-load-alist][auto-load-alist]] entry for =.gpr= files:
|
|
|
|
#+begin_src emacs-lisp
|
|
(add-to-list 'auto-mode-alist '("\\.gpr\\'" . gpr-mode))
|
|
#+end_src
|
|
|
|
** Lua
|
|
Just using basic =lua-mode= package:
|
|
|
|
#+begin_src emacs-lisp
|
|
(use-package lua-mode)
|
|
#+end_src
|
|
|
|
I want to indent with tabs (set to 4 characters wide):
|
|
|
|
#+begin_src emacs-lisp
|
|
(setq lua-indent-level 4)
|
|
#+end_src
|
|
|
|
I also want to be able to run =lua-format= on files with =C-c f=
|
|
like I have with =clang-format=. The first step for this is to make
|
|
an interactive function to run the formatter; this can be done with
|
|
[[help:call-process-region][call-process-region]].
|
|
|
|
#+begin_src emacs-lisp
|
|
(defvar lua-format-binary "lua-format")
|
|
|
|
(defun lua-format ()
|
|
(interactive)
|
|
(if (executable-find lua-format-binary)
|
|
(let ((start (if (region-active-p) (region-beginning) (point-min)))
|
|
(end (if (region-active-p) (region-end) (point-max))))
|
|
(call-process-region start end lua-format-binary t '(t nil)))
|
|
(error "%s" (concat lua-format-binary " not found."))))
|
|
#+end_src
|
|
|
|
This then needs to be assigned to the keybinding:
|
|
|
|
#+begin_src emacs-lisp
|
|
(add-hook
|
|
'lua-mode-hook
|
|
(lambda () (define-key lua-mode-map (kbd "C-c f") 'lua-format)))
|
|
#+end_src
|
|
|
|
** BASIC
|
|
=basic-mode= provides syntax highlighting and a few nice features:
|
|
|
|
#+begin_src emacs-lisp
|
|
(use-package basic-mode)
|
|
#+end_src
|
|
|
|
As well as =.bas= files, I want to open all =.bbc= files in
|
|
=basic-mode=:
|
|
|
|
#+begin_src emacs-lisp
|
|
(add-to-list 'auto-mode-alist '("\\.bbc\\'" . basic-mode))
|
|
#+end_src
|
|
|
|
** Nix
|
|
Basic editing support comes from =nix-mode=:
|
|
|
|
#+begin_src emacs-lisp
|
|
(use-package nix-mode)
|
|
#+end_src
|
|
|
|
And =nix-update= provides a convenient way to update ~fetch~
|
|
blocks:
|
|
|
|
#+begin_src emacs-lisp
|
|
(use-package nix-update)
|
|
#+end_src
|
|
|
|
** SCAD
|
|
There is a language server for OpenSCAD, but I think I'll just
|
|
stick to the basic mode:
|
|
|
|
#+begin_src emacs-lisp
|
|
(use-package scad-mode)
|
|
#+end_src
|
|
|
|
** Go
|
|
First of all, of course, install =go-mode=:
|
|
|
|
#+begin_src emacs-lisp
|
|
(use-package go-mode)
|
|
#+end_src
|
|
|
|
This package provides a convenient lil function to use gofmt to
|
|
format a buffer; I want to run this whenever I save a go source
|
|
file. This is pretty easily done by adding a =before-save-hook= in
|
|
a =go-mode-hook= (hey, I heard you like hooks...)
|
|
|
|
#+begin_src emacs-lisp
|
|
(add-hook 'go-mode-hook
|
|
(lambda ()
|
|
(add-hook 'before-save-hook 'gofmt-before-save)))
|
|
#+end_src
|
|
|
|
* Tool Integrations
|
|
** Git
|
|
=magit= is truly a wonderful creation! Add keybinding for
|
|
=magit-status= and a maximum length for the summary line of commit
|
|
messages (after which the excess is highlighted).
|
|
|
|
#+begin_src emacs-lisp
|
|
(use-package magit
|
|
:bind
|
|
("C-x g" . magit-status)
|
|
:config
|
|
(setq git-commit-summary-max-length 72))
|
|
#+end_src
|
|
|
|
By default, =magit-status= will open itself in a different window
|
|
to the one you open it in. I really don't understand how this is
|
|
useful but thankfully this is Emacs so the behaviour can be
|
|
tweaked. The default behaviour does make sense for other magit
|
|
windows, just not magit-status.
|
|
|
|
The behviour I want can be achieved by setting
|
|
[[help:magit-display-buffer-function][magit-display-buffer-function]] to something which will open the
|
|
buffer in the current window if and only if it's a
|
|
=magit-status-mode= window.
|
|
|
|
#+begin_src emacs-lisp
|
|
(setq magit-display-buffer-function
|
|
(lambda (buffer)
|
|
(display-buffer
|
|
buffer
|
|
(when (eq (with-current-buffer buffer major-mode)
|
|
'magit-status-mode)
|
|
'(display-buffer-same-window)))))
|
|
#+end_src
|
|
|
|
Now I'm thinking it I could customise this further as I often am
|
|
annoyed by diffs opening in a different window but I think I'll
|
|
leave it at that for now as I'm not sure precisely what behaviour
|
|
I'd want.
|
|
|
|
** Docker
|
|
I use docker quite a lot, unfortunately, so it's nice to be able to
|
|
spawn containers etc from Emacs. The =docker= package provides a
|
|
few nice bits and bobs.
|
|
|
|
#+begin_src emacs-lisp
|
|
(use-package docker
|
|
:bind ("C-c d" . docker))
|
|
#+end_src
|
|
|
|
** Build systems
|
|
*** CMake
|
|
I hate it, but it's everywhere. =cmake-mode= provides basic syntax
|
|
highlighting etc.
|
|
|
|
#+begin_src emacs-lisp
|
|
(use-package cmake-mode)
|
|
#+end_src
|
|
|
|
*** Meson
|
|
Use =meson-mode= for syntax highlighting etc in meson.build files.
|
|
|
|
#+begin_src emacs-lisp
|
|
(use-package meson-mode)
|
|
#+end_src
|
|
|
|
*** Cargo
|
|
I never really use Rust without Cargo, so always turn on the minor
|
|
mode for Cargo in Rust buffers.
|
|
|
|
#+begin_src emacs-lisp
|
|
(use-package cargo)
|
|
(add-hook 'rust-mode-hook 'cargo-minor-mode)
|
|
#+end_src
|
|
|
|
** clang-format
|
|
Most of the time, =lsp-mode= is fine for formatting, but sometimes
|
|
it doesn't work (mostly just because I haven't gone through the
|
|
effort to set it up) but I still want to be able to auto-format
|
|
code easily (that is to say, with a convenient keybinding). The
|
|
=clang-format= package provides Elisp functions for invoking it.
|
|
|
|
#+begin_src emacs-lisp
|
|
(use-package clang-format)
|
|
#+end_src
|
|
|
|
I want a keybinding that formats the region if its active, or the
|
|
whole buffer otherwise. It seems that there's no function which
|
|
does that out of the box, so that has to be defined first:
|
|
|
|
#+begin_src emacs-lisp
|
|
(defun clang-format-region-or-buffer ()
|
|
"Format the region if it's active, otherwise format the entire buffer."
|
|
(interactive)
|
|
(if (use-region-p)
|
|
(clang-format-region (region-beginning) (region-end))
|
|
(clang-format-buffer)))
|
|
#+end_src
|
|
|
|
With that defined, the keybinding can be added to C mode.
|
|
|
|
#+begin_src emacs-lisp
|
|
(add-hook
|
|
'c-mode-hook
|
|
(lambda ()
|
|
(define-key c-mode-map (kbd "C-c f")
|
|
'clang-format-region-or-buffer)))
|
|
#+end_src
|
|
|
|
** ChatGPT
|
|
The =chatpt-shell= package provides a shell-like interface for
|
|
ChatGPT.
|
|
|
|
#+begin_src emacs-lisp
|
|
(use-package chatgpt-shell)
|
|
#+end_src
|
|
|
|
[[help:chatgpt-shell-openai-key][chatgpt-shell-openai-key]] must also be set to a function that
|
|
returns an OpenAI API key. I have a key (made on [[https://platform.openai.com/account/api-keys][this page]]) stored
|
|
in =pass(1)= under =openai/api-key=, so this can be retrieved via
|
|
[[help:process-lines][process-lines]]:
|
|
|
|
#+begin_src emacs-lisp
|
|
(setq chatgpt-shell-openai-key
|
|
(lambda ()
|
|
(car (process-lines "pass" "openai/api-key"))))
|
|
#+end_src
|
|
|
|
** GraphViz
|
|
The =graphviz-dot-mode= package provides some integration for the
|
|
GraphViz suite of tools, and a mode for the 'dot' language they
|
|
use:
|
|
|
|
#+begin_src emacs-lisp
|
|
(use-package graphviz-dot-mode)
|
|
#+end_src
|
|
|
|
* Backup and Autosave
|
|
** Keep $PWD Tidy
|
|
Emacs' default behaviour of dumping temporary files in the current
|
|
directory is quite a pain, so we want to get it to instead stick
|
|
them in a dedicated directory somewhere far away.
|
|
|
|
We can do that for auto-save files by setting the variable
|
|
[[help:auto-save-file-name-transforms][auto-save-file-name-transforms]]:
|
|
|
|
#+begin_src emacs-lisp
|
|
(make-directory "~/.emacs-tmp/auto-save" t)
|
|
(setq auto-save-file-name-transforms '((".*" "~/.emacs-tmp/auto-save" t)))
|
|
#+end_src
|
|
|
|
And for backup files there's [[help:backup-directory-alist][backup-directory-alist]].
|
|
|
|
#+begin_src emacs-lisp
|
|
(make-directory "~/.emacs-tmp/backup" t)
|
|
(setq backup-directory-alist '(("." . "~/.emacs-tmp/backup")))
|
|
#+end_src
|
|
|
|
** Backup by Copying
|
|
By default Emacs moves a file to the backup location and then
|
|
creates a copy in the original location, which apart from being a
|
|
very strange thing to do also messes up hard links. Setting
|
|
[[help:backup-by-copying][backup-by-copying]] changes it to the more obvious behaviour of
|
|
simply copying the file to the backup location.
|
|
|
|
#+begin_src emacs-lisp
|
|
(setq backup-by-copying t)
|
|
#+end_src
|
|
|
|
** Use trash
|
|
Commands like [[help:delete-file][delete-file]] and [[help:delete-directory][delete-directory]], as well as deletion
|
|
commands in Dired can be made to move things to trash, instead of
|
|
permanently deleting them. This done by setting the
|
|
[[help:delete-by-moving-to-trash][delete-by-moving-to-trash]] variable to ~t~.
|
|
|
|
#+begin_src emacs-lisp
|
|
(setq delete-by-moving-to-trash t)
|
|
#+end_src
|
|
|
|
* Mail
|
|
Using Mu4e. It requires =mu(1)= tool be set up already. The mu4e
|
|
emacs package is bundled with the system package rather than
|
|
distributed seperately, so if it's been put somewhere on the
|
|
[[help:load-path][load-path]] we'd just need to ~require~ it — this is the case on the
|
|
linux distros that I've used. On OpenBSD, though, packages are
|
|
installed into =/usr/local/= and =/usr/local/share/emacs/site-lisp=
|
|
is not in =load-path=, so we need to add it.
|
|
|
|
#+begin_src emacs-lisp
|
|
(when (string-match-p "OpenBSD" (shell-command-to-string "uname -a"))
|
|
(add-to-list 'load-path "/usr/local/share/emacs/site-lisp"))
|
|
(require 'mu4e)
|
|
#+end_src
|
|
|
|
On Bedrock linux, Mu4e can't the =mu= binary as it's not in a
|
|
standard location. The location can be specified explicitly by
|
|
setting [[help:mu4e-mu-binary][mu4e-mu-binary]]. To check whether we're on Bedrock, we'll see
|
|
if the =brl= program is available, as =uname= will just yield
|
|
whichever distro bedrock booted from.
|
|
|
|
#+begin_src emacs-lisp
|
|
(when (< 0 (length (shell-command-to-string "which brl")))
|
|
(setq mu4e-mu-binary "/bedrock/cross/bin/mu"))
|
|
#+end_src
|
|
|
|
The folder archived mail gets saved into is determined by
|
|
[[help:mu4e-refile-folder][mu4e-refile-folder]]. I prefer to have archived mail stored on the
|
|
remote since then it's accessible from every machine. The default is
|
|
=/archive=, though, which is outside of any remotes. Easiest thing to
|
|
do is hardcode =/wip/archive= as the refile folder.
|
|
|
|
#+begin_src emacs-lisp
|
|
(setq mu4e-refile-folder "/wip/archive")
|
|
#+end_src
|
|
|
|
This only really works because I only have one email account I use
|
|
with Mu4e. It would archive mail from /all/ accounts onto the wip.sh
|
|
mail server.
|
|
|
|
The final general Mu4e thing is stopping it from prompting on exit:
|
|
|
|
#+begin_src emacs-lisp
|
|
(setq mu4e-confirm-quit nil)
|
|
#+end_src
|
|
|
|
** Fetching
|
|
Use =offlineimap(1)= (also needs to be set up seperately) to sync the
|
|
maildir with the remote server:
|
|
|
|
#+begin_src emacs-lisp
|
|
(setq mu4e-get-mail-command "offlineimap")
|
|
#+end_src
|
|
|
|
** Sending
|
|
To send mail we first need to set the mail address:
|
|
|
|
#+begin_src emacs-lisp
|
|
(setq user-mail-address "cdo@wip.sh")
|
|
#+end_src
|
|
|
|
Then set the sent and drafts folders to inside the remote folder:
|
|
|
|
#+begin_src emacs-lisp
|
|
(setq mu4e-sent-folder "/wip/sent"
|
|
mu4e-drafts-folder "/wip/drafts")
|
|
#+end_src
|
|
|
|
And finally configure SMTP:
|
|
|
|
#+begin_src emacs-lisp
|
|
(setq smtpmail-smtp-server "mail.wip.sh"
|
|
smtpmail-smtp-service 587
|
|
smtpmail-stream-type 'starttls
|
|
send-mail-function 'smtpmail-send-it)
|
|
#+end_src
|
|
|
|
* Remote Access
|
|
** Sudo/doas on Remote Hosts
|
|
To edit files as root on remote hosts with sudo or doas while also
|
|
tunneling over SSH, you need to configure TRAMP to use SSH as a
|
|
proxy. This is done by adding to [[help:tramp-default-proxies-alist][tramp-default-proxies-alist]], as
|
|
detailed in [[info:tramp#Multi-hops][the TRAMP manual]]:
|
|
|
|
#+begin_src emacs-lisp
|
|
(add-to-list 'tramp-default-proxies-alist
|
|
'(nil "\\`root\\'" "/ssh:%h:"))
|
|
(add-to-list 'tramp-default-proxies-alist
|
|
'((regexp-quote (system-name)) nil nil))
|
|
#+end_src
|
|
|
|
* Printing
|
|
#+begin_src emacs-lisp
|
|
(setq ps-paper-type 'a4
|
|
ps-font-size 10
|
|
ps-print-header nil)
|
|
#+end_src
|
|
|
|
* Misc
|
|
** God mode
|
|
God mode essentially makes Emacs a bit more VI-like by introducing
|
|
a mode where modifier keys are implicitly held down, thereby
|
|
reducing the amount of 'emacs claw' required for most commands. It
|
|
runs as a global minor mode.
|
|
|
|
#+begin_src emacs-lisp
|
|
(use-package god-mode)
|
|
(god-mode)
|
|
#+end_src
|
|
|
|
In order to enter normal mode, [[help:god-mode-all][god-mode-all]] must be ran, so we'll
|
|
bind =C-.= to that:
|
|
|
|
#+begin_src emacs-lisp
|
|
(global-set-key (kbd "C-.") #'god-mode-all)
|
|
#+end_src
|
|
|
|
I find it jarring and confusing to have it on and off in different
|
|
types of buffers, so will just disable the exemptions:
|
|
|
|
#+begin_src emacs-lisp
|
|
(setq god-exempt-major-modes nil)
|
|
(setq god-exempt-predicates nil)
|
|
#+end_src
|
|
|
|
** Case-flipping
|
|
Want to be able to toggle the 'shiftedness' of a selected region,
|
|
that is, map uppercase to lowercase and vice versa, but also map
|
|
things like '1' to '!' and '[' to '{'.
|
|
|
|
I doubt there's anything in Emacs already that has that mapping for
|
|
non-alphabetic characters, so first thing to do is define that:
|
|
|
|
#+begin_src emacs-lisp
|
|
(defvar non-letter-case-mapping
|
|
'((?1 . ?!) (?2 . ?\") (?3 . ?£) (?4 . ?$) (?5 . ?%)
|
|
(?6 . ?^) (?7 . ?&) (?8 . ?*) (?9 . ?\() (?0 . ?\))
|
|
(?- . ?_) (?= . ?+) (?\` . ?¬) (?\\ . ?\|) (?\[ . ?{)
|
|
(?\] . ?}) (?\; . ?:) (?\' . ?@) (?\# . ?~) (?\, . ?<)
|
|
(?\. . ?>) (?/ . ??)))
|
|
#+end_src
|
|
|
|
And then, a function to toggle a non-letter character, using that
|
|
mapping, defaulting to the identity if there's no entry.
|
|
|
|
#+begin_src emacs-lisp
|
|
(defun flip-non-letter-case (c)
|
|
(let (value)
|
|
(dolist (case-pair non-letter-case-mapping value)
|
|
(cond ((eq (car case-pair) c)
|
|
(setq value (cdr case-pair)))
|
|
((eq (cdr case-pair) c)
|
|
(setq value (car case-pair)))))
|
|
(when (eq value nil)
|
|
(setq value c))
|
|
value))
|
|
#+end_src
|
|
|
|
A similar function for letters can be easily defined using [[help:upcase][upcase]]
|
|
and [[help:downcase][downcase]]:
|
|
|
|
#+begin_src emacs-lisp
|
|
(defun upper-case-p (c)
|
|
(eq (upcase c) c))
|
|
|
|
(defun flip-letter-case (c)
|
|
(if (upper-case-p c)
|
|
(downcase c)
|
|
(upcase c)))
|
|
#+end_src
|
|
|
|
These can then be combined into a case-flipping function that will
|
|
work for both letters and non-letters:
|
|
|
|
#+begin_src emacs-lisp
|
|
(defun letter-p (c)
|
|
(and (characterp c)
|
|
(let ((uc (upcase c)))
|
|
(and (>= uc ?A) (<= uc ?Z)))))
|
|
|
|
(defun flip-char-case (c)
|
|
(if (letter-p c)
|
|
(flip-letter-case c)
|
|
(flip-non-letter-case c)))
|
|
#+end_src
|
|
|
|
~flip-char-case~ can then applied over a whole string:
|
|
|
|
#+begin_src emacs-lisp
|
|
(defun flip-string-case (s)
|
|
(let ((len (length s))
|
|
(i 0))
|
|
(while (< i len)
|
|
(aset s i (flip-char-case (aref s i)))
|
|
(setq i (1+ i)))
|
|
s))
|
|
#+end_src
|
|
|
|
Finally, this can then be applied to the region, if it's active:
|
|
|
|
#+begin_src emacs-lisp
|
|
(defun flip-region-case ()
|
|
(interactive)
|
|
(when (region-active-p)
|
|
(let* ((start (region-beginning))
|
|
(end (region-end))
|
|
(text (buffer-substring-no-properties start end)))
|
|
(delete-region start end)
|
|
(insert (flip-string-case text)))))
|
|
#+end_src
|
|
|
|
And of course, I need a keybinding for that:
|
|
|
|
#+begin_src emacs-lisp
|
|
(global-set-key (kbd "C-~") 'flip-region-case)
|
|
#+end_src
|
|
|
|
* Fin
|
|
Now that start-up is finished, [[help:gc-cons-threshold][gc-cons-threshold]] and
|
|
[[help:gc-cons-percentage][gc-cons-percentage]] need to be set back to reasonable values to avoid
|
|
memory usage getting too high.
|
|
|
|
#+begin_src emacs-lisp
|
|
(setq gc-cons-threshold 1000000)
|
|
(setq gc-cons-percentage 0.2)
|
|
#+end_src
|