To be an efficient hands-free coder, you’ll need to learn how to move the cursor around a file quickly. There are two challenges: first, since you can’t use a mouse, you can’t just click to the location to move to. You can try using an eye tracker to accomplish this, but the precision isn’t quite high enough. Second, with a keyboard you can hold a movement key and release when you reach your location, but this doesn’t translate well to voice control, which has too much latency (although you might try measuring the latency and adjusting for it).
Beginner’s note: to get started, check out the multiedit grammar for dragonfly. This gives you commands to start with and a nice framework for repeating commands quickly.
One approach to movement is to search within the file, for example using Emacs incremental search. This works well if you need to jump somewhere offscreen. The trouble with using this for all onscreen movement is that recognition accuracy isn’t always perfect, and that identifier might be repeated several times. This would work better if there were an Emacs extension that numbered incremental search results so it’s easy to jump to a particular one. Let me know in the comments if you make this!
Let’s break the problem down. Every location onscreen is at a particular line and column, so if we can navigate to each of these quickly, we can jump to anywhere quickly.
Let’s start with jumping to a particular line. Any decent editor can show you line numbers and let you jump to a specific line. But try editing a file with several hundred lines, and you’ll find that this is pretty clunky. There are a couple ways to improve on this: you can show line numbers relative to your current position, or you can show the numbers modulo some value. For example, if you never have more than 100 lines on screen at once, you could just show the last two digits of the absolute line number. Personally, I prefer to use relative line numbers since these work well with relative motion commands such as “up ten” or “down five”. Also, the number of syllables scales nicely with the amount of movement. The main advantage to using modulo is that you can chain together successive commands easily (e.g. “select between line X and line Y”). If you use Emacs and go with the relative line numbers approach, check out the package linum-relative.
Next, we have to jump to a particular column. This is a bit trickier because it is awkward to number every column in an editor. I suppose you could write the numbers vertically; please post in the comments if you come up with something to do this. In the meantime, I use a few different approaches, but the key advantage we can exploit is that you generally want to jump to the boundaries of symbols and words. This means that relative motion commands that move by a symbol or word often work well unless the line is long. In Emacs, familiarize yourself with subword mode. I bind commands for moving across an entire symbol and across a single subword. When the line is long, I use a different strategy where I name the character at the beginning or end of a symbol I want to jump to, and use a custom command to jump there. Searching for a character instead of a full word greatly improves recognition accuracy, especially when dealing with unusual words. See the Emacs lisp for this at the bottom of the post. Note that the Emacs extension “Ace jump mode” works similarly to this, but I prefer my approach because I don’t have to wait for an overlay to appear before issuing my command. Voice dictation latency is high enough that it’s almost always an advantage to accomplish everything in a single command.
I also use a few more commands for quick movement to frequently visited places. I use the directions North, South, West, and East to move to the top/bottom of a file and left/right within a line. I use the mark ring in Emacs to jump to previous locations, and registers to save locations and jump to them quickly later. And of course I use page up and down to scroll through file, although I prefer to use foot pedals for this.
Finally, as promised, my code for jumping within a line:
(defun goto-char-if-not-nil (position) (if position (goto-char position))) (defmacro goto-char-or-cancel (&rest body) `(goto-char-if-not-nil (save-excursion ,@body))) (defun word-start-regex (char) (if (string-match "\\s_\\|\\sw" (char-to-string char)) (concat "\\_<" (regexp-quote (char-to-string char))) (regexp-quote (char-to-string char)))) (defun word-end-regex (char) (if (string-match "\\s_\\|\\sw" (char-to-string char)) (concat (regexp-quote (char-to-string char)) "\\_>") (regexp-quote (char-to-string char)))) (defun search-forward-after-char (char) (re-search-forward (word-end-regex char) (line-beginning-position 2) t)) (defun search-backward-before-char (char) (re-search-backward (word-start-regex char) (line-beginning-position) t)) (defun search-backward-after-char (char) (forward-char -1) (when (re-search-backward (word-end-regex char) (line-beginning-position) t) (forward-char 1) (point))) (defun search-forward-before-char (char) (forward-char 1) (when (re-search-forward (word-start-regex char) (line-beginning-position 2) t) (forward-char -1) (point))) (defun forward-after-char (char) (interactive "cForward after char: ") (goto-char-or-cancel (if (search-forward-after-char char) (point) (beginning-of-line) (search-forward-after-char char)))) (defun forward-before-char (char) (interactive "cForward before char: ") (goto-char-or-cancel (if (search-forward-before-char char) (point) (beginning-of-line) (search-forward-before-char char)))) (defun backward-before-char (char) (interactive "cBackward before char: ") (goto-char-or-cancel (if (search-backward-before-char char) (point) (end-of-line) (search-backward-before-char char)))) (defun backward-after-char (char) (interactive "cBackward after char: ") (goto-char-or-cancel (if (search-backward-after-char char) (point) (end-of-line) (search-backward-after-char char))))