[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
which you can find attached to the bug description at http://macromates.com/ticket/show?ticket_id=58C52785 .
The link get screwed when displayed through the web interface. The correct ticket id is 58C52785. Here's an alternative link: http://tinyurl.com/yt3fol
On 26 Dec 2007, at 14:28, Сергей Яковлев wrote:
[...] is it possible to put this fix into some standard TextMate bundle like Text.tmbundle, so that all TextMate users enjoy it?
Looks good. I plan to do a TM build in a few days, I will commit your macros after that, just to allow for a little time of testing, in the case of unforeseen side effects (not that I think there is any).
[...] What is the best way to do it?
Writing this list or finding someone on IRC with authority is ideal.
[...] 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).
It does in the form of a line/column position of the start position.
The reason it does not use a byte offset is that internally TM would have to “calculate” this (by adding the length of each line preceding the selection) and I am generally wary of exporting shell variables that need to be calculated and have an unknown running time (i.e. in practice the user could have a million lines and each command execution would then always require a million numbers to be added together).
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.
Technically you can store your variables e.g. in /tmp/my_variables.txt and let the command work with that file.
Environment variables are inherited by child processes, but the parent process cannot read modifications done to the environment inherited by the child. So even as a “native” feature, it would not be as graceful as simply interacting with the standard environment and declaring some variables “global” (like you sort of do with “export «variable»” in a shell script).
That said, there could certainly be support surpassing the comfort of / tmp/my_variables.txt, and it is being considered :)
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. [...]
In the future the input/output options will (likely) be broken up into “location” and “format”, which should give more flexibility yet still provide a simple interface.
Suggestion 4. Replace All should not alter current selection if no matches are found.
I could have sworn it didn’t ;)
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)?
We generally use U+FFFC, OBJECT REPLACEMENT CHARACTER.