Refactoring Emacs Lisp with ChatGPT
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?
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).
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)))
(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.
letin a function instead of defining variables separately
lamdasinside 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
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.
About 95% of the prose here was reworded by ChatGPT.