Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions packages/language-core/lib/codegen/codeFeatures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ const raw = {
semantic: true,
navigation: true,
},
htmlAutoImportOnly: {
htmlAutoImport: true,
},
verification: {
verification: true,
},
Expand Down
17 changes: 6 additions & 11 deletions packages/language-core/lib/codegen/template/element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,11 @@ export function* generateComponent(
yield `, `;
}
yield `]} */${endOfLine}`;

// auto import support
yield `// @ts-ignore${newLine}`; // #2304
yield* generateCamelized(capitalize(node.tag), 'template', tagOffsets[0], codeFeatures.htmlAutoImportOnly);
yield endOfLine;
}
else if (dynamicTagInfo) {
yield `const ${componentOriginalVar} = (`;
Expand Down Expand Up @@ -185,17 +190,7 @@ export function* generateComponent(

// auto import support
yield `// @ts-ignore${newLine}`; // #2304
yield* generateCamelized(
capitalize(node.tag),
'template',
tagOffsets[0],
{
completion: {
isAdditional: true,
onlyImport: true,
},
},
);
yield* generateCamelized(capitalize(node.tag), 'template', tagOffsets[0], codeFeatures.htmlAutoImportOnly);
yield endOfLine;
}
}
Expand Down
1 change: 1 addition & 0 deletions packages/language-core/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export type RawVueCompilerOptions = Partial<Omit<VueCompilerOptions, 'target' |
};

