I’m trying to add some missing functionality to a bundle and stumbled upon some odd behavior of the selection string in TM_SELECTION regarding the range…
When I select a block of text and my cursor is somewhere in the middle of a line, the selection string contains exactly the highlighted range. Consider the following selection (see screenshot):
TM_SELECTION is now "5:5-3”, so lines 3-5 are included.
Example 2, column selection:
TM_SELECTION is “5x3”, lines 3-5 included.
But whenever I’m using CMD-L for “Select line”, like here, it gets somewhat problematic:
TM_SELECTION is now "6-3”, the range now includes line 6, but the *visible* part of the selection is still 3-5.
I can work somewhat around this checking if the last character in TM_SELECTED_TEXT is a newline (\n) (because that is where the additional line in the range is coming from - as one can make the same selection in example 3 when the cursor is on the first row of line 6, holding shift and hitting UP 2 times. Standard OSX behavior).
But the more I think about it, the less I’m sure if this is intentional.
Can this be considered a bug or is it intended behavior / a feature?
On 15 Jan 2014, at 20:57, Kai Wood wrote:
TM_SELECTION is now "6-3”, the range now includes line 6, but the *visible* part of the selection is still 3-5 […] Can this be considered a bug or is it intended behavior / a feature?
I believe it is correct behavior.
Line 6-3 is short for 6:1-3:1.
This means the selection goes from line 3, column 1, to line 6, column 1.
As you say yourself, you can check if the newline is included, to test if the last line in the selection is an “empty line”. Had the newline not been included in the selection, TM_SELECTION would be 5:5-3. I.e. starting at line 3, column 1, and ending on line 5, column 5 (including all characters on line 5 except the trailing newline).
On 16.01.2014, at 04:29, Allan Odgaard mailinglist@textmate.org wrote:
On 15 Jan 2014, at 20:57, Kai Wood wrote:
TM_SELECTION is now "6-3”, the range now includes line 6, but the *visible* part of the selection is still 3-5 […] Can this be considered a bug or is it intended behavior / a feature?
I believe it is correct behavior.
Line 6-3 is short for 6:1-3:1.
This means the selection goes from line 3, column 1, to line 6, column 1.
As you say yourself, you can check if the newline is included, to test if the last line in the selection is an “empty line”. Had the newline not been included in the selection, TM_SELECTION would be 5:5-3. I.e. starting at line 3, column 1, and ending on line 5, column 5 (including all characters on line 5 except the trailing newline).
Ok, i finally found the edge case that was driving me nuts :)
Take this column selection, made via Shift+UP and tapping option:
TM_SELECTED_TEXT now holds “\n\n”, TM_SELECTION is "4:3x2:3”.
As soon as I’m moving to the right and back left via the cursors, TM_SELECTED_TEXT changes to nil and the selection string is now "2:3&3:3&4:3”. Even if i press CMD-L to mark the whole lines, TM_SELECTED_TEXT still contains “nil”.
It’s clear that internally the cursors change from some kind of “normal” column selection to multi-cursors, as one can see considering the selection string (initial separator “x”, post movement separator “&"). In my use case, I can still distinguish this state of the cursors by parsing the selection string, but it’s a little more complicated than relying on the newline.
Just wanted to share this if someone trips over something similar, i guess it’s nothing that should (or can) be fixed - just a side effect of the multicursor implementation.
On 16 Jan 2014, at 19:16, Kai Wood wrote:
[…] It’s clear that internally the cursors change from some kind of “normal” column selection to multi-cursors, as one can see considering the selection string […]
The “internal change” does manifest itself in some suble ways, for example extending the selection after “splitting it” will act differently. Though the goal was to treat a single zero-width column selection identical to multiple aligned carets (although they aren’t really the same).
[…] In my use case, I can still distinguish this state of the cursors by parsing the selection string, but it’s a little more complicated than relying on the newline.
Can you tell what info it is you wish to extract? As I believe the value of TM_SELECTION should be enough to tell you exactly what you need (without having to look at TM_SELECTED_TEXT).
On 16.01.2014, at 14:24, Allan Odgaard mailinglist@textmate.org wrote:
Can you tell what info it is you wish to extract? As I believe the value of TM_SELECTION should be enough to tell you exactly what you need (without having to look at TM_SELECTED_TEXT).
I need the first and last line number in a range of lines where either a blinking cursor or a selection appears in the document. Only visible selections count.
On 16 Jan 2014, at 21:42, Kai Wood wrote:
I need the first and last line number in a range of lines where either a blinking cursor or a selection appears in the document. Only visible selections count.
Here is a ruby function that parses the selection string syntax and maps it to a “first, last” line that is rendered selected:
def parse_selection_string(str) str.split('&').map do |range| if range =~ /(\d+)(?::(\d+))?(?:+\d+)?(?:([-x])(\d+)(?::(\d+))?(?:+\d+)?)?/ l1, l2, c1, c2 = $1.to_i, ($4 ? $4.to_i : nil), ($2 || 1).to_i, ($5 || 1).to_i l1, l2, c1, c2 = l2, l1, c2, c1 if l2 && (l1 > l2 || l1 == l2 && c2 > c1)
case $3 when 'x' [ l1, l2 ] when '-' l2 -= 1 if c2 == 1 [ l1, l2 ] else [ l1, l1 ] end else abort "unsupported selection string syntax: ‘#{range}’" end end end
You may want to post-process the result to merge overlapping ranges.
On 16.01.2014, at 16:38, Allan Odgaard mailinglist@textmate.org wrote:
On 16 Jan 2014, at 21:42, Kai Wood wrote:
I need the first and last line number in a range of lines where either a blinking cursor or a selection appears in the document. Only visible selections count.
Here is a ruby function that parses the selection string syntax and maps it to a “first, last” line that is rendered selected:
def parse_selection_string(str) str.split('&').map do |range| if range =~ /(\d+)(?::(\d+))?(?:+\d+)?(?:([-x])(\d+)(?::(\d+))?(?:+\d+)?)?/ l1, l2, c1, c2 = $1.to_i, ($4 ? $4.to_i : nil), ($2 || 1).to_i, ($5 || 1).to_i l1, l2, c1, c2 = l2, l1, c2, c1 if l2 && (l1 > l2 || l1 == l2 && c2 > c1)
case $3 when 'x' [ l1, l2 ] when '-' l2 -= 1 if c2 == 1 [ l1, l2 ] else [ l1, l1 ] end else abort "unsupported selection string syntax: ‘#{range}’" end end
end
You may want to post-process the result to merge overlapping ranges.
I hope this will not cost you a sleepless night, because…
Result: [[18, 22], [4, 3], [11, 11]] (Lowest value: 3, one less on the minimum side)
But a very nice try anyway, thanks!
On 16.01.2014, at 17:21, "Allan Odgaard" mailinglist@textmate.org wrote:
I hope this will not cost you a sleepless night, because… […]
Change “c2 > c1” to “c1 > c2” in the line that swaps begin/end anchors.
You're genius, I'll take yours ;)
Thanks a lot!
On 16.01.2014, at 17:07, Kai Wood lists@kwood.eu wrote:
On 16.01.2014, at 16:38, Allan Odgaard mailinglist@textmate.org wrote:
On 16 Jan 2014, at 21:42, Kai Wood wrote:
I need the first and last line number in a range of lines where either a blinking cursor or a selection appears in the document. Only visible selections count.
Here is a ruby function that parses the selection string syntax and maps it to a “first, last” line that is rendered selected:
def parse_selection_string(str) str.split('&').map do |range| if range =~ /(\d+)(?::(\d+))?(?:+\d+)?(?:([-x])(\d+)(?::(\d+))?(?:+\d+)?)?/ l1, l2, c1, c2 = $1.to_i, ($4 ? $4.to_i : nil), ($2 || 1).to_i, ($5 || 1).to_i l1, l2, c1, c2 = l2, l1, c2, c1 if l2 && (l1 > l2 || l1 == l2 && c2 > c1)
case $3 when 'x' [ l1, l2 ] when '-' l2 -= 1 if c2 == 1 [ l1, l2 ] else [ l1, l1 ] end else abort "unsupported selection string syntax: ‘#{range}’" end end
end
You may want to post-process the result to merge overlapping ranges.
I hope this will not cost you a sleepless night, because…
<Screen Shot 2014-01-16 at 16.58.53.png>
Result: [[18, 22], [4, 3], [11, 11]] (Lowest value: 3, one less on the minimum side)
But a very nice try anyway, thanks!
The solution i came up with, would be this:
def parse_selection_for_visible_lines(selection_string) selected_lines = selection_string.split(/[&x-]/).map { |e| e.split(/[:+]/).first.to_i } selected_range = [selected_lines.min, selected_lines.max]
if (selection_string.split("&").map { |e| (e.match(/.*-(\d*)$/) ? $1 : nil ) } - [nil]).max.to_i == selected_range.last selected_range[1] -= 1 end
selected_range end
It’s ugly and it doesn’t even try to parse the selection string as a whole, like yours. It somehow works, because the only case where there is 1 additional line at the end of the document occurs, if a block of cursors separated by “&” with normal_range separator “-" WITHOUT position “:” at the end is found (Something like "23:3-24”) - but only if this block is at the end of the range. (I’m not even sure I can explain this correctly :) )