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

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


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  

    Suggestion 1. It would be useful if TextMate had variables like  
    $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  

Best regards,
Sergei Yakovlev

More information about the textmate mailing list