export interface VueCodeInformation extends CodeInformation {
htmlAutoImport?: boolean;
__combineToken?: symbol;
__linkedToken?: symbol;
}
Expand Down
6 changes: 6 additions & 0 deletions packages/language-server/lib/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,12 @@ export function startServer(ts: typeof import('typescript')) {
getImportPathForFile(...args) {
return sendTsServerRequest('_vue:getImportPathForFile', args);
},
getAutoImportSuggestions(...args) {
return sendTsServerRequest('_vue:getAutoImportSuggestions', args);
},
resolveAutoImportCompletionEntry(...args) {
return sendTsServerRequest('_vue:resolveAutoImportCompletionEntry', args);
},
isRefAtPosition(...args) {
return sendTsServerRequest('_vue:isRefAtPosition', args);
},
Expand Down
4 changes: 2 additions & 2 deletions packages/language-service/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,8 @@ export function createVueLanguageServicePlugins(
createVueDocumentHighlightsPlugin(client),
createVueExtractFilePlugin(ts, client),
createVueMissingPropsHintsPlugin(client),
createVueTemplatePlugin('html', client),
createVueTemplatePlugin('jade', client),
createVueTemplatePlugin(ts, 'html', client),
createVueTemplatePlugin(ts, 'jade', client),
createVueTwoslashQueriesPlugin(client),
];
}
167 changes: 154 additions & 13 deletions packages/language-service/lib/plugins/vue-template.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import type {
CompletionItemKind,
CompletionItemTag,
CompletionList,
Disposable,
LanguageServicePlugin,
TextDocument,
import {
type CompletionItemKind,
type CompletionItemTag,
type CompletionList,
type Disposable,
type LanguageServicePlugin,
type TextDocument,
transformCompletionItem,
} from '@volar/language-service';
import { getSourceRange } from '@volar/language-service/lib/utils/featureWorkers';
import {
forEachInterpolationNode,
hyphenateAttr,
Expand All @@ -17,6 +19,12 @@ import { camelize, capitalize, isPromise } from '@vue/shared';
import type { ComponentPropInfo } from '@vue/typescript-plugin/lib/requests/getComponentProps';
import { create as createHtmlService, resolveReference } from 'volar-service-html';
import { create as createPugService } from 'volar-service-pug';
import { getFormatCodeSettings } from 'volar-service-typescript/lib/configs/getFormatCodeSettings.js';
import { getUserPreferences } from 'volar-service-typescript/lib/configs/getUserPreferences.js';
import {
applyCompletionEntryDetails,
convertCompletionInfo,
} from 'volar-service-typescript/lib/utils/lspConverters.js';
import * as html from 'vscode-html-languageservice';
import { URI, Utils } from 'vscode-uri';
import { loadModelModifiersData, loadTemplateData } from '../data';
Expand Down Expand Up @@ -51,6 +59,7 @@ let builtInData: html.HTMLDataV1 | undefined;
let modelData: html.HTMLDataV1 | undefined;

export function create(
ts: typeof import('typescript'),
languageId: 'html' | 'jade',
{
getComponentNames,
Expand All @@ -60,6 +69,8 @@ export function create(
getComponentSlots,
getElementAttrs,
resolveModuleName,
getAutoImportSuggestions,
resolveAutoImportCompletionEntry,
}: import('@vue/typescript-plugin/lib/requests').Requests,
): LanguageServicePlugin {
let customData: html.IHTMLDataProvider[] = [];
Expand Down Expand Up @@ -231,8 +242,11 @@ export function create(
}

const disposable = context.env.onDidChangeConfiguration?.(() => initializing = undefined);
const transformedItems = new WeakSet<html.CompletionItem>();

let initializing: Promise<void> | undefined;
let formattingOptions: html.FormattingOptions | undefined;
let lastCompletionDocument: TextDocument | undefined;

return {
...baseServiceInstance,
Expand All @@ -242,6 +256,16 @@ export function create(
disposable?.dispose();
},

provideDocumentFormattingEdits(document, range, options, ...rest) {
formattingOptions = options;
return baseServiceInstance.provideDocumentFormattingEdits?.(document, range, options, ...rest);
},

provideOnTypeFormattingEdits(document, position, key, options, ...rest) {
formattingOptions = options;
return baseServiceInstance.provideOnTypeFormattingEdits?.(document, position, key, options, ...rest);
},

async provideCompletionItems(document, position, completionContext, token) {
if (document.languageId !== languageId) {
return;
Expand All @@ -252,9 +276,10 @@ export function create(
}

const {
result: completionList,
result: htmlCompletion,
target,
info: {
tagNameCasing,
components,
propMap,
},
Expand All @@ -270,24 +295,71 @@ export function create(
),
);

if (!completionList) {
if (!htmlCompletion) {
return;
}

const autoImportPlaceholderIndex = htmlCompletion.items.findIndex(item =>
item.label === 'AutoImportsPlaceholder'
);
if (autoImportPlaceholderIndex !== -1) {
const offset = document.offsetAt(position);
const map = context.language.maps.get(info.code, info.script);
let spliced = false;
for (const [sourceOffset] of map.toSourceLocation(offset)) {
const [formatOptions, preferences] = await Promise.all([
getFormatCodeSettings(context, document, formattingOptions),
getUserPreferences(context, document),
]);
const autoImport = await getAutoImportSuggestions(
info.root.fileName,
sourceOffset,
preferences,
formatOptions,
);
if (!autoImport) {
continue;
}
const tsCompletion = convertCompletionInfo(ts, autoImport, document, position, entry => entry.data);
const placeholder = htmlCompletion.items[autoImportPlaceholderIndex]!;
for (const tsItem of tsCompletion.items) {
if (placeholder.textEdit) {
const newText = tsItem.textEdit?.newText ?? tsItem.label;
tsItem.textEdit = {
...placeholder.textEdit,
newText: tagNameCasing === TagNameCasing.Kebab
? hyphenateTag(newText)
: newText,
};
}
else {
tsItem.textEdit = undefined;
}
}
htmlCompletion.items.splice(autoImportPlaceholderIndex, 1, ...tsCompletion.items);
spliced = true;
lastCompletionDocument = document;
break;
}
if (!spliced) {
htmlCompletion.items.splice(autoImportPlaceholderIndex, 1);
}
}

switch (target) {
case 'tag': {
completionList.items.forEach(transformTag);
htmlCompletion.items.forEach(transformTag);
break;
}
case 'attribute': {
addDirectiveModifiers(completionList, document);
completionList.items.forEach(transformAttribute);
addDirectiveModifiers(htmlCompletion, document);
htmlCompletion.items.forEach(transformAttribute);
break;
}
}

updateExtraCustomData([]);
return completionList;
return htmlCompletion;

function transformTag(item: html.CompletionItem) {
const tagName = capitalize(camelize(item.label));
Expand Down Expand Up @@ -399,6 +471,61 @@ export function create(
}
},

async resolveCompletionItem(item) {
if (item.data?.__isAutoImport || item.data?.__isComponentAutoImport) {
const embeddedUri = URI.parse(lastCompletionDocument!.uri);
const decoded = context.decodeEmbeddedDocumentUri(embeddedUri);
if (!decoded) {
return item;
}
const sourceScript = context.language.scripts.get(decoded[0]);
if (!sourceScript) {
return item;
}
const [formatOptions, preferences] = await Promise.all([
getFormatCodeSettings(context, lastCompletionDocument!, formattingOptions),
getUserPreferences(context, lastCompletionDocument!),
]);
const details = await resolveAutoImportCompletionEntry(item.data, preferences, formatOptions);
if (details) {
const virtualCode = sourceScript.generated!.embeddedCodes.get(decoded[1])!;
const sourceDocument = context.documents.get(
sourceScript.id,
sourceScript.languageId,
sourceScript.snapshot,
);
const embeddedDocument = context.documents.get(embeddedUri, virtualCode.languageId, virtualCode.snapshot);
const map = context.language.maps.get(virtualCode, sourceScript);
item = transformCompletionItem(
item,
embeddedRange =>
getSourceRange(
[sourceDocument, embeddedDocument, map],
embeddedRange,
),
embeddedDocument,
context,
);
applyCompletionEntryDetails(
ts,
item,
details,
sourceDocument,
fileName => URI.file(fileName),
() => undefined,
);
transformedItems.add(item);
}
}
return item;
},

transformCompletionItem(item) {
if (transformedItems.has(item)) {
return item;
}
},

provideHover(document, position, token) {
if (document.languageId !== languageId) {
return;
Expand Down Expand Up @@ -728,6 +855,19 @@ export function create(
}));
},
},
{
getId: () => 'vue-auto-imports',
isApplicable: () => true,
provideTags() {
return [{ name: 'AutoImportsPlaceholder', attributes: [] }];
},
provideAttributes() {
return [];
},
provideValues() {
return [];
},
},
]);

return {
Expand All @@ -737,6 +877,7 @@ export function create(
version,
target,
info: {
tagNameCasing,
components,
propMap,
},
Expand Down
Loading