Skip to content

Commit 61425cb

Browse files
committed
Move most logic to separate file
1 parent 0a4ef0f commit 61425cb

4 files changed

Lines changed: 167 additions & 161 deletions

File tree

src/server/selectionRange.ts

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
/* @internal */
2+
namespace ts.server {
3+
const isImport = or(isImportDeclaration, isImportEqualsDeclaration);
4+
5+
export function getSelectionRange(pos: number, sourceFile: SourceFile, pushSelectionRange: (start: number, end: number, kind?: SyntaxKind) => void) {
6+
pushSelectionRange(sourceFile.getFullStart(), sourceFile.getEnd(), SyntaxKind.SourceFile);
7+
8+
// Skip top-level SyntaxList
9+
let parentNode = sourceFile.getChildAt(0);
10+
outer: while (true) {
11+
const children = parentNode.getChildren(sourceFile);
12+
if (!children.length) break;
13+
for (let i = 0; i < children.length; i++) {
14+
const prevNode: Node | undefined = children[i - 1];
15+
const node: Node = children[i];
16+
const nextNode: Node | undefined = children[i + 1];
17+
if (node.getStart(sourceFile) > pos) {
18+
break outer;
19+
}
20+
21+
if (positionBelongsToNode(node, pos, sourceFile)) {
22+
// Blocks are effectively redundant with SyntaxLists.
23+
// TemplateSpans, along with the SyntaxLists containing them,
24+
// are a somewhat unintuitive grouping of things that should be
25+
// considered independently. Dive in without pushing a selection range.
26+
if (isBlock(node) || isTemplateSpan(node) || isTemplateHead(node) || prevNode && isTemplateHead(prevNode)) {
27+
parentNode = node;
28+
break;
29+
}
30+
31+
// Synthesize a stop for '${ ... }' since '${' and '}' actually belong to siblings.
32+
if (isTemplateSpan(parentNode) && nextNode && isTemplateMiddleOrTemplateTail(nextNode)) {
33+
const start = node.getFullStart() - "${".length;
34+
const end = nextNode.getStart() + "}".length;
35+
pushSelectionRange(start, end, node.kind);
36+
}
37+
// Synthesize a stop for group of adjacent imports
38+
else if (isImport(node)) {
39+
const [firstImportIndex, lastImportIndex] = getGroupBounds(children, i, isImport);
40+
pushSelectionRange(
41+
children[firstImportIndex].getStart(),
42+
children[lastImportIndex].getEnd());
43+
}
44+
45+
// Blocks with braces on separate lines should be selected from brace to brace,
46+
// including whitespace but not including the braces themselves.
47+
const isBetweenMultiLineBraces = isSyntaxList(node)
48+
&& prevNode && prevNode.kind === SyntaxKind.OpenBraceToken
49+
&& nextNode && nextNode.kind === SyntaxKind.CloseBraceToken
50+
&& !positionsAreOnSameLine(prevNode.getStart(), nextNode.getStart(), sourceFile);
51+
const start = isBetweenMultiLineBraces ? prevNode.getEnd() : node.getStart();
52+
const end = isBetweenMultiLineBraces ? nextNode.getStart() : node.getEnd();
53+
pushSelectionRange(start, end, node.kind);
54+
55+
// Mapped types _look_ like ObjectTypes with a single member,
56+
// but in fact don’t contain a SyntaxList or a node containing
57+
// the “key/value” pair like ObjectTypes do, but it seems intuitive
58+
// that the selection would snap to those points. The philosophy
59+
// of choosing a selection range is not so much about what the
60+
// syntax currently _is_ as what the syntax might easily become
61+
// if the user is making a selection; e.g., we synthesize a selection
62+
// around the “key/value” pair not because there’s a node there, but
63+
// because it allows the mapped type to become an object type with a
64+
// few keystrokes.
65+
if (isMappedTypeNode(node)) {
66+
const openBraceToken = Debug.assertDefined(node.getFirstToken());
67+
const firstNonBraceToken = Debug.assertDefined(node.getChildAt(1));
68+
const closeBraceToken = Debug.assertDefined(node.getLastToken());
69+
Debug.assertEqual(openBraceToken.kind, SyntaxKind.OpenBraceToken);
70+
Debug.assertEqual(closeBraceToken.kind, SyntaxKind.CloseBraceToken);
71+
const spanWithoutBraces = [openBraceToken.getEnd(), closeBraceToken.getStart()] as const;
72+
const spanWithoutBracesOrTrivia = [firstNonBraceToken.getStart(), closeBraceToken.getFullStart()] as const;
73+
if (!positionsAreOnSameLine(openBraceToken.getStart(), closeBraceToken.getEnd(), sourceFile)) {
74+
pushSelectionRange(...spanWithoutBraces);
75+
}
76+
pushSelectionRange(...spanWithoutBracesOrTrivia);
77+
}
78+
79+
// String literals should have a stop both inside and outside their quotes.
80+
else if (isStringLiteral(node) || isTemplateLiteral(node)) {
81+
pushSelectionRange(start + 1, end - 1);
82+
}
83+
84+
parentNode = node;
85+
break;
86+
}
87+
}
88+
}
89+
}
90+
91+
function getGroupBounds<T>(array: ArrayLike<T>, index: number, predicate: (element: T) => boolean): [number, number] {
92+
let first = index;
93+
let last = index;
94+
let i = index;
95+
while (i > 0) {
96+
const element = array[--i];
97+
if (predicate(element)) {
98+
first = i;
99+
}
100+
else {
101+
break;
102+
}
103+
}
104+
i = index;
105+
while (i < array.length - 1) {
106+
const element = array[++i];
107+
if (predicate(element)) {
108+
last = i;
109+
}
110+
else {
111+
break;
112+
}
113+
}
114+
return [first, last];
115+
}
116+
}

