From 04b42cf004a820000c284a8ae4283994d65a2185 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Wed, 5 Sep 2018 17:41:09 +0200 Subject: [PATCH] Fixes #53899 --- .../common/controller/cursorWordOperations.ts | 41 +++++++++++-- .../wordOperations/test/wordTestUtils.ts | 4 ++ .../test/wordPartOperations.test.ts | 60 ++++++++++++++++++- 3 files changed, 99 insertions(+), 6 deletions(-) diff --git a/src/vs/editor/common/controller/cursorWordOperations.ts b/src/vs/editor/common/controller/cursorWordOperations.ts index da290068cf9..94370c09bd2 100644 --- a/src/vs/editor/common/controller/cursorWordOperations.ts +++ b/src/vs/editor/common/controller/cursorWordOperations.ts @@ -468,13 +468,31 @@ export class WordOperations { export function _lastWordPartEnd(str: string, startIndex: number = str.length - 1): number { let ignoreUpperCase = !strings.isLowerAsciiLetter(str.charCodeAt(startIndex + 1)); for (let i = startIndex; i >= 0; i--) { - let chCode = str.charCodeAt(i); - if (chCode === CharCode.Space || chCode === CharCode.Tab || (!ignoreUpperCase && strings.isUpperAsciiLetter(chCode)) || chCode === CharCode.Underline) { + const chCode = str.charCodeAt(i); + if (chCode === CharCode.Space || chCode === CharCode.Tab) { + if (i === 0) { + return 0; + } + const prevChCode = str.charCodeAt(i - 1); + if (prevChCode !== CharCode.Space && prevChCode !== CharCode.Tab) { + return i - 1; + } + } + if (!ignoreUpperCase && strings.isUpperAsciiLetter(chCode)) { return i - 1; } if (ignoreUpperCase && i < startIndex && strings.isLowerAsciiLetter(chCode)) { return i; } + if (chCode === CharCode.Underline) { + if (i === 0) { + return 0; + } + const prevChCode = str.charCodeAt(i - 1); + if (prevChCode !== CharCode.Underline) { + return i - 1; + } + } ignoreUpperCase = ignoreUpperCase && strings.isUpperAsciiLetter(chCode); } return -1; @@ -490,7 +508,16 @@ export function _nextWordPartBegin(str: string, startIndex: number = 0): number let ignoreUpperCase = strings.isUpperAsciiLetter(chCode); for (let i = startIndex; i < str.length; ++i) { chCode = str.charCodeAt(i); - if (chCode === CharCode.Space || chCode === CharCode.Tab || (!ignoreUpperCase && strings.isUpperAsciiLetter(chCode))) { + if (chCode === CharCode.Space || chCode === CharCode.Tab) { + if (i + 1 >= str.length) { + return i + 1; + } + const nextChCode = str.charCodeAt(i + 1); + if (nextChCode !== CharCode.Space && nextChCode !== CharCode.Tab) { + return i + 2; + } + } + if (!ignoreUpperCase && strings.isUpperAsciiLetter(chCode)) { return i + 1; } if (ignoreUpperCase && strings.isLowerAsciiLetter(chCode)) { @@ -498,7 +525,13 @@ export function _nextWordPartBegin(str: string, startIndex: number = 0): number } ignoreUpperCase = ignoreUpperCase && strings.isUpperAsciiLetter(chCode); if (chCode === CharCode.Underline) { - return i + 2; + if (i + 1 >= str.length) { + return i + 1; + } + const nextChCode = str.charCodeAt(i + 1); + if (nextChCode !== CharCode.Underline) { + return i + 2; + } } } return str.length + 1; diff --git a/src/vs/editor/contrib/wordOperations/test/wordTestUtils.ts b/src/vs/editor/contrib/wordOperations/test/wordTestUtils.ts index 93b76a33c19..8ab24036b80 100644 --- a/src/vs/editor/contrib/wordOperations/test/wordTestUtils.ts +++ b/src/vs/editor/contrib/wordOperations/test/wordTestUtils.ts @@ -69,6 +69,10 @@ export function testRepeatedActionAndExtractPositions(text: string, initialPosit if (stopCondition(editor)) { break; } + + if (actualStops.length > 1000) { + throw new Error(`Endless loop detected!`); + } } }); return actualStops; diff --git a/src/vs/editor/contrib/wordPartOperations/test/wordPartOperations.test.ts b/src/vs/editor/contrib/wordPartOperations/test/wordPartOperations.test.ts index 5462f8243e6..48b6eb4cf3e 100644 --- a/src/vs/editor/contrib/wordPartOperations/test/wordPartOperations.test.ts +++ b/src/vs/editor/contrib/wordPartOperations/test/wordPartOperations.test.ts @@ -36,7 +36,7 @@ suite('WordPartOperations', () => { test('move word part left basic', () => { const EXPECTED = [ '|start| |line|', - '|this|Is|A|Camel|Case|Var| | |this|_is|_a|_snake|_case|_var| |THIS|_IS|_CAPS|_SNAKE| |this|_IS|Mixed|Use|', + '|this|Is|A|Camel|Case|Var| |this|_is|_a|_snake|_case|_var| |THIS|_IS|_CAPS|_SNAKE| |this|_IS|Mixed|Use|', '|end| |line' ].join('\n'); const [text,] = deserializePipePositions(EXPECTED); @@ -51,10 +51,38 @@ suite('WordPartOperations', () => { assert.deepEqual(actual, EXPECTED); }); + test('issue #53899: move word part left whitespace', () => { + const EXPECTED = '|myvar| |=| |\'|demonstration| |of| |selection| |with| |space\''; + const [text,] = deserializePipePositions(EXPECTED); + const actualStops = testRepeatedActionAndExtractPositions( + text, + new Position(1000, 1000), + ed => moveWordPartLeft(ed), + ed => ed.getPosition(), + ed => ed.getPosition().equals(new Position(1, 1)) + ); + const actual = serializePipePositions(text, actualStops); + assert.deepEqual(actual, EXPECTED); + }); + + test('issue #53899: move word part left underscores', () => { + const EXPECTED = '|myvar| |=| |\'|demonstration|_____of| |selection| |with| |space\''; + const [text,] = deserializePipePositions(EXPECTED); + const actualStops = testRepeatedActionAndExtractPositions( + text, + new Position(1000, 1000), + ed => moveWordPartLeft(ed), + ed => ed.getPosition(), + ed => ed.getPosition().equals(new Position(1, 1)) + ); + const actual = serializePipePositions(text, actualStops); + assert.deepEqual(actual, EXPECTED); + }); + test('move word part right basic', () => { const EXPECTED = [ 'start| |line|', - '|this|Is|A|Camel|Case|Var| | |this_|is_|a_|snake_|case_|var| |THIS_|IS_|CAPS_|SNAKE| |this_|IS|Mixed|Use|', + '|this|Is|A|Camel|Case|Var| |this_|is_|a_|snake_|case_|var| |THIS_|IS_|CAPS_|SNAKE| |this_|IS|Mixed|Use|', '|end| |line|' ].join('\n'); const [text,] = deserializePipePositions(EXPECTED); @@ -69,6 +97,34 @@ suite('WordPartOperations', () => { assert.deepEqual(actual, EXPECTED); }); + test('issue #53899: move word part right whitespace', () => { + const EXPECTED = 'myvar| =| \'demonstration| |of| |selection| |with| |space|\'|'; + const [text,] = deserializePipePositions(EXPECTED); + const actualStops = testRepeatedActionAndExtractPositions( + text, + new Position(1, 1), + ed => moveWordPartRight(ed), + ed => ed.getPosition(), + ed => ed.getPosition().equals(new Position(1, 52)) + ); + const actual = serializePipePositions(text, actualStops); + assert.deepEqual(actual, EXPECTED); + }); + + test('issue #53899: move word part right underscores', () => { + const EXPECTED = 'myvar| =| \'demonstration_____|of| |selection| |with| |space|\'|'; + const [text,] = deserializePipePositions(EXPECTED); + const actualStops = testRepeatedActionAndExtractPositions( + text, + new Position(1, 1), + ed => moveWordPartRight(ed), + ed => ed.getPosition(), + ed => ed.getPosition().equals(new Position(1, 52)) + ); + const actual = serializePipePositions(text, actualStops); + assert.deepEqual(actual, EXPECTED); + }); + test('delete word part left basic', () => { const EXPECTED = '| |/*| |Just| |some| |text| |a|+=| |3| |+|5|-|3| |*/| |this|Is|A|Camel|Case|Var| |this|_is|_a|_snake|_case|_var| |THIS|_IS|_CAPS|_SNAKE| |this|_IS|Mixed|Use'; const [text,] = deserializePipePositions(EXPECTED); -- GitLab