Compare commits
6 Commits
665edb56cd
...
c59d1c8e73
| Author | SHA1 | Date | |
|---|---|---|---|
| c59d1c8e73 | |||
| 1f7d38e03f | |||
| 74af0b81b3 | |||
| 76aad81ca2 | |||
| bfe6414f98 | |||
| d9d2042459 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -13,3 +13,4 @@ session.*
|
||||
eshell/*
|
||||
*.data
|
||||
auto-save-list/*
|
||||
eln-cache/*
|
||||
|
||||
331
config.org
331
config.org
@@ -2,6 +2,17 @@
|
||||
#+AUTHOR: Camden Dixie O'Brien
|
||||
#+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
|
||||
Hey, customize, leave my ~/.emacs.d/init.el alone!
|
||||
|
||||
@@ -826,25 +837,6 @@
|
||||
(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...
|
||||
|
||||
@@ -1396,25 +1388,6 @@
|
||||
'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
|
||||
@@ -1435,6 +1408,186 @@
|
||||
(setq Man-notify-method 'pushy)
|
||||
#+end_src
|
||||
|
||||
** Script-Fu Mode
|
||||
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 Mode
|
||||
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 | Content |
|
||||
|-------+-----------------------------------------|
|
||||
| 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 Mode
|
||||
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:
|
||||
|
||||
#+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-buffer (script-fu-repl))
|
||||
(repl-proc (get-buffer-process repl-buffer)))
|
||||
(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-buffer (script-fu-repl))
|
||||
(repl-proc (get-buffer-process repl-buffer))
|
||||
(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
|
||||
** Keep $PWD Tidy
|
||||
Emacs' default behaviour of dumping temporary files in the current
|
||||
@@ -1525,104 +1678,10 @@
|
||||
(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.
|
||||
* Coda
|
||||
Now that initialization is finished, [[help:gc-cons-threshold][gc-cons-threshold]] should be set
|
||||
back to its default value:
|
||||
|
||||
#+begin_src emacs-lisp
|
||||
(setq gc-cons-threshold 1000000)
|
||||
(setq gc-cons-percentage 0.2)
|
||||
(setq gc-cons-threshold original-gc-cons-threshold)
|
||||
#+end_src
|
||||
|
||||
Reference in New Issue
Block a user