src/server/session.ts

Lines changed: 6 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -112,32 +112,6 @@ namespace ts.server {
112112
return edits.every(edit => textSpanEnd(edit.span) < pos);
113113
}
114114

115-
function getGroupBounds<T>(array: ArrayLike<T>, index: number, predicate: (element: T) => boolean): [number, number] {
116-
let first = index;
117-
let last = index;
118-
let i = index;
119-
while (i > 0) {
120-
const element = array[--i];
121-
if (predicate(element)) {
122-
first = i;
123-
}
124-
else {
125-
break;
126-
}
127-
}
128-
i = index;
129-
while (i < array.length - 1) {
130-
const element = array[++i];
131-
if (predicate(element)) {
132-
last = i;
133-
}
134-
else {
135-
break;
136-
}
137-
}
138-
return [first, last];
139-
}
140-
141115
// CommandNames used to be exposed before TS 2.4 as a namespace
142116
// In TS 2.4 we switched to an enum, keep this for backward compatibility
143117
// The var assignment ensures that even though CommandTypes are a const enum
@@ -2097,107 +2071,26 @@ namespace ts.server {
20972071
const { locations } = args;
20982072
const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args);
20992073

2100-
const isImport = or(isImportDeclaration, isImportEqualsDeclaration);
2074+
21012075
const sourceFile = languageService.getNonBoundSourceFile(file);
21022076
const scriptInfo = Debug.assertDefined(this.projectService.getScriptInfo(file));
2103-
const fullTextSpan = this.toLocationTextSpan(
2104-
createTextSpanFromBounds(sourceFile.getFullStart(), sourceFile.getEnd()),
2105-
scriptInfo);
21062077

