Compare commits

..

11 Commits

Author SHA1 Message Date
33e63ccda2 Tweak Script-Fu config a little 2025-01-08 13:38:02 +00:00
20e426dc69 Remove journal files config
I don't use these anymore, instead I hand-write stuff on my e-ink
tablet like the techno-contrarian qípā that I am.
2025-01-08 13:13:11 +00:00
d855179951 Remove calendar and diary config 2025-01-08 13:11:45 +00:00
02f13ff739 Fix typo in subsection heading 2025-01-08 13:11:00 +00:00
60a0b1c247 Prune out unused org-mode features
It was indeed funny to read the bit in the agenda config.
2025-01-08 13:10:39 +00:00
68fc323efd Set GC threshold high at start of initialisation
I did have this in here before and the code to set it back was still
at the end of the file, so not sure what happened there.  I guess I
accidentally removed it at some point.
2025-01-08 13:04:24 +00:00
1f7d38e03f Add eln-cache to .gitignore 2025-01-08 12:39:39 +00:00
74af0b81b3 Implement Script-Fu mode 2025-01-08 12:39:39 +00:00
76aad81ca2 Remove case-flipping config
I never actually use it (I mostly wrote it to impress Ava) so time for
it to be relegated to the git history.
2025-01-08 12:39:39 +00:00
bfe6414f98 Remove chatgpt-shell config 2025-01-08 12:39:39 +00:00
d9d2042459 Remove Geiser config 2025-01-08 12:39:39 +00:00
2 changed files with 197 additions and 360 deletions

1
.gitignore vendored
View File

