Text object for functions

I've been wanting the ability to have a text object select a function.
Sure, I've had the ability to grab an s-expression, but a function, in
other inferior languages, isn't bound by such syntax.
This commit is contained in:
Howard Abrams 2023-04-19 08:47:47 -07:00
parent b7a35fe1dc
commit 0f87c4ddb5
4 changed files with 55 additions and 82 deletions

View file

@ -33,7 +33,7 @@ Customize the variable, `beep-alert-sound-file' to adjust the sound."
"Call a program to speak the string, PHRASE.
Customize the variable, `beep-speech-executable'."
(let ((command (format beep-speech-executable phrase)))
(shell-command command)))
(async-shell-command command)))
(defun beep--when-finished (phrase &optional to-speak)
"Notify us with string, PHRASE, to grab our attention.

View file

@ -430,6 +430,7 @@ I took the following clever idea and code from [[http://blog.binchen.org/posts/c
(setq found-range range)))))
found-range))
#+end_src
Extend the text object to call this function for both /inner/ and /outer/:
#+begin_src emacs-lisp
(evil-define-text-object ha-evil-a-paren (count &optional beg end type)
@ -442,12 +443,54 @@ Extend the text object to call this function for both /inner/ and /outer/:
:extend-selection nil
(ha-evil-paren-range count beg end type nil))
#+end_src
And the keybindings:
#+begin_src emacs-lisp
(define-key evil-inner-text-objects-map "x" #'ha-evil-inner-paren)
(define-key evil-outer-text-objects-map "x" #'ha-evil-a-paren)
#+end_src
*** Text Object for Functions
While Emacs has the ability to recognize functions, the Evil text object does not. But text objects have both an /inner/ and /outer/ form, and what does that mean for a function? The /inner/ will be the /function itself/ and the /outer/ (like words) would be the surrounding /non-function/ stuff … in other words, the distance between the next functions.
#+begin_src emacs-lisp
(defun ha-evil-defun-range (count beg end type inclusive)
"Get minimum range of `defun` as a text object.
COUNT, is the number of _following_ defuns to count. BEG, END,
TYPE are not used. If INCLUSIVE is t, the text object is
inclusive acquiring the areas between the surrounding defuns."
(let ((start (save-excursion
(beginning-of-defun)
(when inclusive
(beginning-of-defun)
(end-of-defun))
(point)))
(end (save-excursion
(end-of-defun count)
(when inclusive
(end-of-defun)
(beginning-of-defun))
(point))))
(list start end)))
#+end_src
Extend the text object to call this function for both /inner/ and /outer/:
#+begin_src emacs-lisp
(evil-define-text-object ha-evil-a-defun (count &optional beg end type)
"Select a defun and surrounding non-defun content."
:extend-selection t
(ha-evil-defun-range count beg end type t))
(evil-define-text-object ha-evil-inner-defun (count &optional beg end type)
"Select 'inner' (actual) defun."
:extend-selection nil
(ha-evil-defun-range count beg end type nil))
#+end_src
And the keybindings:
#+begin_src emacs-lisp
(define-key evil-inner-text-objects-map "d" #'ha-evil-inner-defun)
(define-key evil-outer-text-objects-map "d" #'ha-evil-a-defun)
#+end_src
Why not use ~f~? Im reserving the ~f~ for a tree-sitter version that is not always available for all modes… yet.
*** Key Chord
Using the key-chord project allows me to make Escape be on two key combo presses on both sides of my keyboard:
#+begin_src emacs-lisp

View file

@ -616,11 +616,16 @@ Splitting out HTML snippets is often a way that I can transfer org-formatted con
"@import url('https://fonts.googleapis.com/css2?family=Overpass:ital,wght@0,300;0,600;1,300;1,600&display=swap');"
"body { font-family: 'Literata', sans-serif; color: #333; }"
"h1,h2,h3,h4,h5 { font-family: 'Overpass', sans-serif; color: #333; }"
"pre.src { background-color: #eee; }"
"code { color: steelblue }"
"pre { background-color: #eee; border-color: #aaa; }"
"a { text-decoration-style: dotted }"
"@media (prefers-color-scheme: dark) {"
" body { background-color: #1d1f21; color: white; }"
" h1,h2,h3,h4,h5 { color: #fcca1b; }"
" pre.src { background-color: black; }"
" code { color: lightsteelblue; }"
" pre { background-color: black; border-color: #777; }"
" a:link { color: lightblue }"
" a:visited { color: violet }"
"}"
"</style>")
hard-newline))

View file

@ -298,10 +298,10 @@ Now, I can create an /interface/ of keystrokes to jump around like a boss:
(when (string-search "TREE_SITTER" system-configuration-features)
(use-package combobulate
:general
(:states 'visual :keymaps 'combobulate-key-map
"o" '("mark node" . combobulate-mark-node-dwim)) ; Mark symbol since "o" doesn't do anything
(:states 'normal :keymaps 'combobulate-key-map
"v" 'combobulate-mark-node-dwim
"g J" '("avy jump" . combobulate-avy)
"[ [" '("prev node" . combobulate-navigate-logical-previous)
"] ]" '("next node" . combobulate-navigate-logical-next)
"[ f" '("prev defun" . combobulate-navigate-beginning-of-defun)
@ -979,81 +979,6 @@ I think the [[https://fishshell.com/][fish shell]] is an interesting experiment
:hook
(fish-mode . (lambda () (add-hook 'before-save-hook 'fish_indent-before-save))))
#+end_src
* Aux
** AI Robot Helpers
Seems like a number of personal projects to interface with the ChatGPT. Lets try a few, and see what I like. All of the project require the OpenAI key that I store in [[file:~/.authinfo.gpg][~/.authinfo.gpg]]:
#+begin_src emacs-lisp
(defvar ha-openai-key nil
"Decoded the OpenAI Key from the authinfo database.")
(defun ha-openai-key ()
"Return the decoded OpenAI Key stored in the authinfo database."
(unless ha-openai-key
(let* ((openai-encfun (--> "openai.com"
(auth-source-search :host it)
(first it)
(plist-get it :secret))))
(setq ha-openai-key (funcall openai-encfun))))
ha-openai-key)
#+end_src
The [[https://github.com/xenodium/chatgpt-shell][chatgpt-shell]] project attempts to be an interactive session.
#+begin_src emacs-lisp
(use-package chatgpt-shell
:straight (:host github :repo "xenodium/chatgpt-shell")
:init
:config
(defun chatgpt-start-shell ()
"Get the password and then start the `chatgpt-shell' program."
(interactive)
(setq chatgpt-shell-openai-key (ha-openai-key))
(chatgpt-shell))
(ha-prog-leader
"a" '(:ignore t :which-key "ChatGPT")
"a s" '("shell" . chatgpt-start-shell)))
#+end_src
Lets play with [[https://github.com/CarlQLange/chatgpt-arcana.el][ChatGPT Arcana]] project.
#+begin_src emacs-lisp
(use-package chatgpt-arcana
:straight (:host github :repo "CarlQLange/ChatGPT-Arcana.el" :files ("*.el"))
:config
(defun chatgpt-arcana-start ()
"Get the password and then start the `chatgpt-arcana-start-chat' program."
(interactive)
(setq chatgpt-arcana-api-key (ha-openai-key))
(chatgpt-arcana-start-chat))
(use-package all-the-icons
:config
(add-to-list 'all-the-icons-mode-icon-alist
'(chatgpt-arcana-chat-mode all-the-icons-octicon
"comment-discussion"
:height 1.0
:v-adjust -0.1
:face all-the-icons-purple)))
(use-package pretty-hydra
:config
(eval `(pretty-hydra-define chatgpt-arcana-hydra (:color blue :quit-key "q" :title "ChatGPT Arcana")
("Query"
(("a" chatgpt-arcana-query "Query")
("r" chatgpt-arcana-replace-region "Replace region"))
"Insert"
(("i" chatgpt-arcana-insert-at-point-with-context "At point with context")
("I" chatgpt-arcana-insert-at-point "At point")
("j" chatgpt-arcana-insert-after-region "Before region")
("J" chatgpt-arcana-insert-before-region "After region"))
"Chat"
(("c" chatgpt-arcana-start-chat "Start chat"))
"Shortcuts"
(,@(chatgpt-arcana-generate-prompt-shortcuts))))))
(ha-prog-leader
"a" '(:ignore t :which-key "ChatGPT")
"a c" '("Start chat" . chatgpt-arcana-start)
"a a" '("AI commands" . chatgpt-arcana-hydra/body)))
#+end_src
* Technical Artifacts :noexport:
Provide a name to =require= this code.
#+begin_src emacs-lisp :exports none