@astrojs/language-server 0.14.0 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -0
- package/dist/core/documents/utils.d.ts +5 -0
- package/dist/core/documents/utils.js +15 -1
- package/dist/plugins/PluginHost.d.ts +1 -1
- package/dist/plugins/PluginHost.js +29 -4
- package/dist/plugins/astro/features/CompletionsProvider.js +21 -5
- package/dist/plugins/interfaces.d.ts +2 -2
- package/dist/plugins/typescript/TypeScriptPlugin.d.ts +6 -4
- package/dist/plugins/typescript/TypeScriptPlugin.js +9 -4
- package/dist/plugins/typescript/astro2tsx.d.ts +1 -1
- package/dist/plugins/typescript/astro2tsx.js +6 -5
- package/dist/plugins/typescript/features/CompletionsProvider.d.ts +23 -6
- package/dist/plugins/typescript/features/CompletionsProvider.js +256 -51
- package/dist/plugins/typescript/features/DocumentSymbolsProvider.js +2 -2
- package/dist/plugins/typescript/features/FoldingRangesProvider.d.ts +9 -0
- package/dist/plugins/typescript/features/FoldingRangesProvider.js +64 -0
- package/dist/plugins/typescript/features/SemanticTokenProvider.js +1 -1
- package/dist/plugins/typescript/features/utils.d.ts +2 -0
- package/dist/plugins/typescript/features/utils.js +7 -1
- package/dist/plugins/typescript/snapshots/utils.js +24 -3
- package/dist/plugins/typescript/utils.d.ts +3 -0
- package/dist/plugins/typescript/utils.js +17 -1
- package/dist/utils.d.ts +8 -0
- package/dist/utils.js +24 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# @astrojs/language-server
|
|
2
2
|
|
|
3
|
+
## 0.15.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 6bb45cb: Overhaul TypeScript completions
|
|
8
|
+
|
|
9
|
+
- Add support for completions inside expressions
|
|
10
|
+
- Add support for auto imports on completion
|
|
11
|
+
- Fix misc issues in completions (missing description, deprecated stuff not showing as deprecated)
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- 7978de1: Add support for folding JavaScript
|
|
16
|
+
- 3ac74bc: Improve props completions on components
|
|
17
|
+
- Updated dependencies [6bb45cb]
|
|
18
|
+
- @astrojs/svelte-language-integration@0.1.3
|
|
19
|
+
|
|
3
20
|
## 0.14.0
|
|
4
21
|
|
|
5
22
|
### Minor Changes
|
|
@@ -15,6 +15,11 @@ export interface TagInformation {
|
|
|
15
15
|
}
|
|
16
16
|
export declare function walk(node: Node): Generator<Node, void, unknown>;
|
|
17
17
|
export declare function extractStyleTags(source: string, html?: HTMLDocument): TagInformation[];
|
|
18
|
+
export declare function getLineAtPosition(position: Position, text: string): string;
|
|
19
|
+
/**
|
|
20
|
+
* Returns the node if offset is inside a HTML start tag
|
|
21
|
+
*/
|
|
22
|
+
export declare function getNodeIfIsInHTMLStartTag(html: HTMLDocument, offset: number): Node | undefined;
|
|
18
23
|
/**
|
|
19
24
|
* Return if a Node is a Component
|
|
20
25
|
*/
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getFirstNonWhitespaceIndex = exports.getLineOffsets = exports.offsetAt = exports.positionAt = exports.isInsideFrontmatter = exports.isInsideExpression = exports.isInTag = exports.isInComponentStartTag = exports.isComponentTag = exports.extractStyleTags = exports.walk = void 0;
|
|
3
|
+
exports.getFirstNonWhitespaceIndex = exports.getLineOffsets = exports.offsetAt = exports.positionAt = exports.isInsideFrontmatter = exports.isInsideExpression = exports.isInTag = exports.isInComponentStartTag = exports.isComponentTag = exports.getNodeIfIsInHTMLStartTag = exports.getLineAtPosition = exports.extractStyleTags = exports.walk = void 0;
|
|
4
4
|
const vscode_languageserver_1 = require("vscode-languageserver");
|
|
5
5
|
const utils_1 = require("../../utils");
|
|
6
6
|
const parseHtml_1 = require("./parseHtml");
|
|
@@ -79,6 +79,20 @@ function parseAttributes(rawAttrs) {
|
|
|
79
79
|
return attrValue;
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
|
+
function getLineAtPosition(position, text) {
|
|
83
|
+
return text.substring(offsetAt({ line: position.line, character: 0 }, text), offsetAt({ line: position.line, character: Number.MAX_VALUE }, text));
|
|
84
|
+
}
|
|
85
|
+
exports.getLineAtPosition = getLineAtPosition;
|
|
86
|
+
/**
|
|
87
|
+
* Returns the node if offset is inside a HTML start tag
|
|
88
|
+
*/
|
|
89
|
+
function getNodeIfIsInHTMLStartTag(html, offset) {
|
|
90
|
+
const node = html.findNodeAt(offset);
|
|
91
|
+
if (!!node.tag && node.tag[0] === node.tag[0].toLowerCase() && (!node.startTagEnd || offset < node.startTagEnd)) {
|
|
92
|
+
return node;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
exports.getNodeIfIsInHTMLStartTag = getNodeIfIsInHTMLStartTag;
|
|
82
96
|
/**
|
|
83
97
|
* Return if a Node is a Component
|
|
84
98
|
*/
|
|
@@ -12,7 +12,7 @@ export declare class PluginHost {
|
|
|
12
12
|
constructor(docManager: DocumentManager);
|
|
13
13
|
initialize(pluginHostConfig: PluginHostConfig): void;
|
|
14
14
|
registerPlugin(plugin: Plugin): void;
|
|
15
|
-
getCompletions(textDocument: TextDocumentIdentifier, position: Position, completionContext?: CompletionContext): Promise<CompletionList>;
|
|
15
|
+
getCompletions(textDocument: TextDocumentIdentifier, position: Position, completionContext?: CompletionContext, cancellationToken?: CancellationToken): Promise<CompletionList>;
|
|
16
16
|
resolveCompletion(textDocument: TextDocumentIdentifier, completionItem: AppCompletionItem): Promise<CompletionItem>;
|
|
17
17
|
getDiagnostics(textDocument: TextDocumentIdentifier): Promise<Diagnostic[]>;
|
|
18
18
|
doHover(textDocument: TextDocumentIdentifier, position: Position): Promise<Hover | null>;
|
|
@@ -3,6 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.PluginHost = void 0;
|
|
4
4
|
const vscode_languageserver_1 = require("vscode-languageserver");
|
|
5
5
|
const lodash_1 = require("lodash");
|
|
6
|
+
const utils_1 = require("../utils");
|
|
7
|
+
const documents_1 = require("../core/documents");
|
|
6
8
|
var ExecuteMode;
|
|
7
9
|
(function (ExecuteMode) {
|
|
8
10
|
ExecuteMode[ExecuteMode["None"] = 0] = "None";
|
|
@@ -24,11 +26,34 @@ class PluginHost {
|
|
|
24
26
|
registerPlugin(plugin) {
|
|
25
27
|
this.plugins.push(plugin);
|
|
26
28
|
}
|
|
27
|
-
async getCompletions(textDocument, position, completionContext) {
|
|
29
|
+
async getCompletions(textDocument, position, completionContext, cancellationToken) {
|
|
28
30
|
const document = this.getDocument(textDocument.uri);
|
|
29
|
-
const completions =
|
|
30
|
-
|
|
31
|
-
|
|
31
|
+
const completions = await Promise.all(this.plugins.map(async (plugin) => {
|
|
32
|
+
const result = await this.tryExecutePlugin(plugin, 'getCompletions', [document, position, completionContext, cancellationToken], null);
|
|
33
|
+
if (result) {
|
|
34
|
+
return { result: result, plugin: plugin.__name };
|
|
35
|
+
}
|
|
36
|
+
})).then((fullCompletions) => fullCompletions.filter(utils_1.isNotNullOrUndefined));
|
|
37
|
+
const html = completions.find((completion) => completion.plugin === 'html');
|
|
38
|
+
const ts = completions.find((completion) => completion.plugin === 'typescript');
|
|
39
|
+
const astro = completions.find((completion) => completion.plugin === 'astro');
|
|
40
|
+
if (html && ts) {
|
|
41
|
+
if ((0, documents_1.getNodeIfIsInHTMLStartTag)(document.html, document.offsetAt(position))) {
|
|
42
|
+
ts.result.items = [];
|
|
43
|
+
}
|
|
44
|
+
// If the Astro plugin has completions for us, don't show TypeScript's as they're most likely duplicates
|
|
45
|
+
if (astro && astro.result.items.length > 0 && (0, documents_1.isInComponentStartTag)(document.html, document.offsetAt(position))) {
|
|
46
|
+
ts.result.items = [];
|
|
47
|
+
}
|
|
48
|
+
ts.result.items = ts.result.items.map((item) => {
|
|
49
|
+
if (item.sortText != '-1') {
|
|
50
|
+
item.sortText = 'Z' + (item.sortText || '');
|
|
51
|
+
}
|
|
52
|
+
return item;
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
let flattenedCompletions = (0, lodash_1.flatten)(completions.map((completion) => completion.result.items));
|
|
56
|
+
const isIncomplete = completions.reduce((incomplete, completion) => incomplete || completion.result.isIncomplete, false);
|
|
32
57
|
return vscode_languageserver_1.CompletionList.create(flattenedCompletions, isIncomplete);
|
|
33
58
|
}
|
|
34
59
|
async resolveCompletion(textDocument, completionItem) {
|
|
@@ -33,12 +33,12 @@ class CompletionsProviderImpl {
|
|
|
33
33
|
}
|
|
34
34
|
const html = document.html;
|
|
35
35
|
const offset = document.offsetAt(position);
|
|
36
|
-
|
|
36
|
+
const node = html.findNodeAt(offset);
|
|
37
|
+
if ((0, utils_1.isInComponentStartTag)(html, offset) && !(0, utils_1.isInsideExpression)(document.getText(), node.start, offset)) {
|
|
37
38
|
const props = await this.getPropCompletions(document, position, completionContext);
|
|
38
39
|
if (props.length) {
|
|
39
40
|
items.push(...props);
|
|
40
41
|
}
|
|
41
|
-
const node = html.findNodeAt(offset);
|
|
42
42
|
const isAstro = await this.isAstroComponent(document, node);
|
|
43
43
|
if (!isAstro) {
|
|
44
44
|
const directives = (0, utils_4.removeDataAttrCompletion)(this.directivesHTMLLang.doComplete(document, position, html).items);
|
|
@@ -123,7 +123,8 @@ class CompletionsProviderImpl {
|
|
|
123
123
|
// Add completions for this component's props type properties
|
|
124
124
|
const properties = componentType.getProperties().filter((property) => property.name !== 'children') || [];
|
|
125
125
|
properties.forEach((property) => {
|
|
126
|
-
|
|
126
|
+
const type = typeChecker.getTypeOfSymbolAtLocation(property, imp);
|
|
127
|
+
let completionItem = this.getCompletionItemForProperty(property, typeChecker, type);
|
|
127
128
|
completionItems.push(completionItem);
|
|
128
129
|
});
|
|
129
130
|
// Ensure that props shows up first as a completion, despite this plugin being ran after the HTML one
|
|
@@ -180,10 +181,25 @@ class CompletionsProviderImpl {
|
|
|
180
181
|
}
|
|
181
182
|
return null;
|
|
182
183
|
}
|
|
183
|
-
getCompletionItemForProperty(mem, typeChecker) {
|
|
184
|
+
getCompletionItemForProperty(mem, typeChecker, type) {
|
|
185
|
+
const typeString = typeChecker.typeToString(type);
|
|
186
|
+
let insertText = mem.name;
|
|
187
|
+
switch (typeString) {
|
|
188
|
+
case 'string':
|
|
189
|
+
insertText = `${mem.name}="$1"`;
|
|
190
|
+
break;
|
|
191
|
+
case 'boolean':
|
|
192
|
+
insertText = mem.name;
|
|
193
|
+
break;
|
|
194
|
+
default:
|
|
195
|
+
insertText = `${mem.name}={$1}`;
|
|
196
|
+
break;
|
|
197
|
+
}
|
|
184
198
|
let item = {
|
|
185
199
|
label: mem.name,
|
|
186
|
-
|
|
200
|
+
detail: typeString,
|
|
201
|
+
insertText: insertText,
|
|
202
|
+
insertTextFormat: vscode_languageserver_1.InsertTextFormat.Snippet,
|
|
187
203
|
commitCharacters: [],
|
|
188
204
|
};
|
|
189
205
|
mem.getDocumentationComment(typeChecker);
|
|
@@ -13,7 +13,7 @@ export interface DiagnosticsProvider {
|
|
|
13
13
|
export interface HoverProvider {
|
|
14
14
|
doHover(document: TextDocument, position: Position): Resolvable<Hover | null>;
|
|
15
15
|
}
|
|
16
|
-
export interface
|
|
16
|
+
export interface FoldingRangesProvider {
|
|
17
17
|
getFoldingRanges(document: TextDocument): Resolvable<FoldingRange[] | null>;
|
|
18
18
|
}
|
|
19
19
|
export interface CompletionsProvider<T extends TextDocumentIdentifier = any> {
|
|
@@ -81,7 +81,7 @@ export interface OnWatchFileChangesProvider {
|
|
|
81
81
|
export interface UpdateNonAstroFile {
|
|
82
82
|
updateNonAstroFile(fileName: string, changes: TextDocumentContentChangeEvent[]): void;
|
|
83
83
|
}
|
|
84
|
-
declare type ProviderBase = DiagnosticsProvider & HoverProvider & CompletionsProvider & DefinitionsProvider & FormattingProvider &
|
|
84
|
+
declare type ProviderBase = DiagnosticsProvider & HoverProvider & CompletionsProvider & DefinitionsProvider & FormattingProvider & FoldingRangesProvider & TagCompleteProvider & DocumentColorsProvider & ColorPresentationsProvider & DocumentSymbolsProvider & UpdateImportsProvider & CodeActionsProvider & FindReferencesProvider & RenameProvider & SignatureHelpProvider & SemanticTokensProvider & SelectionRangeProvider & OnWatchFileChangesProvider & LinkedEditingRangesProvider & UpdateNonAstroFile;
|
|
85
85
|
export declare type LSProvider = ProviderBase;
|
|
86
86
|
export declare type Plugin = Partial<ProviderBase> & {
|
|
87
87
|
__name: string;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { CancellationToken, CompletionContext, DefinitionLink, Diagnostic, Hover, Position, Range, SemanticTokens, SignatureHelp, SignatureHelpContext, SymbolInformation, TextDocumentContentChangeEvent, WorkspaceEdit } from 'vscode-languageserver';
|
|
1
|
+
import { CancellationToken, CompletionContext, DefinitionLink, Diagnostic, FoldingRange, Hover, Position, Range, SemanticTokens, SignatureHelp, SignatureHelpContext, SymbolInformation, TextDocumentContentChangeEvent, WorkspaceEdit } from 'vscode-languageserver';
|
|
2
2
|
import { ConfigManager } from '../../core/config';
|
|
3
3
|
import { AstroDocument, DocumentManager } from '../../core/documents';
|
|
4
4
|
import { AppCompletionItem, AppCompletionList, OnWatchFileChangesParam, Plugin } from '../interfaces';
|
|
5
|
-
import {
|
|
5
|
+
import { CompletionItemData } from './features/CompletionsProvider';
|
|
6
6
|
export declare class TypeScriptPlugin implements Plugin {
|
|
7
7
|
__name: string;
|
|
8
8
|
private configManager;
|
|
@@ -13,13 +13,15 @@ export declare class TypeScriptPlugin implements Plugin {
|
|
|
13
13
|
private readonly diagnosticsProvider;
|
|
14
14
|
private readonly documentSymbolsProvider;
|
|
15
15
|
private readonly semanticTokensProvider;
|
|
16
|
+
private readonly foldingRangesProvider;
|
|
16
17
|
constructor(docManager: DocumentManager, configManager: ConfigManager, workspaceUris: string[]);
|
|
17
18
|
doHover(document: AstroDocument, position: Position): Promise<Hover | null>;
|
|
18
19
|
rename(document: AstroDocument, position: Position, newName: string): Promise<WorkspaceEdit | null>;
|
|
20
|
+
getFoldingRanges(document: AstroDocument): Promise<FoldingRange[] | null>;
|
|
19
21
|
getSemanticTokens(textDocument: AstroDocument, range?: Range, cancellationToken?: CancellationToken): Promise<SemanticTokens | null>;
|
|
20
22
|
getDocumentSymbols(document: AstroDocument): Promise<SymbolInformation[]>;
|
|
21
|
-
getCompletions(document: AstroDocument, position: Position, completionContext?: CompletionContext): Promise<AppCompletionList<
|
|
22
|
-
resolveCompletion(document: AstroDocument, completionItem: AppCompletionItem<
|
|
23
|
+
getCompletions(document: AstroDocument, position: Position, completionContext?: CompletionContext, cancellationToken?: CancellationToken): Promise<AppCompletionList<CompletionItemData> | null>;
|
|
24
|
+
resolveCompletion(document: AstroDocument, completionItem: AppCompletionItem<CompletionItemData>, cancellationToken?: CancellationToken): Promise<AppCompletionItem<CompletionItemData>>;
|
|
23
25
|
getDefinitions(document: AstroDocument, position: Position): Promise<DefinitionLink[]>;
|
|
24
26
|
getDiagnostics(document: AstroDocument, cancellationToken?: CancellationToken): Promise<Diagnostic[]>;
|
|
25
27
|
onWatchFileChanges(onWatchFileChangesParas: OnWatchFileChangesParam[]): Promise<void>;
|
|
@@ -37,6 +37,7 @@ const LanguageServiceManager_1 = require("./LanguageServiceManager");
|
|
|
37
37
|
const utils_3 = require("./utils");
|
|
38
38
|
const DocumentSymbolsProvider_1 = require("./features/DocumentSymbolsProvider");
|
|
39
39
|
const SemanticTokenProvider_1 = require("./features/SemanticTokenProvider");
|
|
40
|
+
const FoldingRangesProvider_1 = require("./features/FoldingRangesProvider");
|
|
40
41
|
class TypeScriptPlugin {
|
|
41
42
|
constructor(docManager, configManager, workspaceUris) {
|
|
42
43
|
this.__name = 'typescript';
|
|
@@ -48,6 +49,7 @@ class TypeScriptPlugin {
|
|
|
48
49
|
this.diagnosticsProvider = new DiagnosticsProvider_1.DiagnosticsProviderImpl(this.languageServiceManager);
|
|
49
50
|
this.documentSymbolsProvider = new DocumentSymbolsProvider_1.DocumentSymbolsProviderImpl(this.languageServiceManager);
|
|
50
51
|
this.semanticTokensProvider = new SemanticTokenProvider_1.SemanticTokensProviderImpl(this.languageServiceManager);
|
|
52
|
+
this.foldingRangesProvider = new FoldingRangesProvider_1.FoldingRangesProviderImpl(this.languageServiceManager);
|
|
51
53
|
}
|
|
52
54
|
async doHover(document, position) {
|
|
53
55
|
if (!this.featureEnabled('hover')) {
|
|
@@ -78,6 +80,9 @@ class TypeScriptPlugin {
|
|
|
78
80
|
});
|
|
79
81
|
return edit;
|
|
80
82
|
}
|
|
83
|
+
async getFoldingRanges(document) {
|
|
84
|
+
return this.foldingRangesProvider.getFoldingRanges(document);
|
|
85
|
+
}
|
|
81
86
|
async getSemanticTokens(textDocument, range, cancellationToken) {
|
|
82
87
|
if (!this.featureEnabled('semanticTokens')) {
|
|
83
88
|
return null;
|
|
@@ -91,15 +96,15 @@ class TypeScriptPlugin {
|
|
|
91
96
|
const symbols = await this.documentSymbolsProvider.getDocumentSymbols(document);
|
|
92
97
|
return symbols;
|
|
93
98
|
}
|
|
94
|
-
async getCompletions(document, position, completionContext) {
|
|
99
|
+
async getCompletions(document, position, completionContext, cancellationToken) {
|
|
95
100
|
if (!this.featureEnabled('completions')) {
|
|
96
101
|
return null;
|
|
97
102
|
}
|
|
98
|
-
const completions = await this.completionProvider.getCompletions(document, position, completionContext);
|
|
103
|
+
const completions = await this.completionProvider.getCompletions(document, position, completionContext, cancellationToken);
|
|
99
104
|
return completions;
|
|
100
105
|
}
|
|
101
|
-
async resolveCompletion(document, completionItem) {
|
|
102
|
-
return this.completionProvider.resolveCompletion(document, completionItem);
|
|
106
|
+
async resolveCompletion(document, completionItem, cancellationToken) {
|
|
107
|
+
return this.completionProvider.resolveCompletion(document, completionItem, cancellationToken);
|
|
103
108
|
}
|
|
104
109
|
async getDefinitions(document, position) {
|
|
105
110
|
const { lang, tsDoc } = await this.languageServiceManager.getLSAndTSDoc(document);
|
|
@@ -2,17 +2,17 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const os_1 = require("os");
|
|
4
4
|
const parseAstro_1 = require("../../core/documents/parseAstro");
|
|
5
|
-
function addProps(content) {
|
|
5
|
+
function addProps(content, className) {
|
|
6
6
|
let defaultExportType = 'Record<string, any>';
|
|
7
7
|
if (/(interface|type) Props/.test(content)) {
|
|
8
8
|
defaultExportType = 'Props';
|
|
9
9
|
}
|
|
10
|
-
return os_1.EOL + `export default function (_props: ${defaultExportType}) {
|
|
10
|
+
return os_1.EOL + `export default function ${className}__AstroComponent_(_props: ${defaultExportType}): any {}`;
|
|
11
11
|
}
|
|
12
12
|
function escapeTemplateLiteralContent(content) {
|
|
13
13
|
return content.replace(/`/g, '\\`');
|
|
14
14
|
}
|
|
15
|
-
function default_1(content) {
|
|
15
|
+
function default_1(content, className) {
|
|
16
16
|
var _a, _b;
|
|
17
17
|
let result = {
|
|
18
18
|
code: '',
|
|
@@ -24,8 +24,9 @@ function default_1(content) {
|
|
|
24
24
|
frontMatterRaw = content
|
|
25
25
|
.substring((_a = astroDocument.frontmatter.startOffset) !== null && _a !== void 0 ? _a : 0, ((_b = astroDocument.frontmatter.endOffset) !== null && _b !== void 0 ? _b : 0) + 3)
|
|
26
26
|
// Handle case where semicolons is not used in the frontmatter section
|
|
27
|
+
// We need to add something before the semi-colon or TypeScript won't be able to do completions
|
|
27
28
|
.replace(/((?!^)(?<!;)\n)(---)/g, (_whole, start, _dashes) => {
|
|
28
|
-
return start + ';'
|
|
29
|
+
return start + '"";';
|
|
29
30
|
})
|
|
30
31
|
// Replace frontmatter marks with comments
|
|
31
32
|
.replace(/---/g, '///');
|
|
@@ -80,7 +81,7 @@ function default_1(content) {
|
|
|
80
81
|
htmlRaw +
|
|
81
82
|
os_1.EOL +
|
|
82
83
|
// Add TypeScript definitions
|
|
83
|
-
addProps(frontMatterRaw);
|
|
84
|
+
addProps(frontMatterRaw, className);
|
|
84
85
|
return result;
|
|
85
86
|
}
|
|
86
87
|
exports.default = default_1;
|
|
@@ -1,16 +1,33 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { CompletionContext, Position, TextDocumentIdentifier, TextEdit, CancellationToken } from 'vscode-languageserver';
|
|
2
2
|
import type { LanguageServiceManager } from '../LanguageServiceManager';
|
|
3
3
|
import { AstroDocument } from '../../../core/documents';
|
|
4
4
|
import ts from 'typescript';
|
|
5
5
|
import { AppCompletionItem, AppCompletionList, CompletionsProvider } from '../../interfaces';
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
import { AstroSnapshotFragment } from '../snapshots/DocumentSnapshot';
|
|
7
|
+
export interface CompletionItemData extends TextDocumentIdentifier {
|
|
8
|
+
filePath: string;
|
|
9
|
+
offset: number;
|
|
10
|
+
originalItem: ts.CompletionEntry;
|
|
8
11
|
}
|
|
9
|
-
export declare class CompletionsProviderImpl implements CompletionsProvider<
|
|
12
|
+
export declare class CompletionsProviderImpl implements CompletionsProvider<CompletionItemData> {
|
|
10
13
|
private languageServiceManager;
|
|
11
14
|
constructor(languageServiceManager: LanguageServiceManager);
|
|
12
|
-
|
|
13
|
-
|
|
15
|
+
private readonly validTriggerCharacters;
|
|
16
|
+
private isValidTriggerCharacter;
|
|
17
|
+
private lastCompletion?;
|
|
18
|
+
getCompletions(document: AstroDocument, position: Position, completionContext?: CompletionContext, cancellationToken?: CancellationToken): Promise<AppCompletionList<CompletionItemData> | null>;
|
|
19
|
+
resolveCompletion(document: AstroDocument, item: AppCompletionItem<CompletionItemData>, cancellationToken?: CancellationToken): Promise<AppCompletionItem<CompletionItemData>>;
|
|
14
20
|
private toCompletionItem;
|
|
21
|
+
private isValidCompletion;
|
|
22
|
+
codeActionChangeToTextEdit(document: AstroDocument, fragment: AstroSnapshotFragment, change: ts.TextChange): TextEdit;
|
|
15
23
|
private getCompletionDocument;
|
|
24
|
+
/**
|
|
25
|
+
* If the textEdit is out of the word range of the triggered position
|
|
26
|
+
* vscode would refuse to show the completions
|
|
27
|
+
* split those edits into additionalTextEdit to fix it
|
|
28
|
+
*/
|
|
29
|
+
private fixTextEditRange;
|
|
30
|
+
private canReuseLastCompletion;
|
|
31
|
+
private getExistingImports;
|
|
32
|
+
private isAstroComponentImport;
|
|
16
33
|
}
|
|
@@ -1,92 +1,297 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var
|
|
3
|
-
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
4
24
|
};
|
|
5
25
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
26
|
exports.CompletionsProviderImpl = void 0;
|
|
7
|
-
const utils_1 = require("../../../core/documents/utils");
|
|
8
|
-
const typescript_1 = __importDefault(require("typescript"));
|
|
9
27
|
const vscode_languageserver_1 = require("vscode-languageserver");
|
|
28
|
+
const vscode_languageserver_protocol_1 = require("vscode-languageserver-protocol");
|
|
29
|
+
const utils_1 = require("../../../core/documents/utils");
|
|
30
|
+
const documents_1 = require("../../../core/documents");
|
|
31
|
+
const typescript_1 = __importStar(require("typescript"));
|
|
32
|
+
const vscode_languageserver_2 = require("vscode-languageserver");
|
|
10
33
|
const utils_2 = require("../utils");
|
|
11
|
-
const
|
|
34
|
+
const utils_3 = require("../../../utils");
|
|
35
|
+
const lodash_1 = require("lodash");
|
|
36
|
+
const previewer_1 = require("../previewer");
|
|
37
|
+
const utils_4 = require("./utils");
|
|
38
|
+
const completionOptions = {
|
|
12
39
|
importModuleSpecifierPreference: 'relative',
|
|
13
40
|
importModuleSpecifierEnding: 'auto',
|
|
14
41
|
quotePreference: 'single',
|
|
15
|
-
|
|
42
|
+
includeCompletionsForModuleExports: true,
|
|
43
|
+
includeCompletionsForImportStatements: true,
|
|
44
|
+
includeCompletionsWithInsertText: true,
|
|
45
|
+
allowIncompleteCompletions: true,
|
|
46
|
+
includeCompletionsWithSnippetText: true,
|
|
47
|
+
};
|
|
48
|
+
// `import {...} from '..'` or `import ... from '..'`
|
|
49
|
+
// Note: Does not take into account if import is within a comment.
|
|
50
|
+
const scriptImportRegex = /\bimport\s+{([^}]*?)}\s+?from\s+['"`].+?['"`]|\bimport\s+(\w+?)\s+from\s+['"`].+?['"`]/g;
|
|
16
51
|
class CompletionsProviderImpl {
|
|
17
52
|
constructor(languageServiceManager) {
|
|
18
53
|
this.languageServiceManager = languageServiceManager;
|
|
54
|
+
this.validTriggerCharacters = ['.', '"', "'", '`', '/', '@', '<', '#'];
|
|
19
55
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
56
|
+
isValidTriggerCharacter(character) {
|
|
57
|
+
return this.validTriggerCharacters.includes(character);
|
|
58
|
+
}
|
|
59
|
+
async getCompletions(document, position, completionContext, cancellationToken) {
|
|
60
|
+
const triggerCharacter = completionContext === null || completionContext === void 0 ? void 0 : completionContext.triggerCharacter;
|
|
61
|
+
const triggerKind = completionContext === null || completionContext === void 0 ? void 0 : completionContext.triggerKind;
|
|
62
|
+
const validTriggerCharacter = this.isValidTriggerCharacter(triggerCharacter) ? triggerCharacter : undefined;
|
|
63
|
+
const isCustomTriggerCharacter = triggerKind === vscode_languageserver_1.CompletionTriggerKind.TriggerCharacter;
|
|
64
|
+
if ((isCustomTriggerCharacter && !validTriggerCharacter) || (cancellationToken === null || cancellationToken === void 0 ? void 0 : cancellationToken.isCancellationRequested)) {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
if (this.canReuseLastCompletion(this.lastCompletion, triggerKind, triggerCharacter, document, position)) {
|
|
68
|
+
this.lastCompletion.position = position;
|
|
69
|
+
return this.lastCompletion.completionList;
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
this.lastCompletion = undefined;
|
|
73
|
+
}
|
|
74
|
+
const html = document.html;
|
|
75
|
+
const offset = document.offsetAt(position);
|
|
76
|
+
const node = html.findNodeAt(offset);
|
|
77
|
+
// TODO: Add support for script tags
|
|
78
|
+
if (node.tag === 'script') {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
const isCompletionInsideFrontmatter = (0, utils_1.isInsideFrontmatter)(document.getText(), offset);
|
|
82
|
+
const isCompletionInsideExpression = (0, utils_1.isInsideExpression)(document.getText(), node.start, offset);
|
|
83
|
+
// PERF: Getting TS completions is fairly slow and I am currently not sure how to speed it up
|
|
84
|
+
// As such, we'll try to avoid getting them when unneeded, such as when we're doing HTML stuff
|
|
85
|
+
// When at the root of the document TypeScript offer all kinds of completions, because it doesn't know yet that
|
|
86
|
+
// it's JSX and not JS. As such, people who are using Emmet to write their template suffer from a very degraded experience
|
|
87
|
+
// from what they're used to in HTML files (which is instant completions). So let's disable ourselves when we're at the root
|
|
88
|
+
if (!isCompletionInsideFrontmatter && !node.parent && !isCompletionInsideExpression) {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
// If the user just typed `<` with nothing else, let's disable ourselves until we're more sure if the user wants TS completions
|
|
92
|
+
if (!isCompletionInsideFrontmatter && node.parent && node.tag === undefined && !isCompletionInsideExpression) {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
// If the current node is not a component (aka, it doesn't start with a caps), let's disable ourselves as the user
|
|
96
|
+
// is most likely looking for HTML completions
|
|
97
|
+
if (!isCompletionInsideFrontmatter && !(0, utils_1.isComponentTag)(node) && !isCompletionInsideExpression) {
|
|
24
98
|
return null;
|
|
25
99
|
}
|
|
26
100
|
const { lang, tsDoc } = await this.languageServiceManager.getLSAndTSDoc(document);
|
|
27
101
|
const filePath = (0, utils_2.toVirtualAstroFilePath)(tsDoc.filePath);
|
|
102
|
+
const completions = lang.getCompletionsAtPosition(filePath, offset, {
|
|
103
|
+
...completionOptions,
|
|
104
|
+
triggerCharacter: validTriggerCharacter,
|
|
105
|
+
});
|
|
106
|
+
if (completions === undefined || completions.entries.length === 0) {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
const wordRange = completions.optionalReplacementSpan
|
|
110
|
+
? vscode_languageserver_1.Range.create(document.positionAt(completions.optionalReplacementSpan.start), document.positionAt(completions.optionalReplacementSpan.start + completions.optionalReplacementSpan.length))
|
|
111
|
+
: undefined;
|
|
112
|
+
const wordRangeStartPosition = wordRange === null || wordRange === void 0 ? void 0 : wordRange.start;
|
|
28
113
|
const fragment = await tsDoc.createFragment();
|
|
29
|
-
const
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
.map((entry) => this.toCompletionItem(fragment, entry,
|
|
33
|
-
.filter(
|
|
34
|
-
|
|
114
|
+
const existingImports = this.getExistingImports(document);
|
|
115
|
+
const completionItems = completions.entries
|
|
116
|
+
.filter(this.isValidCompletion)
|
|
117
|
+
.map((entry) => this.toCompletionItem(fragment, entry, filePath, offset, isCompletionInsideFrontmatter, existingImports))
|
|
118
|
+
.filter(utils_3.isNotNullOrUndefined)
|
|
119
|
+
.map((comp) => this.fixTextEditRange(wordRangeStartPosition, comp));
|
|
120
|
+
const completionList = vscode_languageserver_2.CompletionList.create(completionItems, true);
|
|
121
|
+
this.lastCompletion = { key: document.getFilePath() || '', position, completionList };
|
|
122
|
+
return completionList;
|
|
35
123
|
}
|
|
36
|
-
async resolveCompletion(document,
|
|
37
|
-
|
|
124
|
+
async resolveCompletion(document, item, cancellationToken) {
|
|
125
|
+
var _a;
|
|
38
126
|
const { lang, tsDoc } = await this.languageServiceManager.getLSAndTSDoc(document);
|
|
39
|
-
|
|
40
|
-
if (!
|
|
41
|
-
return
|
|
127
|
+
const data = item.data;
|
|
128
|
+
if (!data || !data.filePath || (cancellationToken === null || cancellationToken === void 0 ? void 0 : cancellationToken.isCancellationRequested)) {
|
|
129
|
+
return item;
|
|
42
130
|
}
|
|
43
131
|
const fragment = await tsDoc.createFragment();
|
|
44
|
-
const detail = lang.getCompletionEntryDetails(filePath, // fileName
|
|
45
|
-
|
|
46
|
-
|
|
132
|
+
const detail = lang.getCompletionEntryDetails(data.filePath, // fileName
|
|
133
|
+
data.offset, // position
|
|
134
|
+
data.originalItem.name, // entryName
|
|
47
135
|
{}, // formatOptions
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
136
|
+
data.originalItem.source, // source
|
|
137
|
+
completionOptions, // preferences
|
|
138
|
+
data.originalItem.data // data
|
|
51
139
|
);
|
|
52
140
|
if (detail) {
|
|
53
141
|
const { detail: itemDetail, documentation: itemDocumentation } = this.getCompletionDocument(detail);
|
|
54
|
-
|
|
55
|
-
|
|
142
|
+
item.detail = itemDetail;
|
|
143
|
+
item.documentation = itemDocumentation;
|
|
56
144
|
}
|
|
57
|
-
|
|
145
|
+
const actions = detail === null || detail === void 0 ? void 0 : detail.codeActions;
|
|
146
|
+
if (actions) {
|
|
147
|
+
const edit = [];
|
|
148
|
+
for (const action of actions) {
|
|
149
|
+
for (const change of action.changes) {
|
|
150
|
+
edit.push(...change.textChanges.map((textChange) => this.codeActionChangeToTextEdit(document, fragment, textChange)));
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
item.additionalTextEdits = ((_a = item.additionalTextEdits) !== null && _a !== void 0 ? _a : []).concat(edit);
|
|
154
|
+
}
|
|
155
|
+
return item;
|
|
58
156
|
}
|
|
59
|
-
toCompletionItem(fragment, comp,
|
|
157
|
+
toCompletionItem(fragment, comp, filePath, offset, insideFrontmatter, existingImports) {
|
|
158
|
+
var _a, _b, _c;
|
|
159
|
+
let item = vscode_languageserver_protocol_1.CompletionItem.create(comp.name);
|
|
160
|
+
const isAstroComponent = this.isAstroComponentImport(comp.name);
|
|
161
|
+
const isImport = (_a = comp.insertText) === null || _a === void 0 ? void 0 : _a.includes('import');
|
|
162
|
+
// Avoid showing completions for using components as functions
|
|
163
|
+
if (isAstroComponent && !isImport && insideFrontmatter) {
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
if (isAstroComponent) {
|
|
167
|
+
item.label = (0, utils_2.removeAstroComponentSuffix)(comp.name);
|
|
168
|
+
// Set component imports as file completion, that way we get cool icons
|
|
169
|
+
item.kind = vscode_languageserver_protocol_1.CompletionItemKind.File;
|
|
170
|
+
item.detail = (_b = comp.data) === null || _b === void 0 ? void 0 : _b.moduleSpecifier;
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
item.kind = (0, utils_2.scriptElementKindToCompletionItemKind)(comp.kind);
|
|
174
|
+
}
|
|
175
|
+
// TS may suggest another component even if there already exists an import with the same.
|
|
176
|
+
// This happens because internally, components get suffixed with __AstroComponent_
|
|
177
|
+
if (isAstroComponent && existingImports.has(item.label)) {
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
if (comp.kindModifiers) {
|
|
181
|
+
const kindModifiers = new Set(comp.kindModifiers.split(/,|\s+/g));
|
|
182
|
+
if (kindModifiers.has(typescript_1.ScriptElementKindModifier.deprecatedModifier)) {
|
|
183
|
+
item.tags = [vscode_languageserver_1.CompletionItemTag.Deprecated];
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
// Label details are currently unsupported, however, they'll be supported in the next version of LSP
|
|
187
|
+
if (comp.sourceDisplay) {
|
|
188
|
+
item.labelDetails = { description: typescript_1.default.displayPartsToString(comp.sourceDisplay) };
|
|
189
|
+
}
|
|
190
|
+
item.commitCharacters = (0, utils_2.getCommitCharactersForScriptElement)(comp.kind);
|
|
191
|
+
item.sortText = comp.sortText;
|
|
192
|
+
item.preselect = comp.isRecommended;
|
|
193
|
+
if (comp.replacementSpan) {
|
|
194
|
+
item.insertText = comp.insertText ? (0, utils_2.removeAstroComponentSuffix)(comp.insertText) : undefined;
|
|
195
|
+
item.insertTextFormat = comp.isSnippet ? vscode_languageserver_1.InsertTextFormat.Snippet : vscode_languageserver_1.InsertTextFormat.PlainText;
|
|
196
|
+
item.textEdit = comp.replacementSpan
|
|
197
|
+
? vscode_languageserver_1.TextEdit.replace((0, utils_2.convertRange)(fragment, comp.replacementSpan), (_c = item.insertText) !== null && _c !== void 0 ? _c : item.label)
|
|
198
|
+
: undefined;
|
|
199
|
+
}
|
|
60
200
|
return {
|
|
61
|
-
|
|
62
|
-
insertText: comp.insertText,
|
|
63
|
-
kind: (0, utils_2.scriptElementKindToCompletionItemKind)(comp.kind),
|
|
64
|
-
commitCharacters: (0, utils_2.getCommitCharactersForScriptElement)(comp.kind),
|
|
65
|
-
// Make sure svelte component takes precedence
|
|
66
|
-
sortText: comp.sortText,
|
|
67
|
-
preselect: comp.isRecommended,
|
|
68
|
-
// pass essential data for resolving completion
|
|
201
|
+
...item,
|
|
69
202
|
data: {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
203
|
+
uri: fragment.getURL(),
|
|
204
|
+
filePath,
|
|
205
|
+
offset,
|
|
206
|
+
originalItem: comp,
|
|
73
207
|
},
|
|
74
208
|
};
|
|
75
209
|
}
|
|
210
|
+
isValidCompletion(completion) {
|
|
211
|
+
// Remove completion for default exported function
|
|
212
|
+
if (completion.name === 'default' && completion.kindModifiers == typescript_1.ScriptElementKindModifier.exportedModifier) {
|
|
213
|
+
return false;
|
|
214
|
+
}
|
|
215
|
+
return true;
|
|
216
|
+
}
|
|
217
|
+
codeActionChangeToTextEdit(document, fragment, change) {
|
|
218
|
+
change.newText = (0, utils_2.removeAstroComponentSuffix)(change.newText);
|
|
219
|
+
// If we don't have a frontmatter already, create one with the import
|
|
220
|
+
const frontmatterState = document.astroMeta.frontmatter.state;
|
|
221
|
+
if (frontmatterState === null) {
|
|
222
|
+
return vscode_languageserver_1.TextEdit.replace(vscode_languageserver_1.Range.create(vscode_languageserver_1.Position.create(0, 0), vscode_languageserver_1.Position.create(0, 0)), `---${typescript_1.default.sys.newLine}${change.newText}---${typescript_1.default.sys.newLine}${typescript_1.default.sys.newLine}`);
|
|
223
|
+
}
|
|
224
|
+
const { span } = change;
|
|
225
|
+
let range;
|
|
226
|
+
const virtualRange = (0, utils_2.convertRange)(fragment, span);
|
|
227
|
+
range = (0, documents_1.mapRangeToOriginal)(fragment, virtualRange);
|
|
228
|
+
range = (0, utils_2.ensureFrontmatterInsert)(range, document);
|
|
229
|
+
return vscode_languageserver_1.TextEdit.replace(range, change.newText);
|
|
230
|
+
}
|
|
76
231
|
getCompletionDocument(compDetail) {
|
|
77
|
-
const {
|
|
78
|
-
let detail = typescript_1.default.displayPartsToString(displayParts);
|
|
79
|
-
if (
|
|
80
|
-
const importPath = typescript_1.default.displayPartsToString(
|
|
81
|
-
detail =
|
|
82
|
-
}
|
|
83
|
-
const documentation =
|
|
84
|
-
|
|
85
|
-
:
|
|
232
|
+
const { sourceDisplay, documentation: tsDocumentation, displayParts } = compDetail;
|
|
233
|
+
let detail = (0, utils_2.removeAstroComponentSuffix)(typescript_1.default.displayPartsToString(displayParts));
|
|
234
|
+
if (sourceDisplay) {
|
|
235
|
+
const importPath = typescript_1.default.displayPartsToString(sourceDisplay);
|
|
236
|
+
detail = importPath;
|
|
237
|
+
}
|
|
238
|
+
const documentation = {
|
|
239
|
+
kind: 'markdown',
|
|
240
|
+
value: (0, previewer_1.getMarkdownDocumentation)(tsDocumentation, compDetail.tags),
|
|
241
|
+
};
|
|
86
242
|
return {
|
|
87
243
|
documentation,
|
|
88
244
|
detail,
|
|
89
245
|
};
|
|
90
246
|
}
|
|
247
|
+
/**
|
|
248
|
+
* If the textEdit is out of the word range of the triggered position
|
|
249
|
+
* vscode would refuse to show the completions
|
|
250
|
+
* split those edits into additionalTextEdit to fix it
|
|
251
|
+
*/
|
|
252
|
+
fixTextEditRange(wordRangePosition, completionItem) {
|
|
253
|
+
const { textEdit } = completionItem;
|
|
254
|
+
if (!textEdit || !vscode_languageserver_1.TextEdit.is(textEdit) || !wordRangePosition) {
|
|
255
|
+
return completionItem;
|
|
256
|
+
}
|
|
257
|
+
const { newText, range: { start }, } = textEdit;
|
|
258
|
+
const wordRangeStartCharacter = wordRangePosition.character;
|
|
259
|
+
if (wordRangePosition.line !== wordRangePosition.line || start.character > wordRangePosition.character) {
|
|
260
|
+
return completionItem;
|
|
261
|
+
}
|
|
262
|
+
textEdit.newText = newText.substring(wordRangeStartCharacter - start.character);
|
|
263
|
+
textEdit.range.start = {
|
|
264
|
+
line: start.line,
|
|
265
|
+
character: wordRangeStartCharacter,
|
|
266
|
+
};
|
|
267
|
+
completionItem.additionalTextEdits = [
|
|
268
|
+
vscode_languageserver_1.TextEdit.replace({
|
|
269
|
+
start,
|
|
270
|
+
end: {
|
|
271
|
+
line: start.line,
|
|
272
|
+
character: wordRangeStartCharacter,
|
|
273
|
+
},
|
|
274
|
+
}, newText.substring(0, wordRangeStartCharacter - start.character)),
|
|
275
|
+
];
|
|
276
|
+
return completionItem;
|
|
277
|
+
}
|
|
278
|
+
canReuseLastCompletion(lastCompletion, triggerKind, triggerCharacter, document, position) {
|
|
279
|
+
return (!!lastCompletion &&
|
|
280
|
+
lastCompletion.key === document.getFilePath() &&
|
|
281
|
+
lastCompletion.position.line === position.line &&
|
|
282
|
+
Math.abs(lastCompletion.position.character - position.character) < 2 &&
|
|
283
|
+
(triggerKind === vscode_languageserver_1.CompletionTriggerKind.TriggerForIncompleteCompletions ||
|
|
284
|
+
// Special case: `.` is a trigger character, but inside import path completions
|
|
285
|
+
// it shouldn't trigger another completion because we can reuse the old one
|
|
286
|
+
(triggerCharacter === '.' && (0, utils_4.isPartOfImportStatement)(document.getText(), position))));
|
|
287
|
+
}
|
|
288
|
+
getExistingImports(document) {
|
|
289
|
+
const rawImports = (0, utils_3.getRegExpMatches)(scriptImportRegex, document.getText()).map((match) => { var _a; return ((_a = match[1]) !== null && _a !== void 0 ? _a : match[2]).split(','); });
|
|
290
|
+
const tidiedImports = (0, lodash_1.flatten)(rawImports).map((match) => match.trim());
|
|
291
|
+
return new Set(tidiedImports);
|
|
292
|
+
}
|
|
293
|
+
isAstroComponentImport(className) {
|
|
294
|
+
return className.endsWith('__AstroComponent_');
|
|
295
|
+
}
|
|
91
296
|
}
|
|
92
297
|
exports.CompletionsProviderImpl = CompletionsProviderImpl;
|
|
@@ -42,8 +42,8 @@ class DocumentSymbolsProviderImpl {
|
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
|
-
// Remove the
|
|
46
|
-
if (symbol.
|
|
45
|
+
// Remove the exported function in our TSX output from the symbols
|
|
46
|
+
if (document.offsetAt(symbol.location.range.start) >= document.getTextLength()) {
|
|
47
47
|
continue;
|
|
48
48
|
}
|
|
49
49
|
result.push(symbol);
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { FoldingRange } from 'vscode-languageserver';
|
|
2
|
+
import { AstroDocument } from '../../../core/documents';
|
|
3
|
+
import { FoldingRangesProvider } from '../../interfaces';
|
|
4
|
+
import { LanguageServiceManager } from '../LanguageServiceManager';
|
|
5
|
+
export declare class FoldingRangesProviderImpl implements FoldingRangesProvider {
|
|
6
|
+
private readonly languageServiceManager;
|
|
7
|
+
constructor(languageServiceManager: LanguageServiceManager);
|
|
8
|
+
getFoldingRanges(document: AstroDocument): Promise<FoldingRange[] | null>;
|
|
9
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.FoldingRangesProviderImpl = void 0;
|
|
7
|
+
const typescript_1 = __importDefault(require("typescript"));
|
|
8
|
+
const vscode_languageserver_1 = require("vscode-languageserver");
|
|
9
|
+
const utils_1 = require("../utils");
|
|
10
|
+
class FoldingRangesProviderImpl {
|
|
11
|
+
constructor(languageServiceManager) {
|
|
12
|
+
this.languageServiceManager = languageServiceManager;
|
|
13
|
+
}
|
|
14
|
+
async getFoldingRanges(document) {
|
|
15
|
+
const html = document.html;
|
|
16
|
+
const { lang, tsDoc } = await this.languageServiceManager.getLSAndTSDoc(document);
|
|
17
|
+
const filePath = (0, utils_1.toVirtualAstroFilePath)(tsDoc.filePath);
|
|
18
|
+
const outliningSpans = lang.getOutliningSpans(filePath);
|
|
19
|
+
const foldingRanges = [];
|
|
20
|
+
for (const span of outliningSpans) {
|
|
21
|
+
const node = html.findNodeAt(span.textSpan.start);
|
|
22
|
+
// Due to how our TSX output transform those tags into function calls or template literals
|
|
23
|
+
// TypeScript thinks of those as outlining spans, which is fine but we don't want folding ranges for those
|
|
24
|
+
if (node.tag === 'script' || node.tag === 'Markdown' || node.tag === 'style') {
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
const start = document.positionAt(span.textSpan.start);
|
|
28
|
+
const end = adjustFoldingEnd(start, document.positionAt(span.textSpan.start + span.textSpan.length), document);
|
|
29
|
+
// When using this method for generating folding ranges, TypeScript tend to return some
|
|
30
|
+
// one line / one character ones that we should be able to safely ignore
|
|
31
|
+
if (start.line === end.line && start.character === end.character) {
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
foldingRanges.push(vscode_languageserver_1.FoldingRange.create(start.line, end.line, start.character, end.character, transformFoldingRangeKind(span.kind)));
|
|
35
|
+
}
|
|
36
|
+
return foldingRanges;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
exports.FoldingRangesProviderImpl = FoldingRangesProviderImpl;
|
|
40
|
+
function transformFoldingRangeKind(tsKind) {
|
|
41
|
+
switch (tsKind) {
|
|
42
|
+
case typescript_1.default.OutliningSpanKind.Comment:
|
|
43
|
+
return vscode_languageserver_1.FoldingRangeKind.Comment;
|
|
44
|
+
case typescript_1.default.OutliningSpanKind.Imports:
|
|
45
|
+
return vscode_languageserver_1.FoldingRangeKind.Imports;
|
|
46
|
+
case typescript_1.default.OutliningSpanKind.Region:
|
|
47
|
+
return vscode_languageserver_1.FoldingRangeKind.Region;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
// https://github.com/microsoft/vscode/blob/bed61166fb604e519e82e4d1d1ed839bc45d65f8/extensions/typescript-language-features/src/languageFeatures/folding.ts#L61-L73
|
|
51
|
+
function adjustFoldingEnd(start, end, document) {
|
|
52
|
+
// workaround for #47240
|
|
53
|
+
if (end.character > 0) {
|
|
54
|
+
const foldEndCharacter = document.getText({
|
|
55
|
+
start: { line: end.line, character: end.character - 1 },
|
|
56
|
+
end,
|
|
57
|
+
});
|
|
58
|
+
if (['}', ']', ')', '`'].includes(foldEndCharacter)) {
|
|
59
|
+
const endOffset = Math.max(document.offsetAt({ line: end.line, character: 0 }) - 1, document.offsetAt(start));
|
|
60
|
+
return document.positionAt(endOffset);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return end;
|
|
64
|
+
}
|
|
@@ -25,7 +25,7 @@ class SemanticTokensProviderImpl {
|
|
|
25
25
|
length: range
|
|
26
26
|
? fragment.offsetAt(fragment.getGeneratedPosition(range.end)) - start
|
|
27
27
|
: // We don't want tokens for things added by astro2tsx
|
|
28
|
-
fragment.text.lastIndexOf('export default function
|
|
28
|
+
fragment.text.lastIndexOf('export default function ') || fragment.text.length,
|
|
29
29
|
}, typescript_1.default.SemanticClassificationFormat.TwentyTwenty);
|
|
30
30
|
const tokens = [];
|
|
31
31
|
let i = 0;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { SnapshotFragment, DocumentSnapshot } from '../snapshots/DocumentSnapshot';
|
|
2
2
|
import type { LanguageServiceManager } from '../LanguageServiceManager';
|
|
3
|
+
import { Position } from 'vscode-languageserver';
|
|
4
|
+
export declare function isPartOfImportStatement(text: string, position: Position): boolean;
|
|
3
5
|
export declare class SnapshotFragmentMap {
|
|
4
6
|
private languageServiceManager;
|
|
5
7
|
private map;
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.SnapshotFragmentMap = void 0;
|
|
3
|
+
exports.SnapshotFragmentMap = exports.isPartOfImportStatement = void 0;
|
|
4
|
+
const documents_1 = require("../../../core/documents");
|
|
5
|
+
function isPartOfImportStatement(text, position) {
|
|
6
|
+
const line = (0, documents_1.getLineAtPosition)(position, text);
|
|
7
|
+
return /\s*from\s+["'][^"']*/.test(line.slice(0, position.character));
|
|
8
|
+
}
|
|
9
|
+
exports.isPartOfImportStatement = isPartOfImportStatement;
|
|
4
10
|
class SnapshotFragmentMap {
|
|
5
11
|
constructor(languageServiceManager) {
|
|
6
12
|
this.languageServiceManager = languageServiceManager;
|
|
@@ -6,12 +6,14 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.createFromFrameworkFilePath = exports.createFromAstroFilePath = exports.createFromTSFilePath = exports.createFromNonAstroFilePath = exports.createFromFilePath = exports.createFromDocument = void 0;
|
|
7
7
|
const typescript_1 = __importDefault(require("typescript"));
|
|
8
8
|
const astro2tsx_1 = __importDefault(require("../astro2tsx"));
|
|
9
|
+
const vscode_uri_1 = require("vscode-uri");
|
|
9
10
|
const utils_1 = require("../utils");
|
|
10
11
|
const DocumentSnapshot_1 = require("./DocumentSnapshot");
|
|
11
12
|
const svelte_language_integration_1 = require("@astrojs/svelte-language-integration");
|
|
13
|
+
const utils_2 = require("../../../utils");
|
|
12
14
|
// Utilities to create Snapshots from different contexts
|
|
13
15
|
function createFromDocument(document) {
|
|
14
|
-
const { code } = (0, astro2tsx_1.default)(document.getText());
|
|
16
|
+
const { code } = (0, astro2tsx_1.default)(document.getText(), classNameFromFilename(document.getURL()));
|
|
15
17
|
return new DocumentSnapshot_1.AstroSnapshot(document, code, typescript_1.default.ScriptKind.TSX);
|
|
16
18
|
}
|
|
17
19
|
exports.createFromDocument = createFromDocument;
|
|
@@ -71,14 +73,33 @@ function createFromAstroFilePath(filePath, createDocument) {
|
|
|
71
73
|
exports.createFromAstroFilePath = createFromAstroFilePath;
|
|
72
74
|
function createFromFrameworkFilePath(filePath, framework) {
|
|
73
75
|
var _a;
|
|
76
|
+
const className = classNameFromFilename(filePath);
|
|
74
77
|
const originalText = (_a = typescript_1.default.sys.readFile(filePath)) !== null && _a !== void 0 ? _a : '';
|
|
75
78
|
let code = '';
|
|
76
79
|
if (framework === 'svelte') {
|
|
77
|
-
code = (0, svelte_language_integration_1.toTSX)(originalText);
|
|
80
|
+
code = (0, svelte_language_integration_1.toTSX)(originalText, className);
|
|
78
81
|
}
|
|
79
82
|
else {
|
|
80
|
-
code =
|
|
83
|
+
code = `export default function ${className}__AstroComponent_(props: Record<string, any>): any {}`;
|
|
81
84
|
}
|
|
82
85
|
return new DocumentSnapshot_1.TypeScriptDocumentSnapshot(0, filePath, code, typescript_1.default.ScriptKind.TSX);
|
|
83
86
|
}
|
|
84
87
|
exports.createFromFrameworkFilePath = createFromFrameworkFilePath;
|
|
88
|
+
function classNameFromFilename(filename) {
|
|
89
|
+
const url = vscode_uri_1.URI.parse(filename);
|
|
90
|
+
const withoutExtensions = vscode_uri_1.Utils.basename(url).slice(0, -vscode_uri_1.Utils.extname(url).length);
|
|
91
|
+
const withoutInvalidCharacters = withoutExtensions
|
|
92
|
+
.split('')
|
|
93
|
+
// Although "-" is invalid, we leave it in, pascal-case-handling will throw it out later
|
|
94
|
+
.filter((char) => /[A-Za-z$_\d-]/.test(char))
|
|
95
|
+
.join('');
|
|
96
|
+
const firstValidCharIdx = withoutInvalidCharacters
|
|
97
|
+
.split('')
|
|
98
|
+
// Although _ and $ are valid first characters for classes, they are invalid first characters
|
|
99
|
+
// for tag names. For a better import autocompletion experience, we therefore throw them out.
|
|
100
|
+
.findIndex((char) => /[A-Za-z]/.test(char));
|
|
101
|
+
const withoutLeadingInvalidCharacters = withoutInvalidCharacters.substr(firstValidCharIdx);
|
|
102
|
+
const inPascalCase = (0, utils_2.toPascalCase)(withoutLeadingInvalidCharacters);
|
|
103
|
+
const finalName = firstValidCharIdx === -1 ? `A${inPascalCase}` : inPascalCase;
|
|
104
|
+
return finalName;
|
|
105
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import ts from 'typescript';
|
|
2
2
|
import { CompletionItemKind, DiagnosticSeverity, Position, Range, SymbolKind, SemanticTokensLegend } from 'vscode-languageserver';
|
|
3
|
+
import { AstroDocument } from '../../core/documents';
|
|
3
4
|
import { SnapshotFragment } from './snapshots/DocumentSnapshot';
|
|
4
5
|
export declare const enum TokenType {
|
|
5
6
|
class = 0,
|
|
@@ -39,6 +40,8 @@ export declare function convertRange(document: {
|
|
|
39
40
|
length?: number;
|
|
40
41
|
}): Range;
|
|
41
42
|
export declare function convertToLocationRange(defDoc: SnapshotFragment, textSpan: ts.TextSpan): Range;
|
|
43
|
+
export declare function ensureFrontmatterInsert(resultRange: Range, document: AstroDocument): Range;
|
|
44
|
+
export declare function removeAstroComponentSuffix(name: string): string;
|
|
42
45
|
export declare type FrameworkExt = 'astro' | 'vue' | 'jsx' | 'tsx' | 'svelte';
|
|
43
46
|
declare type FrameworkVirtualExt = 'ts' | 'tsx';
|
|
44
47
|
export declare function getFrameworkFromFilePath(filePath: string): FrameworkExt;
|
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.ensureRealFilePath = exports.ensureRealAstroFilePath = exports.toRealAstroFilePath = exports.toVirtualFilePath = exports.toVirtualAstroFilePath = exports.isVirtualFilePath = exports.isVirtualSvelteFilePath = exports.isVirtualVueFilePath = exports.isVirtualAstroFilePath = exports.isFrameworkFilePath = exports.isAstroFilePath = exports.isVirtualFrameworkFilePath = exports.getFrameworkFromFilePath = exports.convertToLocationRange = exports.convertRange = exports.mapSeverity = exports.getScriptKindFromFileName = exports.isSubPath = exports.findTsConfigPath = exports.getExtensionFromScriptKind = exports.getCommitCharactersForScriptElement = exports.scriptElementKindToCompletionItemKind = exports.symbolKindFromString = exports.getSemanticTokenLegend = void 0;
|
|
6
|
+
exports.ensureRealFilePath = exports.ensureRealAstroFilePath = exports.toRealAstroFilePath = exports.toVirtualFilePath = exports.toVirtualAstroFilePath = exports.isVirtualFilePath = exports.isVirtualSvelteFilePath = exports.isVirtualVueFilePath = exports.isVirtualAstroFilePath = exports.isFrameworkFilePath = exports.isAstroFilePath = exports.isVirtualFrameworkFilePath = exports.getFrameworkFromFilePath = exports.removeAstroComponentSuffix = exports.ensureFrontmatterInsert = exports.convertToLocationRange = exports.convertRange = exports.mapSeverity = exports.getScriptKindFromFileName = exports.isSubPath = exports.findTsConfigPath = exports.getExtensionFromScriptKind = exports.getCommitCharactersForScriptElement = exports.scriptElementKindToCompletionItemKind = exports.symbolKindFromString = exports.getSemanticTokenLegend = void 0;
|
|
7
7
|
const typescript_1 = __importDefault(require("typescript"));
|
|
8
8
|
const path_1 = require("path");
|
|
9
9
|
const utils_1 = require("../../utils");
|
|
@@ -239,6 +239,22 @@ function convertToLocationRange(defDoc, textSpan) {
|
|
|
239
239
|
return range;
|
|
240
240
|
}
|
|
241
241
|
exports.convertToLocationRange = convertToLocationRange;
|
|
242
|
+
// Some code actions will insert code at the start of the file instead of inside our frontmatter
|
|
243
|
+
// We'll redirect those to the proper starting place
|
|
244
|
+
function ensureFrontmatterInsert(resultRange, document) {
|
|
245
|
+
if (document.astroMeta.frontmatter.state === 'closed') {
|
|
246
|
+
const position = document.positionAt(document.astroMeta.frontmatter.startOffset);
|
|
247
|
+
position.line += 1;
|
|
248
|
+
position.character = resultRange.start.character;
|
|
249
|
+
return vscode_languageserver_1.Range.create(position, position);
|
|
250
|
+
}
|
|
251
|
+
return resultRange;
|
|
252
|
+
}
|
|
253
|
+
exports.ensureFrontmatterInsert = ensureFrontmatterInsert;
|
|
254
|
+
function removeAstroComponentSuffix(name) {
|
|
255
|
+
return name.replace(/(\w+)__AstroComponent_/, '$1');
|
|
256
|
+
}
|
|
257
|
+
exports.removeAstroComponentSuffix = removeAstroComponentSuffix;
|
|
242
258
|
const VirtualExtension = {
|
|
243
259
|
ts: 'ts',
|
|
244
260
|
tsx: 'tsx',
|
package/dist/utils.d.ts
CHANGED
|
@@ -16,6 +16,10 @@ export declare function pathToUrl(path: string): string;
|
|
|
16
16
|
* (bar or bar.astro in this example).
|
|
17
17
|
*/
|
|
18
18
|
export declare function getLastPartOfPath(path: string): string;
|
|
19
|
+
/**
|
|
20
|
+
* Transform a string into PascalCase
|
|
21
|
+
*/
|
|
22
|
+
export declare function toPascalCase(string: string): string;
|
|
19
23
|
/**
|
|
20
24
|
* Return true if a specific node could be a component.
|
|
21
25
|
* This is not a 100% sure test as it'll return false for any component that does not match the standard format for a component
|
|
@@ -28,6 +32,10 @@ export declare function clamp(num: number, min: number, max: number): number;
|
|
|
28
32
|
export declare function isNotNullOrUndefined<T>(val: T | undefined | null): val is T;
|
|
29
33
|
export declare function isInRange(range: Range, positionToTest: Position): boolean;
|
|
30
34
|
export declare function isBeforeOrEqualToPosition(position: Position, positionToTest: Position): boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Get all matches of a regexp.
|
|
37
|
+
*/
|
|
38
|
+
export declare function getRegExpMatches(regex: RegExp, str: string): RegExpExecArray[];
|
|
31
39
|
/**
|
|
32
40
|
* Debounces a function but cancels previous invocation only if
|
|
33
41
|
* a second function determines it should.
|
package/dist/utils.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getUserAstroVersion = exports.debounceThrottle = exports.debounceSameArg = exports.isBeforeOrEqualToPosition = exports.isInRange = exports.isNotNullOrUndefined = exports.clamp = exports.flatten = exports.isPossibleComponent = exports.getLastPartOfPath = exports.pathToUrl = exports.urlToPath = exports.normalizePath = exports.normalizeUri = void 0;
|
|
3
|
+
exports.getUserAstroVersion = exports.debounceThrottle = exports.debounceSameArg = exports.getRegExpMatches = exports.isBeforeOrEqualToPosition = exports.isInRange = exports.isNotNullOrUndefined = exports.clamp = exports.flatten = exports.isPossibleComponent = exports.toPascalCase = exports.getLastPartOfPath = exports.pathToUrl = exports.urlToPath = exports.normalizePath = exports.normalizeUri = void 0;
|
|
4
4
|
const vscode_uri_1 = require("vscode-uri");
|
|
5
5
|
/** Normalizes a document URI */
|
|
6
6
|
function normalizeUri(uri) {
|
|
@@ -37,6 +37,17 @@ function getLastPartOfPath(path) {
|
|
|
37
37
|
return path.replace(/\\/g, '/').split('/').pop() || '';
|
|
38
38
|
}
|
|
39
39
|
exports.getLastPartOfPath = getLastPartOfPath;
|
|
40
|
+
/**
|
|
41
|
+
* Transform a string into PascalCase
|
|
42
|
+
*/
|
|
43
|
+
function toPascalCase(string) {
|
|
44
|
+
return `${string}`
|
|
45
|
+
.replace(new RegExp(/[-_]+/, 'g'), ' ')
|
|
46
|
+
.replace(new RegExp(/[^\w\s]/, 'g'), '')
|
|
47
|
+
.replace(new RegExp(/\s+(.)(\w*)/, 'g'), ($1, $2, $3) => `${$2.toUpperCase() + $3.toLowerCase()}`)
|
|
48
|
+
.replace(new RegExp(/\w/), (s) => s.toUpperCase());
|
|
49
|
+
}
|
|
50
|
+
exports.toPascalCase = toPascalCase;
|
|
40
51
|
/**
|
|
41
52
|
* Return true if a specific node could be a component.
|
|
42
53
|
* This is not a 100% sure test as it'll return false for any component that does not match the standard format for a component
|
|
@@ -69,6 +80,18 @@ function isBeforeOrEqualToPosition(position, positionToTest) {
|
|
|
69
80
|
(positionToTest.line === position.line && positionToTest.character <= position.character));
|
|
70
81
|
}
|
|
71
82
|
exports.isBeforeOrEqualToPosition = isBeforeOrEqualToPosition;
|
|
83
|
+
/**
|
|
84
|
+
* Get all matches of a regexp.
|
|
85
|
+
*/
|
|
86
|
+
function getRegExpMatches(regex, str) {
|
|
87
|
+
const matches = [];
|
|
88
|
+
let match;
|
|
89
|
+
while ((match = regex.exec(str))) {
|
|
90
|
+
matches.push(match);
|
|
91
|
+
}
|
|
92
|
+
return matches;
|
|
93
|
+
}
|
|
94
|
+
exports.getRegExpMatches = getRegExpMatches;
|
|
72
95
|
/**
|
|
73
96
|
* Debounces a function but cancels previous invocation only if
|
|
74
97
|
* a second function determines it should.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astrojs/language-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"author": "withastro",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "commonjs",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"test": "cross-env TS_NODE_TRANSPILE_ONLY=true mocha --timeout 20000 --require ts-node/register \"test/**/*.ts\" --exclude \"test/**/*.d.ts\""
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@astrojs/svelte-language-integration": "^0.1.
|
|
22
|
+
"@astrojs/svelte-language-integration": "^0.1.3",
|
|
23
23
|
"@vscode/emmet-helper": "^2.8.4",
|
|
24
24
|
"lodash": "^4.17.21",
|
|
25
25
|
"source-map": "^0.7.3",
|