@@ -13,3 +13,4 @@ session.*
eshell/* eshell/*
*.data *.data
auto-save-list/* auto-save-list/*
eln-cache/*

View File

@@ -2,6 +2,17 @@
#+AUTHOR: Camden Dixie O'Brien #+AUTHOR: Camden Dixie O'Brien
#+ATTR_LATEX: :float t #+ATTR_LATEX: :float t
* Prelude
Before doing anything else, set a higher [[help:gc-cons-threshold][gc-cons-threshold]] (I'm
using 100 MiB here) so that we're not garbage collecting during
initialisation to make startup a little faster. We'll save the
original value to restore it at the end of initialisation.
#+begin_src emacs-lisp
(setq original-gc-cons-threshold gc-cons-threshold)
(setq gc-cons-threshold (* 100 1024 1024))
#+end_src
* Customize * Customize
Hey, customize, leave my ~/.emacs.d/init.el alone! Hey, customize, leave my ~/.emacs.d/init.el alone!
@@ -222,35 +233,6 @@
(yas-reload-all) (yas-reload-all)
#+end_src #+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 * Org
** Code and Quote block shortcuts ** Code and Quote block shortcuts
I am a big fan of using =<s= for source blocks and =<q= for quotes; I am a big fan of using =<s= for source blocks and =<q= for quotes;
@@ -282,91 +264,6 @@
'org-insert-heading-after-current))) 'org-insert-heading-after-current)))
#+end_src #+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 ** Source Blocks
Pressing tab inside a source block should indent appropriately for its Pressing tab inside a source block should indent appropriately for its
language. language.
@@ -494,116 +391,7 @@
(auto-mode . emacs))) (auto-mode . emacs)))
#+end_src #+end_src
** Workflow States ** Indentation
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 Setting [[help:org-adapt-indentation][org-adapt-indentation]] to ~t~ ensures that Org will indent text
under a headline: under a headline:
@@ -826,25 +614,6 @@
(paredit-mode)))) (paredit-mode))))
#+end_src #+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 ** YAML
I don't really like YAML if I'm honest, but it's used a lot so... I don't really like YAML if I'm honest, but it's used a lot so...
@@ -1396,25 +1165,6 @@
'clang-format-region-or-buffer))) 'clang-format-region-or-buffer)))
#+end_src #+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 ** GraphViz
The =graphviz-dot-mode= package provides some integration for the The =graphviz-dot-mode= package provides some integration for the
GraphViz suite of tools, and a mode for the 'dot' language they GraphViz suite of tools, and a mode for the 'dot' language they
@@ -1435,6 +1185,186 @@
(setq Man-notify-method 'pushy) (setq Man-notify-method 'pushy)
#+end_src #+end_src
** Script-Fu
GIMP has a scheme-based language -- Script-Fu -- built into it that
you can use to script things (based). Sadly, the built-in console
is rather lackluster as a coding environment. Happily, there /is/
an option to run a server which listens for Script-Fu commands on a
TCP port, so I can use =comint= to make my own lil interface in
Emacs.
It's things like this that make me really glad I switched to Emacs
because this is ridiculously cool. By my definition of "cool"
anyway (what can I say, I'm a massive nerd).
I should probably extract this and make a standalone package out of
it and stick it on Melpa at some point.
*** REPL
The Script-Fu server request format is very simple:
| Bytes | Description |
|-------+-----------------------------------------------|
| 0 | 'G' magic byte (47h) |
| 1-2 | Length of expression (BE 32-bit unsigned int) |
| 3+ | Expression |
Writing an encoder for this is pretty trivial:
#+begin_src emacs-lisp
(defun script-fu-repl-encode-request (input)
(let* ((len (length input))
(hi (logand (lsh len -8) #xff))
(lo (logand len #xff))
(hdr (vector ?G hi lo)))
(vconcat hdr (encode-coding-string input 'utf-8))))
#+end_src
We then want a sender function to use with [[help:comint-mode][comint-mode]] that
applies this encoding. Unfortunately, it seems that there is no
=comint-send-bytes= or similar function to directly send a byte
vector to the comint process. I did try just sending the request
as a string with some invalid characters at the start but ran into
issues: Emacs would sometimes insert unicode control characters
into the data, which GIMP understandably didn't appreciate.
The method I ended up with is to create a temporary, unibyte
buffer, stick the data in there and then use [[help:comint-send-region][comint-send-region]] to
send the data. It's a bit of a kludge but it seems like it should
be reasonably robust.
#+begin_src emacs-lisp
(defun script-fu-repl-comint-send-bytes (proc bytes)
(let ((temp-buffer (generate-new-buffer "*script-fu-repl-tmp*")))
(unwind-protect
(with-current-buffer temp-buffer
(set-buffer-multibyte nil)
(insert (apply #'string (append bytes nil)))
(comint-send-region proc (point-min) (point-max)))
(kill-buffer temp-buffer))))
#+end_src
With that handled, implementing the sender function itself is nice
and easy:
#+begin_src emacs-lisp
(defun script-fu-repl-send (proc input)
(let ((request (script-fu-repl-encode-request input)))
(script-fu-repl-comint-send-bytes proc request)))
#+end_src
The response format is similarly simple:
| Bytes | Description |
|-------+-----------------------------------------|
| 0 | 'G' magic byte (47h) |
| 1 | Status code -- 0 on success, 1 on error |
| 2-3 | Length of response text |
| 4 | Response text |
For now, we only care about the response text, so all we need to do
is skip the first 4 bytes and add a trailing newline.
#+begin_src emacs-lisp
(defun script-fu-repl-decode-response (response)
(concat (substring response 4) "\n"))
#+end_src
Another thing is adding a prompt to the comint buffer -- the
server doesn't send one, so we have to add it ourselves.
#+begin_src emacs-lisp
(defvar script-fu-repl-prompt "> ")
(defun script-fu-repl-insert-prompt (output)
(unless (string-blank-p output)
(let ((proc (get-buffer-process (current-buffer))))
(goto-char (process-mark proc))
(unless (looking-back script-fu-repl-prompt)
(insert script-fu-repl-prompt)
(set-marker (process-mark proc) (point)))))
output)
#+end_src
A mode for the client buffer can then be derived from [[help:comint-mode][comint-mode]].
#+begin_src emacs-lisp
(define-derived-mode script-fu-repl-mode comint-mode "Script-Fu REPL"
(setq-local comint-prompt-read-only t)
(setq-local comint-prompt-regexp nil)
(setq-local comint-input-sender #'script-fu-repl-send)
(add-hook 'comint-preoutput-filter-functions
'script-fu-repl-decode-response nil t)
(add-hook 'comint-output-filter-functions
'script-fu-repl-insert-prompt nil t))
#+end_src
Now, to create a function to create or get the current REPL
buffer. The [[help:comint-check-proc][comint-check-proc]] function can be used to test
whether the buffer is already set up. Rather nicely,
[[help:make-comint-in-buffer][make-comint-in-buffer]] supports passing a ~(HOST . SERVICE)~ pair
to specify a TCP connection to open (via [[help:open-network-stream][open-network-stream]]) so
this is pretty simple. In both cases, we want to return the
client buffer for the caller to use.
#+begin_src emacs-lisp
(defvar script-fu-repl-server '("localhost" . 10008))
(defun script-fu-repl ()
(interactive)
(let ((buffer (get-buffer-create "*Script-Fu REPL*")))
(when (not (comint-check-proc buffer))
(make-comint-in-buffer "Script-Fu REPL"
buffer script-fu-repl-server)
(with-current-buffer buffer (script-fu-repl-mode)))
(pop-to-buffer buffer '((display-buffer-in-direction)
(direction . below)
(window-height . 0.3)))
buffer))
#+end_src
*** Code Editing
With the client stuff done, we can define the code editing mode:
#+begin_src emacs-lisp
(define-derived-mode script-fu-mode scheme-mode "Script-Fu")
#+end_src
Now to define something to send an expression or region to the
REPL. Since =script-fu-repl= returns the buffer we can use that
to transparently start a REPL or get the existing one if one's
already running.
#+begin_src emacs-lisp
(defun script-fu-mode-send-region-or-sexp ()
(interactive)
(let ((code (if (use-region-p)
(let ((start (region-beginning))
(end (region-end)))
(buffer-substring-no-properties start end))
(thing-at-point 'sexp t))))
(if (not code) (message "No code to send.")
(let* ((repl-proc (get-buffer-process (script-fu-repl))))
(script-fu-repl-send repl-proc code)))))
(define-key script-fu-mode-map (kbd "C-c C-c")
'script-fu-mode-send-region-or-sexp)
#+end_src
And finally, a similar thing for the whole file:
#+begin_src emacs-lisp
(defun script-fu-mode-send-file ()
(interactive)
(let* ((repl-proc (get-buffer-process (script-fu-repl)))
(buffer-contents
(buffer-substring-no-properties (point-min)
(point-max))))
(script-fu-repl-send repl-proc buffer-contents)))
(define-key script-fu-mode-map (kbd "C-c C-l")
'script-fu-mode-send-file)
#+end_src
I think that's all I need for now!
* Backup and Autosave * Backup and Autosave
** Keep $PWD Tidy ** Keep $PWD Tidy
Emacs' default behaviour of dumping temporary files in the current Emacs' default behaviour of dumping temporary files in the current
@@ -1525,104 +1455,10 @@
(setq god-exempt-predicates nil) (setq god-exempt-predicates nil)
#+end_src #+end_src
** Case-flipping * Coda
Want to be able to toggle the 'shiftedness' of a selected region, Now that initialization is finished, [[help:gc-cons-threshold][gc-cons-threshold]] should be set
that is, map uppercase to lowercase and vice versa, but also map back to its default value:
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 #+begin_src emacs-lisp
(setq gc-cons-threshold 1000000) (setq gc-cons-threshold original-gc-cons-threshold)
(setq gc-cons-percentage 0.2)
#+end_src #+end_src