Refactoring Emacs Lisp with ChatGPT
Why?
Look… If you haven’t been living under a rock, you’ve probably noticed that ChatGPT is a hot topic on the internet.
Rightfully so, if you ask me. This thing is amazing. I got no words.
I am not going to explain it because I do not feel like I am the right person to do so. Just visit openai.com and see for yourself. However here are a few words from the devs:
ChatGPT is fine-tuned from a model in the GPT-3.5 series, which finished training in early 2022.
Since I am no wiz kid, when it come to write Emacs lisp I needed to give it a shot, right?
What?
I of course tried out a lot of things, both code-related and not. However, I am only going to talk about a few functions in my Emacs configuration that I – or rather, ChatGPT
– refactored.
For now, I am going to list the before and after versions for lack of more time. I just did not want to miss the opportunity to write a post for posterity (mostly for myself).
Macro to require
my custom modules
(defmacro timu-func-require-modules (&rest modules)
"Load MODULES, if they are not already loaded and are installed.
Skips any modules that are not found.
Displays a message in the minibuffer for any modules that are not found.
Displays a message in the minibuffer for any modules that are already loaded.
Credit: https://chat.openai.com/chat."
`(progn
,@(mapcar (lambda (module)
`(when (not (featurep ',module))
(if (locate-library ,(symbol-name module))
(progn
(require ',module)
(message "Loaded module '%s'" ',module))
(message "Module '%s' not found" ',module))))
modules)))
With this my custom functions to load the modules at startup change from this:
(defun timu-func-load-modules ()
"Load all custom the modules using `require'.
The order in which the modules are loaded is important."
(interactive)
(progn
(require 'timu-defaults)
(require 'timu-personal)
(require 'timu-evil)
(require 'timu-ui)
(require 'timu-dired)
(pcase system-type
((or'darwin 'gnu/linux)
(require 'timu-pdf)))
(require 'timu-org)
(require 'timu-nav)
(require 'timu-minibuffer)
(require 'timu-transients)
(require 'timu-editor)
(require 'timu-shell)
(pcase system-type
((or'darwin 'gnu/linux)
(require 'timu-git)))
(if timu-defaults-wsl-p
(message "No mu4e for wsl")
(pcase system-type
((or'darwin 'gnu/linux)
(require 'timu-mu4e))))
(require 'timu-elfeed)
(pcase system-type
((or'darwin 'gnu/linux)
(require 'timu-prog)))
(pcase system-type
((or'darwin 'gnu/linux)
(require 'timu-latex)))
(require 'timu-major-modes)
(pcase system-type
((or'darwin 'gnu/linux)
(require 'timu-work)))
(require 'timu-fun)
(require 'timu-map)
(require 'timu-keys)
(require 'timu-scratch)))
to this:
(defun timu-func-load-modules ()
"Load all custom the modules using `require'.
The order in which the modules are loaded is important."
(interactive)
(timu-func-require-modules
timu-defaults
timu-personal
timu-evil
timu-ui
timu-dired
timu-org
timu-nav
timu-minibuffer
timu-major-modes
timu-transients
timu-editor
timu-shell
timu-fun
timu-map
timu-keys
timu-scratch)
(pcase system-type
((or 'darwin 'gnu/linux)
(timu-func-require-modules
timu-pdf
timu-git
timu-mu4e
timu-elfeed
timu-prog
timu-latex
timu-work))))
As a little bonus, using the macro also reduced the Emacs startup time by about 1.5 seconds.
Function to attach files in mu4e with less prompts
(defun timu-func-mu4e-attach-file ()
"Attach a file to an email.
Use the built-in function `mml-attach-file'."
(interactive)
(let ((default-directory "~/"))
(let ((file (read-file-name "Select a file to attach: ")))
(mml-attach-file (expand-file-name file)))))
Heavy refactoring of some functions
Since I am being lazy for this part, I am going to dump a diff here.
This included:
- Using
let
in a function instead of defining variables separately - Using
lamdas
inside of functions instead of several helper functions - And much more…
Listen… I know that some of it doesn’t make sense. Arguably, it does make readability worse. Still, I needed to play around, and this was the result.
In case you are curious, you can find more details in the commits here.
Heavy refactor functions using chatGPT
1 file changed, 39 insertions(+), 52 deletions(-)
libraries/timu-func.el | 91 ++++++++++++++++++++++----------------------------
modified libraries/timu-func.el
@@ -1000,32 +1000,34 @@ Triggered by a custom macOS Quick Action with keybinding."
;;; ORG REFILING SUBTREE TO A STANDALONE FILE
(defun timu-func-org-get-subtree-tags (&optional props)
"Given PROPS, from a call to `org-entry-properties', return a list of tags."
- (unless props
- (setq props (org-entry-properties)))
- (let ((tag-label (if timu-func-org-get-subtree-tags-inherited "ALLTAGS" "TAGS")))
- (-some->> props
- (assoc tag-label)
- cdr
- substring-no-properties
- (s-split ":")
- (--filter (not (cl-equalp "" it))))))
+ (let ((tags-inherited t))
+ (unless props
+ (setq props (org-entry-properties)))
+ (let ((tag-label (if tags-inherited "ALLTAGS" "TAGS")))
+ (-some->> props
+ (assoc tag-label)
+ cdr
+ substring-no-properties
+ (s-split ":")
+ (--filter (not (cl-equalp "" it)))))))
(defun timu-func-org-get-subtree-properties (attributes)
"Return a list of tuples of a subtrees ATTRIBUTES where the keys are strings."
-
- (defun timu-func-symbol-upcase-p (sym)
- (let ((case-fold-search nil))
- (string-match-p "^:[A-Z]+$" (symbol-name sym))))
-
- (defun timu-func-convert-tuple (tup)
- (let ((key (cl-first tup))
- (val (cl-second tup)))
- (list (substring (symbol-name key) 1) val)))
-
- (->> attributes
- (-partition 2) ; Convert plist to list of tuples
- (--filter (timu-func-symbol-upcase-p (cl-first it))) ; Remove lowercase tuples
- (-map 'timu-func-convert-tuple)))
+ (let ((timu-func-symbol-upcase-p
+ (lambda (sym)
+ (let ((case-fold-search nil))
+ (string-match-p "^:[A-Z]+$" (symbol-name sym)))))
+ (timu-func-convert-tuple
+ (lambda (tup)
+ (let ((key (cl-first tup))
+ (val (cl-second tup)))
+ (list (substring (symbol-name key) 1) val)))))
+ (->> attributes
+ ;; Convert plist to list of tuples
+ (-partition 2)
+ ;; Remove lowercase tuples
+ (--filter (funcall timu-func-symbol-upcase-p (cl-first it)))
+ (-map timu-func-convert-tuple))))
(defun timu-func-org-get-subtree-content (attributes)
"Return the contents and ATTRIBUTES of the current subtree as a string."
@@ -1060,11 +1062,6 @@ region list of the start and end of the subtree."
:properties (timu-func-org-get-subtree-properties attrs)
:body (timu-func-org-get-subtree-content attrs)))))
-(defvar timu-func-org-get-subtree-tags-inherited t
- "Returns a subtree's tags, and all tags inherited.
-This is from tags specified in parents headlines or on the file itself.
-Defaults to true.")
-
(defun timu-func-org-set-file-property (key value &optional spot)
"Make sure file has a top-level, file-wide property.
KEY is something like \"TITLE\" or \"FILETAGS\".
@@ -1134,34 +1131,24 @@ It attempts to move as many of the properties and features to the new file."
(apply #'delete-region area)
(timu-func-org-create-org-file filepath head body tags properties)))
-(defvar timu-func-org-refile-directly-show-after nil
- "Show the destination afterwards when using `timu-func-org-refile-directly'.
-This is if this is set to t, otherwise, just do all in the background.")
-
-(defun timu-func-org-subtree-region ()
- "Return a list of the start and end of a subtree."
- (save-excursion
- (list (progn (org-back-to-heading) (point))
- (progn (org-end-of-subtree) (point)))))
-
-(defun timu-func-org-refile-directly (file-dest)
+(defun timu-func-org-refile-directly (file-dest &optional show-after)
"Move the current subtree to the end of FILE-DEST.
If SHOW-AFTER is non-nil, show the destination window,
otherwise, this destination buffer is not shown."
(interactive "fDestination: ")
-
- (defun timu-func-dump-it (file contents)
- (find-file-other-window file-dest)
- (goto-char (point-max))
- (insert "\n" contents))
-
- (save-excursion
- (let* ((region (timu-func-org-subtree-region))
- (contents (buffer-substring (cl-first region) (cl-second region))))
- (apply #'kill-region region)
- (if timu-func-org-refile-directly-show-after
- (save-current-buffer (timu-func-dump-it file-dest contents))
- (save-window-excursion (timu-func-dump-it file-dest contents))))))
+ (let ((dump-it (lambda (file contents)
+ (find-file-other-window file-dest)
+ (goto-char (point-max))
+ (insert "\n" contents))))
+ (save-excursion
+ (let* ((region (save-excursion
+ (list (progn (org-back-to-heading) (point))
+ (progn (org-end-of-subtree) (point)))))
+ (contents (buffer-substring (cl-first region) (cl-second region))))
+ (apply #'kill-region region)
+ (if show-after
+ (save-current-buffer (funcall dump-it file-dest contents))
+ (save-window-excursion (funcall dump-it file-dest contents)))))))
;;; OX-HUGO
Conclusion
Let’s close by some words by ChatGPT:
As a language model, my main purpose is to assist users in generating human-like text based on the input provided to me. This can be useful in a variety of applications, such as generating content for websites, assisting in the creation of documents or reports, and even providing personalized responses to messages or customer inquiries. I can also be useful in language translation tasks, summarizing long articles or documents, and providing information on a wide range of topics. Ultimately, my usefulness will depend on how I am trained and used by the developers and users who employ my services.
P.S.
About 95% of the prose here was reworded by ChatGPT.