[TxMt] A fix for Move Selection Line Up/Down and suggestions for TextMate

Сергей Яковлев sergei.yakovlev at gmail.com
Wed Dec 26 13:28:04 UTC 2007


[REPOSTING BECAUSE OF BAD FORMATTING]


Hi!

A well-known problem with Move Selection Line Up/Down
commands (Control-Command-Up/Down) is that if you use
them without selection (which means "move current
line"), the cursor ends up in the wrong place. This
has been bugging me and my friends for quite some
time, so I wrote a fix. It's two macros overriding
default key equivalents, which you can find attached
to the bug description at
http://macromates.com/ticket/show?ticket_id=58C52785 .
Please tell if this works for you.

Also, is it possible to put this fix into some
standard TextMate bundle like Text.tmbundle, so that
all TextMate users enjoy it? (Until it is fixed in the
new engine of TextMate 2.) What is the best way to do
it?

This is my first experience in TextMate programming,
and several times I've been stuck in various
dead-ends. What follows is a somewhat long description
of how I approached the problem along with some
suggestions on making TextMate easier to program. Most
people would like to skip this.

So, my first (rather silly) attempt was a huge Ruby
program which replicated functionality of Move
Selection Line Up/Down and depended on my assumption
that the cursor is always at the end of selection
(potentially allowing me to calculate all the
necessary offsets in the file). Well, it turned out
that my assumption was simply not true, as the cursor
can be anywhere inside the selection, depending on how
you've selected the text.

    Suggestion 1. It would be useful if TextMate had
    variables like $TM_SELECTION_START and
    $TM_SELECTION_END which would return the
    corresponding offsets in the file (or the cursor
    offset, if nothing is selected).

On a second attempt, I decided that I should instead
go with the built-in commands, simply correcting the
cursor position for the special case when nothing is
selected. So we remember current cursor position, call
Move Selection Line Up/Down, and move the cursor back
to remembered position if nothing is selected. There
are two problems here. First, you can only call
built-in commands from macros, and it is not possible
to remember values through the steps of a macro.
Second, there is no direct way to reposition the
cursor. One indirect way to achieve this is to insert
a snippet with a $0 variable. However, to move the
cursor to arbitrary position in this fashion, we need
an output option like "Replace Document as Snippet",
which is not available. Because of these two problems,
I decided to use TMTOOLS plugin, which has "call
macro" (for calling arbitrary macros and built-in
commands from commands) and "set caretTo" (for setting
the cursor position). I got a working solution, but it
had a delay of 0.5 to 1 s, which was too slow to be
useful.

     Suggestion 2. Add a way to set "global"
     environment variables programmatically (from
     commands). These "global" variables should be
     available to all subsequently called commands (and
     other bundle items) until explicitly unset.

     Suggestion 3. Replace output options "Insert as
     Text" and "Insert as Snippet" with just one
     option, "Insert", and add a checkbox "as Snippet"
     to the right. This would add such useful options
     as "Replace Selected Text as Snippet", "Replace
     Document as Snippet" and "Create New Document as
     Snippet" without making the list of options too
     long.

So, on a third (and final) attempt, I dropped TMTOOLS
and returned to the idea of a macro. Inspired by Duane
Johnson's solution for multiple insertion points, I
decided to use a special symbol (mark) to "remember"
the initial position of the cursor. So we insert the
mark at the cursor (if nothing is selected), call Move
Selection Line Up/Down, and replace the mark with
empty string (if nothing is selected), which moves the
cursor there as a side-effect. The problem here is
that Replace command always replaces current selection
with replace buffer, even if it doesn't match the find
buffer, which ruins the case when something has
already been selected in the beginning. Replace All
also ruins current selection, moving the cursor to the
beginning of the file even if it has not found any
matches. Fortunately, the Find Next/Previous command
will only change current selection if the match has
been found. So that's what we use. If there is a mark
in the text, it will get selected, otherwise, the
selection won't change. Finally, the selection is
replaced with an empty string if what's selected is a
mark. The wrong cursor position after Move Selection
Line Up/Down is always further in text than the right
position (actually, it's on the next line), so we use
Find Previous command to perform the search. Because
of this little detail, the solution is actually pretty
fast.

     Suggestion 4. Replace All should not alter current
     selection if no matches are found.

The only remaining question is what symbol to use as a
mark. I decided to use à (221A, SQUARE ROOT character,
which you can type with Option-V), but I'm not sure
this is the best choice. Maybe it's better to use some
character from Private Use Area (E000--F8FF)?

Best regards,
Sergei Yakovlev


More information about the textmate mailing list