21072078
return map(locations, location => {
21082079
const pos = this.getPosition(location, scriptInfo);
2109-
let selectionRange: protocol.SelectionRange = { textSpan: fullTextSpan };
2080+
let selectionRange: protocol.SelectionRange | undefined;
21102081
const pushSelectionRange = (start: number, end: number, syntaxKind?: SyntaxKind): void => {
21112082
// Skip ranges that are identical to the parent
21122083
const textSpan = this.toLocationTextSpan(createTextSpanFromBounds(start, end), scriptInfo);
2113-
if (!this.locationTextSpansAreEqual(textSpan, selectionRange.textSpan)) {
2114-
selectionRange = { textSpan, parent: selectionRange };
2084+
if (!selectionRange || !this.locationTextSpansAreEqual(textSpan, selectionRange.textSpan)) {
2085+
selectionRange = { textSpan, ...selectionRange && { parent: selectionRange } };
21152086
if (syntaxKind) {
21162087
Object.defineProperty(selectionRange, "__debugKind", { value: formatSyntaxKind(syntaxKind) });
21172088
}
21182089
}
21192090
};
21202091

2121-
// Skip top-level SyntaxList
2122-
let parentNode = sourceFile.getChildAt(0);
2123-
outer: while (true) {
2124-
const children = parentNode.getChildren(sourceFile);
2125-
if (!children.length) break;
2126-
for (let i = 0; i < children.length; i++) {
2127-
const prevNode: Node | undefined = children[i - 1];
2128-
const node: Node = children[i];
2129-
const nextNode: Node | undefined = children[i + 1];
2130-
if (node.getStart(sourceFile) > pos) {
2131-
break outer;
2132-
}
2133-
2134-
if (positionBelongsToNode(node, pos, sourceFile)) {
2135-
// Blocks are effectively redundant with SyntaxLists.
2136-
// TemplateSpans, along with the SyntaxLists containing them,
2137-
// are a somewhat unintuitive grouping of things that should be
2138-
// considered independently. Dive in without pushing a selection range.
2139-
if (isBlock(node) || isTemplateSpan(node) || isTemplateHead(node) || prevNode && isTemplateHead(prevNode)) {
2140-
parentNode = node;
2141-
break;
2142-
}
2143-
2144-
// Synthesize a stop for '${ ... }' since '${' and '}' actually belong to siblings.
2145-
if (isTemplateSpan(parentNode) && nextNode && isTemplateMiddleOrTemplateTail(nextNode)) {
2146-
const start = node.getFullStart() - "${".length;
2147-
const end = nextNode.getStart() + "}".length;
2148-
pushSelectionRange(start, end, node.kind);
2149-
}
2150-
// Synthesize a stop for group of adjacent imports
2151-
else if (isImport(node)) {
2152-
const [firstImportIndex, lastImportIndex] = getGroupBounds(children, i, isImport);
2153-
pushSelectionRange(
2154-
children[firstImportIndex].getStart(),
2155-
children[lastImportIndex].getEnd());
2156-
}
2157-
2158-
// Blocks with braces on separate lines should be selected from brace to brace,
2159-
// including whitespace but not including the braces themselves.
2160-
const isBetweenMultiLineBraces = isSyntaxList(node)
2161-
&& prevNode && prevNode.kind === SyntaxKind.OpenBraceToken
2162-
&& nextNode && nextNode.kind === SyntaxKind.CloseBraceToken
2163-
&& !positionsAreOnSameLine(prevNode.getStart(), nextNode.getStart(), sourceFile);
2164-
const start = isBetweenMultiLineBraces ? prevNode.getEnd() : node.getStart();
2165-
const end = isBetweenMultiLineBraces ? nextNode.getStart() : node.getEnd();
2166-
pushSelectionRange(start, end, node.kind);
2167-
2168-
// Mapped types _look_ like ObjectTypes with a single member,
2169-
// but in fact don’t contain a SyntaxList or a node containing
2170-
// the “key/value” pair like ObjectTypes do, but it seems intuitive
2171-
// that the selection would snap to those points. The philosophy
2172-
// of choosing a selection range is not so much about what the
2173-
// syntax currently _is_ as what the syntax might easily become
2174-
// if the user is making a selection; e.g., we synthesize a selection
2175-
// around the “key/value” pair not because there’s a node there, but
2176-
// because it allows the mapped type to become an object type with a
2177-
// few keystrokes.
2178-
if (isMappedTypeNode(node)) {
2179-
const openBraceToken = Debug.assertDefined(node.getFirstToken());
2180-
const firstNonBraceToken = Debug.assertDefined(node.getChildAt(1));
2181-
const closeBraceToken = Debug.assertDefined(node.getLastToken());
2182-
Debug.assertEqual(openBraceToken.kind, SyntaxKind.OpenBraceToken);
2183-
Debug.assertEqual(closeBraceToken.kind, SyntaxKind.CloseBraceToken);
2184-
const spanWithoutBraces = [openBraceToken.getEnd(), closeBraceToken.getStart()] as const;
2185-
const spanWithoutBracesOrTrivia = [firstNonBraceToken.getStart(), closeBraceToken.getFullStart()] as const;
2186-
pushSelectionRange(...spanWithoutBraces);
2187-
pushSelectionRange(...spanWithoutBracesOrTrivia);
2188-
}
2189-
2190-
// String literals should have a stop both inside and outside their quotes.
2191-
else if (isStringLiteral(node) || isTemplateLiteral(node)) {
2192-
pushSelectionRange(start + 1, end - 1);
2193-
}
2194-
2195-
parentNode = node;
2196-
break;
2197-
}
2198-
}
2199-
}
2200-
return selectionRange;
2092+
getSelectionRange(pos, sourceFile, pushSelectionRange);
2093+
return selectionRange!;
22012094
});
22022095
}
22032096

src/server/tsconfig.json

Lines changed: 28 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,28 @@
1-
{
2-
"extends": "../tsconfig-base",
3-
"compilerOptions": {
4-
"removeComments": false,
5-
"outFile": "../../built/local/server.js",
6-
"preserveConstEnums": true,
7-
"types": [
8-
"node"
9-
]
10-
},
11-
"references": [
12-
{ "path": "../compiler" },
13-
{ "path": "../jsTyping" },
14-
{ "path": "../services" }
15-
],
16-
"files": [
17-
"types.ts",
18-
"utilities.ts",
19-
"protocol.ts",
20-
"scriptInfo.ts",
21-
"typingsCache.ts",
22-
"project.ts",
23-
"editorServices.ts",
24-
"session.ts",
25-
"scriptVersionCache.ts"
26-
]
27-
}
1+
{
2+
"extends": "../tsconfig-base",
3+
"compilerOptions": {
4+
"removeComments": false,
5+
"outFile": "../../built/local/server.js",
6+
"preserveConstEnums": true,
7+
"types": [
8+
"node"
9+
]
10+
},
11+
"references": [
12+
{ "path": "../compiler" },
13+
{ "path": "../jsTyping" },
14+
{ "path": "../services" }
15+
],
16+
"files": [
17+
"types.ts",
18+
"utilities.ts",
19+
"protocol.ts",
20+
"scriptInfo.ts",
21+
"typingsCache.ts",
22+
"project.ts",
23+
"editorServices.ts",
24+
"selectionRange.ts",
25+
"session.ts",
26+
"scriptVersionCache.ts"
27+
]
28+
}

0 commit comments

Comments
 (0)