From 70e2672ab3b765672de00502ae8f6347669423ea Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Fri, 12 Apr 2019 10:53:20 -0700 Subject: [PATCH] Add rules for expanding selection to sibling nodes --- src/services/selectionRange.ts | 51 +++++++++++--- .../unittests/tsserver/selectionRange.ts | 67 ++++++++++--------- 2 files changed, 74 insertions(+), 44 deletions(-) diff --git a/src/services/selectionRange.ts b/src/services/selectionRange.ts index 7abff8cda88..3a3c0cba086 100644 --- a/src/services/selectionRange.ts +++ b/src/services/selectionRange.ts @@ -13,9 +13,9 @@ namespace ts.SelectionRange { const children = parentNode.getChildren(sourceFile); if (!children.length) break; for (let i = 0; i < children.length; i++) { - const prevNode: Node | undefined = children[i - 1]; + let prevNode: Node | undefined = children[i - 1]; const node: Node = children[i]; - const nextNode: Node | undefined = children[i + 1]; + let nextNode: Node | undefined = children[i + 1]; if (node.getStart(sourceFile) > pos) { break outer; } @@ -30,6 +30,26 @@ namespace ts.SelectionRange { break; } + const siblingExpansionRule = getSiblingExpansionRule(parentNode); + let expansionCandidate: SyntaxKind | [SyntaxKind, SyntaxKind] | undefined; + while ((prevNode || nextNode) && (expansionCandidate = siblingExpansionRule.shift())) { + if (isArray(expansionCandidate) + && prevNode && prevNode.kind === expansionCandidate[0] + && nextNode && nextNode.kind === expansionCandidate[1]) { + pushSelectionRange(prevNode.getStart(), nextNode.getEnd()); + prevNode = children[children.indexOf(prevNode) - 1]; + nextNode = children[children.indexOf(nextNode) + 1]; + } + else if (prevNode && prevNode.kind === expansionCandidate) { + pushSelectionRange(prevNode.getStart(), node.getEnd()); + prevNode = children[children.indexOf(prevNode) - 1]; + } + else if (nextNode && nextNode.kind === expansionCandidate) { + pushSelectionRange(node.getStart(), nextNode.getEnd()); + nextNode = children[children.indexOf(nextNode) + 1]; + } + } + // Synthesize a stop for '${ ... }' since '${' and '}' actually belong to siblings. if (isTemplateSpan(parentNode) && nextNode && isTemplateMiddleOrTemplateTail(nextNode)) { const start = node.getFullStart() - "${".length; @@ -103,15 +123,24 @@ namespace ts.SelectionRange { } } - // function getSiblingExpansionRule(parentNode: T): (SyntaxKind | SyntaxKind[])[] | undefined { - // switch (parentNode.kind) { - // case SyntaxKind.BindingElement: return [SyntaxKind.Identifier, SyntaxKind.DotDotDotToken]; - // case SyntaxKind.Parameter: return [SyntaxKind.Identifier, SyntaxKind.DotDotDotToken, SyntaxKind.QuestionToken]; - // case SyntaxKind.PropertySignature: return [SyntaxKind.Identifier, SyntaxKind.QuestionToken, SyntaxKind.SyntaxList]; - // case SyntaxKind.ElementAccessExpression: return [[SyntaxKind.OpenBracketToken, SyntaxKind.CloseBracketToken]]; - // case SyntaxKind.IndexedAccessType: return [[SyntaxKind.OpenBracketToken, SyntaxKind.CloseBracketToken]]; - // } - // } + function getSiblingExpansionRule(parentNode: T): (SyntaxKind | [SyntaxKind, SyntaxKind])[] { + switch (parentNode.kind) { + case SyntaxKind.BindingElement: return [SyntaxKind.Identifier, SyntaxKind.DotDotDotToken]; + case SyntaxKind.Parameter: return [SyntaxKind.Identifier, SyntaxKind.DotDotDotToken, SyntaxKind.QuestionToken]; + case SyntaxKind.PropertySignature: return [SyntaxKind.Identifier, SyntaxKind.QuestionToken, SyntaxKind.SyntaxList]; + case SyntaxKind.ElementAccessExpression: return [[SyntaxKind.OpenBracketToken, SyntaxKind.CloseBracketToken]]; + case SyntaxKind.MappedType: return [ + SyntaxKind.TypeParameter, + [SyntaxKind.OpenBracketToken, SyntaxKind.CloseBracketToken], + SyntaxKind.MinusToken, + SyntaxKind.PlusToken, + SyntaxKind.ReadonlyKeyword, + SyntaxKind.MinusToken, + SyntaxKind.PlusToken, + ]; + default: return []; + } + } function getGroupBounds(array: ArrayLike, index: number, predicate: (element: T) => boolean): [number, number] { let first = index; diff --git a/src/testRunner/unittests/tsserver/selectionRange.ts b/src/testRunner/unittests/tsserver/selectionRange.ts index c2261e041e0..88bc0a6ad10 100644 --- a/src/testRunner/unittests/tsserver/selectionRange.ts +++ b/src/testRunner/unittests/tsserver/selectionRange.ts @@ -231,44 +231,45 @@ type X = { end: { line: 4, offset: 33 } }, parent: allMembersUp } }; - assert.deepEqual(locations, [ - { - textSpan: { // foo + assert.deepEqual(locations![0], { + textSpan: { // foo + start: { line: 3, offset: 5 }, + end: { line: 3, offset: 8 } }, + parent: { + textSpan: { // foo? start: { line: 3, offset: 5 }, - end: { line: 3, offset: 8 } }, + end: { line: 3, offset: 9 } }, parent: { - textSpan: { // foo? + textSpan: { // foo?: string; start: { line: 3, offset: 5 }, - end: { line: 3, offset: 9 } }, - parent: { - textSpan: { // foo?: string; - start: { line: 3, offset: 5 }, - end: { line: 3, offset: 18 } }, - parent: allMembersUp } } }, - { - textSpan: { // readonly - start: { line: 4, offset: 5 }, - end: { line: 4, offset: 13 } }, - parent: readonlyBarUp }, - { - textSpan: { // bar - start: { line: 4, offset: 14 }, - end: { line: 4, offset: 17 } }, - parent: readonlyBarUp }, - { - textSpan: { // number - start: { line: 4, offset: 24 }, + end: { line: 3, offset: 18 } }, + parent: allMembersUp } } }); + + assert.deepEqual(locations![1], { + textSpan: { // readonly + start: { line: 4, offset: 5 }, + end: { line: 4, offset: 13 } }, + parent: readonlyBarUp }); + + assert.deepEqual(locations![2], { + textSpan: { // bar + start: { line: 4, offset: 14 }, + end: { line: 4, offset: 17 } }, + parent: readonlyBarUp }); + + assert.deepEqual(locations![3], { + textSpan: { // number + start: { line: 4, offset: 24 }, + end: { line: 4, offset: 30 } }, + parent: { + textSpan: { // x: number + start: { line: 4, offset: 21 }, end: { line: 4, offset: 30 } }, parent: { - textSpan: { // x: number - start: { line: 4, offset: 21 }, - end: { line: 4, offset: 30 } }, - parent: { - textSpan: { // { x: number } - start: { line: 4, offset: 19 }, - end: { line: 4, offset: 32 } }, - parent: readonlyBarUp } } }, - ]); + textSpan: { // { x: number } + start: { line: 4, offset: 19 }, + end: { line: 4, offset: 32 } }, + parent: readonlyBarUp.parent } } }); }); it("works for string literals and template strings", () => {