Emacs Lisp: Relation of Curor Position to Begin/End of Matching Pairs in Source Code

By Xah Lee. Date: . Last updated: .

When writing commands that work with html tags, there is a interesting issue: should the cursor be associated to the tag to the left of cursor <…>▮ , or on the right of cursor ▮<…> , or the tag the cursor is in <…▮…> ?

when you have

<ul> <li>▮<a href="some">this</a></li> <li>that</li> </ul>

and suppose you call a command to move to the matching tag, which tag to consider, the a or li?

and, what if cursor is not adjacent to a tag? or not inside a tag?

Here's detail.

the current problem is this: there are lots core critical commands/function to check if cursor is inside tag, or jump to begin/end tag, etc. see

There is the issue of relation of cursor position and identification of which is the tag it is associated with. E.g. If cursor is inside a tag <…▮…> then it uniquely identifies the tag. But if cursor is not inside a tag, what u gonna do? Typically, for interactive commands, it's preferable not stop dead and say: sorry, ambiguity error: your cursor must be inside a tag. Instead, you want some kinda smart, like move to parent tag, or the previous/next tag, or nearest tag, and start there. (exactly which, is another issue to be thought about. So all commands behave in consistent way.).

But, if the function is to be called by other lisp code, then, it becomes critical, to have a well defined semantic, about which tag the cursor position is associated with. If this is sloppy and fuzzy (like unix philosophy of grok), then, tons of bugs will creep in later.

Also, another problem. If we define the relation of cursor position to the tag, by defining it to be inside tag. (and undefined if it is outside tag), there's an issue. That is, we have a map of many cursor positions to 1 tag. More desirable is one-on-one map. Namely, we can uniquely define the relation of cursor position and tag, by always require the cursor is to the left of left angle bracket, e.g. ▮<name…> or ▮</name> . This way, it's easier to work with. One html element is associated with exactly 2 cursor positions. (if it is self closing tag, just 1).

but this clear and easy to work with spec, is incompatible, with the more intuitive use of command of having cursor inside a tag. so, to solve this problem means, for commands, u want them to: ① associate cursor position to tag by having the cursor inside a tag. ② if cursor is not in a tag, move it to so, instead of cold error out. But, while the function definitions, they need to have a clear semantic of associating cursor position and tag by a cannonical way: cursor must be on the left of < to identify the tag on the right.

so this means, lots of careful writing of elisp command, the interaction between the arguments, the (interactive) clause when the cmd is called by user, and precise semantic in the body of function using the canonical way of id tag by cursor position.

by the way, the same problem creeps up in lisp. e.g. when you have (▮(())) , which sexp the cursor is associated with, when you have a command jump to the matching parenthesis? i think i have run into inconsistencies in elisp, but not 100% certain. e.g. sometimes, in source code, you have ()▮(). and i think there emacs behavior is inconsistent with most other commands. so i started to add spaces around in source code in general to avoid this problem.

Decided, that the user interface stick with the simple idea that cursor identify the tag to its right, like this: ▮<…>, always. So, all commands that navigate cursor around tags, will end up with cursor in that position. When cursor is not in such a position, the commands will do so first by moving to the left (instead of error out). (note, this is contrary to emacs convention, where left moving commands will have the cursor ends up on the left, while right moving commands have cursor end up on the right of the thing. I think there are pros and cons in either user interface design. The usefulness of either design choice, and the ease of implementation, is about the same.)

There is a problem with relating cursor position to beginning of tag. That is, if you have selection and move cursor, and when you move to closing tag, it won't nicely select the element. You need to place cursor at end of closing tag.