mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-05-15 21:36:50 -05:00
Move most logic to separate file
This commit is contained in:
116
src/server/selectionRange.ts
Normal file
116
src/server/selectionRange.ts
Normal file
@@ -0,0 +1,116 @@
|
||||
/* @internal */
|
||||
namespace ts.server {
|
||||
const isImport = or(isImportDeclaration, isImportEqualsDeclaration);
|
||||
|
||||
export function getSelectionRange(pos: number, sourceFile: SourceFile, pushSelectionRange: (start: number, end: number, kind?: SyntaxKind) => void) {
|
||||
pushSelectionRange(sourceFile.getFullStart(), sourceFile.getEnd(), SyntaxKind.SourceFile);
|
||||
|
||||
// Skip top-level SyntaxList
|
||||
let parentNode = sourceFile.getChildAt(0);
|
||||
outer: while (true) {
|
||||
const children = parentNode.getChildren(sourceFile);
|
||||
if (!children.length) break;
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
const prevNode: Node | undefined = children[i - 1];
|
||||
const node: Node = children[i];
|
||||
const nextNode: Node | undefined = children[i + 1];
|
||||
if (node.getStart(sourceFile) > pos) {
|
||||
break outer;
|
||||
}
|
||||
|
||||
if (positionBelongsToNode(node, pos, sourceFile)) {
|
||||
// Blocks are effectively redundant with SyntaxLists.
|
||||
// TemplateSpans, along with the SyntaxLists containing them,
|
||||
// are a somewhat unintuitive grouping of things that should be
|
||||
// considered independently. Dive in without pushing a selection range.
|
||||
if (isBlock(node) || isTemplateSpan(node) || isTemplateHead(node) || prevNode && isTemplateHead(prevNode)) {
|
||||
parentNode = node;
|
||||
break;
|
||||
}
|
||||
|
||||
// Synthesize a stop for '${ ... }' since '${' and '}' actually belong to siblings.
|
||||
if (isTemplateSpan(parentNode) && nextNode && isTemplateMiddleOrTemplateTail(nextNode)) {
|
||||
const start = node.getFullStart() - "${".length;
|
||||
const end = nextNode.getStart() + "}".length;
|
||||
pushSelectionRange(start, end, node.kind);
|
||||
}
|
||||
// Synthesize a stop for group of adjacent imports
|
||||
else if (isImport(node)) {
|
||||
const [firstImportIndex, lastImportIndex] = getGroupBounds(children, i, isImport);
|
||||
pushSelectionRange(
|
||||
children[firstImportIndex].getStart(),
|
||||
children[lastImportIndex].getEnd());
|
||||
}
|
||||
|
||||
// Blocks with braces on separate lines should be selected from brace to brace,
|
||||
// including whitespace but not including the braces themselves.
|
||||
const isBetweenMultiLineBraces = isSyntaxList(node)
|
||||
&& prevNode && prevNode.kind === SyntaxKind.OpenBraceToken
|
||||
&& nextNode && nextNode.kind === SyntaxKind.CloseBraceToken
|
||||
&& !positionsAreOnSameLine(prevNode.getStart(), nextNode.getStart(), sourceFile);
|
||||
const start = isBetweenMultiLineBraces ? prevNode.getEnd() : node.getStart();
|
||||
const end = isBetweenMultiLineBraces ? nextNode.getStart() : node.getEnd();
|
||||
pushSelectionRange(start, end, node.kind);
|
||||
|
||||
// Mapped types _look_ like ObjectTypes with a single member,
|
||||
// but in fact don’t contain a SyntaxList or a node containing
|
||||
// the “key/value” pair like ObjectTypes do, but it seems intuitive
|
||||
// that the selection would snap to those points. The philosophy
|
||||
// of choosing a selection range is not so much about what the
|
||||
// syntax currently _is_ as what the syntax might easily become
|
||||
// if the user is making a selection; e.g., we synthesize a selection
|
||||
// around the “key/value” pair not because there’s a node there, but
|
||||
// because it allows the mapped type to become an object type with a
|
||||
// few keystrokes.
|
||||
if (isMappedTypeNode(node)) {
|
||||
const openBraceToken = Debug.assertDefined(node.getFirstToken());
|
||||
const firstNonBraceToken = Debug.assertDefined(node.getChildAt(1));
|
||||
const closeBraceToken = Debug.assertDefined(node.getLastToken());
|
||||
Debug.assertEqual(openBraceToken.kind, SyntaxKind.OpenBraceToken);
|
||||
Debug.assertEqual(closeBraceToken.kind, SyntaxKind.CloseBraceToken);
|
||||
const spanWithoutBraces = [openBraceToken.getEnd(), closeBraceToken.getStart()] as const;
|
||||
const spanWithoutBracesOrTrivia = [firstNonBraceToken.getStart(), closeBraceToken.getFullStart()] as const;
|
||||
if (!positionsAreOnSameLine(openBraceToken.getStart(), closeBraceToken.getEnd(), sourceFile)) {
|
||||
pushSelectionRange(...spanWithoutBraces);
|
||||
}
|
||||
pushSelectionRange(...spanWithoutBracesOrTrivia);
|
||||
}
|
||||
|
||||
// String literals should have a stop both inside and outside their quotes.
|
||||
else if (isStringLiteral(node) || isTemplateLiteral(node)) {
|
||||
pushSelectionRange(start + 1, end - 1);
|
||||
}
|
||||
|
||||
parentNode = node;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getGroupBounds<T>(array: ArrayLike<T>, index: number, predicate: (element: T) => boolean): [number, number] {
|
||||
let first = index;
|
||||
let last = index;
|
||||
let i = index;
|
||||
while (i > 0) {
|
||||
const element = array[--i];
|
||||
if (predicate(element)) {
|
||||
first = i;
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
i = index;
|
||||
while (i < array.length - 1) {
|
||||
const element = array[++i];
|
||||
if (predicate(element)) {
|
||||
last = i;
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return [first, last];
|
||||
}
|
||||
}
|
||||
@@ -112,32 +112,6 @@ namespace ts.server {
|
||||
return edits.every(edit => textSpanEnd(edit.span) < pos);
|
||||
}
|
||||
|
||||
function getGroupBounds<T>(array: ArrayLike<T>, index: number, predicate: (element: T) => boolean): [number, number] {
|
||||
let first = index;
|
||||
let last = index;
|
||||
let i = index;
|
||||
while (i > 0) {
|
||||
const element = array[--i];
|
||||
if (predicate(element)) {
|
||||
first = i;
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
i = index;
|
||||
while (i < array.length - 1) {
|
||||
const element = array[++i];
|
||||
if (predicate(element)) {
|
||||
last = i;
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return [first, last];
|
||||
}
|
||||
|
||||
// CommandNames used to be exposed before TS 2.4 as a namespace
|
||||
// In TS 2.4 we switched to an enum, keep this for backward compatibility
|
||||
// The var assignment ensures that even though CommandTypes are a const enum
|
||||
@@ -2097,107 +2071,26 @@ namespace ts.server {
|
||||
const { locations } = args;
|
||||
const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args);
|
||||
|
||||
const isImport = or(isImportDeclaration, isImportEqualsDeclaration);
|
||||
|
||||
const sourceFile = languageService.getNonBoundSourceFile(file);
|
||||
const scriptInfo = Debug.assertDefined(this.projectService.getScriptInfo(file));
|
||||
const fullTextSpan = this.toLocationTextSpan(
|
||||
createTextSpanFromBounds(sourceFile.getFullStart(), sourceFile.getEnd()),
|
||||
scriptInfo);
|
||||
|
||||
return map(locations, location => {
|
||||
const pos = this.getPosition(location, scriptInfo);
|
||||
let selectionRange: protocol.SelectionRange = { textSpan: fullTextSpan };
|
||||
let selectionRange: protocol.SelectionRange | undefined;
|
||||
const pushSelectionRange = (start: number, end: number, syntaxKind?: SyntaxKind): void => {
|
||||
// Skip ranges that are identical to the parent
|
||||
const textSpan = this.toLocationTextSpan(createTextSpanFromBounds(start, end), scriptInfo);
|
||||
if (!this.locationTextSpansAreEqual(textSpan, selectionRange.textSpan)) {
|
||||
selectionRange = { textSpan, parent: selectionRange };
|
||||
if (!selectionRange || !this.locationTextSpansAreEqual(textSpan, selectionRange.textSpan)) {
|
||||
selectionRange = { textSpan, ...selectionRange && { parent: selectionRange } };
|
||||
if (syntaxKind) {
|
||||
Object.defineProperty(selectionRange, "__debugKind", { value: formatSyntaxKind(syntaxKind) });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Skip top-level SyntaxList
|
||||
let parentNode = sourceFile.getChildAt(0);
|
||||
outer: while (true) {
|
||||
const children = parentNode.getChildren(sourceFile);
|
||||
if (!children.length) break;
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
const prevNode: Node | undefined = children[i - 1];
|
||||
const node: Node = children[i];
|
||||
const nextNode: Node | undefined = children[i + 1];
|
||||
if (node.getStart(sourceFile) > pos) {
|
||||
break outer;
|
||||
}
|
||||
|
||||
if (positionBelongsToNode(node, pos, sourceFile)) {
|
||||
// Blocks are effectively redundant with SyntaxLists.
|
||||
// TemplateSpans, along with the SyntaxLists containing them,
|
||||
// are a somewhat unintuitive grouping of things that should be
|
||||
// considered independently. Dive in without pushing a selection range.
|
||||
if (isBlock(node) || isTemplateSpan(node) || isTemplateHead(node) || prevNode && isTemplateHead(prevNode)) {
|
||||
parentNode = node;
|
||||
break;
|
||||
}
|
||||
|
||||
// Synthesize a stop for '${ ... }' since '${' and '}' actually belong to siblings.
|
||||
if (isTemplateSpan(parentNode) && nextNode && isTemplateMiddleOrTemplateTail(nextNode)) {
|
||||
const start = node.getFullStart() - "${".length;
|
||||
const end = nextNode.getStart() + "}".length;
|
||||
pushSelectionRange(start, end, node.kind);
|
||||
}
|
||||
// Synthesize a stop for group of adjacent imports
|
||||
else if (isImport(node)) {
|
||||
const [firstImportIndex, lastImportIndex] = getGroupBounds(children, i, isImport);
|
||||
pushSelectionRange(
|
||||
children[firstImportIndex].getStart(),
|
||||
children[lastImportIndex].getEnd());
|
||||
}
|
||||
|
||||
// Blocks with braces on separate lines should be selected from brace to brace,
|
||||
// including whitespace but not including the braces themselves.
|
||||
const isBetweenMultiLineBraces = isSyntaxList(node)
|
||||
&& prevNode && prevNode.kind === SyntaxKind.OpenBraceToken
|
||||
&& nextNode && nextNode.kind === SyntaxKind.CloseBraceToken
|
||||
&& !positionsAreOnSameLine(prevNode.getStart(), nextNode.getStart(), sourceFile);
|
||||
const start = isBetweenMultiLineBraces ? prevNode.getEnd() : node.getStart();
|
||||
const end = isBetweenMultiLineBraces ? nextNode.getStart() : node.getEnd();
|
||||
pushSelectionRange(start, end, node.kind);
|
||||
|
||||
// Mapped types _look_ like ObjectTypes with a single member,
|
||||
// but in fact don’t contain a SyntaxList or a node containing
|
||||
// the “key/value” pair like ObjectTypes do, but it seems intuitive
|
||||
// that the selection would snap to those points. The philosophy
|
||||
// of choosing a selection range is not so much about what the
|
||||
// syntax currently _is_ as what the syntax might easily become
|
||||
// if the user is making a selection; e.g., we synthesize a selection
|
||||
// around the “key/value” pair not because there’s a node there, but
|
||||
// because it allows the mapped type to become an object type with a
|
||||
// few keystrokes.
|
||||
if (isMappedTypeNode(node)) {
|
||||
const openBraceToken = Debug.assertDefined(node.getFirstToken());
|
||||
const firstNonBraceToken = Debug.assertDefined(node.getChildAt(1));
|
||||
const closeBraceToken = Debug.assertDefined(node.getLastToken());
|
||||
Debug.assertEqual(openBraceToken.kind, SyntaxKind.OpenBraceToken);
|
||||
Debug.assertEqual(closeBraceToken.kind, SyntaxKind.CloseBraceToken);
|
||||
const spanWithoutBraces = [openBraceToken.getEnd(), closeBraceToken.getStart()] as const;
|
||||
const spanWithoutBracesOrTrivia = [firstNonBraceToken.getStart(), closeBraceToken.getFullStart()] as const;
|
||||
pushSelectionRange(...spanWithoutBraces);
|
||||
pushSelectionRange(...spanWithoutBracesOrTrivia);
|
||||
}
|
||||
|
||||
// String literals should have a stop both inside and outside their quotes.
|
||||
else if (isStringLiteral(node) || isTemplateLiteral(node)) {
|
||||
pushSelectionRange(start + 1, end - 1);
|
||||
}
|
||||
|
||||
parentNode = node;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return selectionRange;
|
||||
getSelectionRange(pos, sourceFile, pushSelectionRange);
|
||||
return selectionRange!;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,27 +1,28 @@
|
||||
{
|
||||
"extends": "../tsconfig-base",
|
||||
"compilerOptions": {
|
||||
"removeComments": false,
|
||||
"outFile": "../../built/local/server.js",
|
||||
"preserveConstEnums": true,
|
||||
"types": [
|
||||
"node"
|
||||
]
|
||||
},
|
||||
"references": [
|
||||
{ "path": "../compiler" },
|
||||
{ "path": "../jsTyping" },
|
||||
{ "path": "../services" }
|
||||
],
|
||||
"files": [
|
||||
"types.ts",
|
||||
"utilities.ts",
|
||||
"protocol.ts",
|
||||
"scriptInfo.ts",
|
||||
"typingsCache.ts",
|
||||
"project.ts",
|
||||
"editorServices.ts",
|
||||
"session.ts",
|
||||
"scriptVersionCache.ts"
|
||||
]
|
||||
}
|
||||
{
|
||||
"extends": "../tsconfig-base",
|
||||
"compilerOptions": {
|
||||
"removeComments": false,
|
||||
"outFile": "../../built/local/server.js",
|
||||
"preserveConstEnums": true,
|
||||
"types": [
|
||||
"node"
|
||||
]
|
||||
},
|
||||
"references": [
|
||||
{ "path": "../compiler" },
|
||||
{ "path": "../jsTyping" },
|
||||
{ "path": "../services" }
|
||||
],
|
||||
"files": [
|
||||
"types.ts",
|
||||
"utilities.ts",
|
||||
"protocol.ts",
|
||||
"scriptInfo.ts",
|
||||
"typingsCache.ts",
|
||||
"project.ts",
|
||||
"editorServices.ts",
|
||||
"selectionRange.ts",
|
||||
"session.ts",
|
||||
"scriptVersionCache.ts"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -164,38 +164,34 @@ type X<T, P> = IsExactlyAny<P> extends true ? T : ({ [K in keyof P]: IsExactlyAn
|
||||
textSpan: { // [K in keyof P]: IsExactlyAny<P[K]> extends true ? K extends keyof T ? T[K] : P[K] : P[K];
|
||||
start: { line: 2, offset: 54 },
|
||||
end: { line: 2, offset: 143 } },
|
||||
parent: { // same as above + whitespace
|
||||
textSpan: {
|
||||
start: { line: 2, offset: 53 },
|
||||
end: { line: 2, offset: 144 } },
|
||||
parent: {
|
||||
textSpan: { // MappedType: same as above + braces
|
||||
start: { line: 2, offset: 52 },
|
||||
end: { line: 2, offset: 145 } },
|
||||
parent: {
|
||||
textSpan: { // MappedType: same as above + braces
|
||||
textSpan: { // IntersectionType: { [K in keyof P]: ... } & Pick<T, Exclude<keyof T, keyof P>>
|
||||
start: { line: 2, offset: 52 },
|
||||
end: { line: 2, offset: 145 } },
|
||||
end: { line: 2, offset: 182 } },
|
||||
parent: {
|
||||
textSpan: { // IntersectionType: { [K in keyof P]: ... } & Pick<T, Exclude<keyof T, keyof P>>
|
||||
start: { line: 2, offset: 52 },
|
||||
end: { line: 2, offset: 182 } },
|
||||
textSpan: { // same as above + parens
|
||||
start: { line: 2, offset: 51 },
|
||||
end: { line: 2, offset: 183 } },
|
||||
parent: {
|
||||
textSpan: { // same as above + parens
|
||||
start: { line: 2, offset: 51 },
|
||||
textSpan: { // Whole TypeNode of TypeAliasDeclaration
|
||||
start: { line: 2, offset: 16 },
|
||||
end: { line: 2, offset: 183 } },
|
||||
parent: {
|
||||
textSpan: { // Whole TypeNode of TypeAliasDeclaration
|
||||
start: { line: 2, offset: 16 },
|
||||
textSpan: { // Whole TypeAliasDeclaration
|
||||
start: { line: 2, offset: 1 },
|
||||
end: { line: 2, offset: 183 } },
|
||||
parent: {
|
||||
textSpan: { // Whole TypeAliasDeclaration
|
||||
start: { line: 2, offset: 1 },
|
||||
end: { line: 2, offset: 183 } },
|
||||
parent: {
|
||||
textSpan: { // SourceFile
|
||||
start: { line: 1, offset: 1 },
|
||||
end: { line: 2, offset: 184 } } } } } } } } } } } } } },
|
||||
textSpan: { // SourceFile
|
||||
start: { line: 1, offset: 1 },
|
||||
end: { line: 2, offset: 184 } } } } } } } } } } } } },
|
||||
]);
|
||||
});
|
||||
|
||||
it.skip("works for object types", () => {
|
||||
it("works for object types", () => {
|
||||
const getSelectionRange = setup("/file.js", `
|
||||
type X = {
|
||||
foo?: string;
|
||||
|
||||
Reference in New Issue
Block a user