Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: incremental update #1718

Merged
merged 16 commits into from Aug 16, 2022
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
21 changes: 21 additions & 0 deletions extensions/vscode-vue-language-features/src/nodeClientMain.ts
Expand Up @@ -31,6 +31,27 @@ export function activate(context: vscode.ExtensionContext) {
},
};
const clientOptions: lsp.LanguageClientOptions = {
middleware: {
handleDiagnostics: (uri, diagnostics, next) => {
const document = vscode.workspace.textDocuments.find(d => d.uri.toString() === uri.toString());
if (document) {
let outdated = false;
for (const diagnostic of diagnostics) {
const data = (diagnostic as any).data;
if (typeof data === 'object' && 'version' in data) {
if (document.version !== data.version) {
outdated = true;
break;
}
}
}
if (outdated) {
return;
}
}
next(uri, diagnostics);
},
},
documentSelector,
initializationOptions: initOptions,
progressOnInitialization: true,
Expand Down
3 changes: 3 additions & 0 deletions packages/alpine-language-core/src/plugins/file-html.ts
Expand Up @@ -6,6 +6,9 @@ const plugin: VueLanguagePlugin = (ctx) => {
const vueHtmlFilePlugin = useVueHtmlFilePlugin(ctx);

return {

order: -1,

parseSFC(fileName, content) {

if (fileName.endsWith('.html')) {
Expand Down
8 changes: 2 additions & 6 deletions packages/source-map/src/index.ts
Expand Up @@ -90,22 +90,18 @@ export class SourceMapBase<Data = undefined> {

const mapped = this.getRange(startOffset, endOffset, sourceToTarget, mapping.mode, mapping.sourceRange, mapping.mappedRange, mapping.data);
if (mapped) {
yield getMapped(mapped);
yield mapped;
}
else if (mapping.additional) {
for (const other of mapping.additional) {
const mapped = this.getRange(startOffset, endOffset, sourceToTarget, other.mode, other.sourceRange, other.mappedRange, mapping.data);
if (mapped) {
yield getMapped(mapped);
yield mapped;
break; // only return first match additional range
}
}
}
}

function getMapped(mapped: [{ start: number, end: number; }, Data]) {
return mapped;
}
}

private getRange(start: number, end: number, sourceToTarget: boolean, mode: Mode, sourceRange: Range, targetRange: Range, data: Data): [{ start: number, end: number; }, Data] | undefined {
Expand Down
8 changes: 4 additions & 4 deletions packages/vue-component-meta/src/index.ts
Expand Up @@ -175,7 +175,7 @@ export function createComponentMetaChecker(tsconfigPath: string, checkerOptions:
const snapshot = host.getScriptSnapshot(componentPath)!;

const vueDefaults = componentPath.endsWith('.vue') && exportName === 'default'
? readVueComponentDefaultProps(core, snapshot.getText(0, snapshot.getLength()), printer)
? readVueComponentDefaultProps(core, snapshot, printer)
: {};
const tsDefaults = !componentPath.endsWith('.vue') ? readTsComponentDefaultProps(
componentPath.substring(componentPath.lastIndexOf('.') + 1), // ts | js | tsx | jsx
Expand Down Expand Up @@ -458,7 +458,7 @@ function createSchemaResolvers(typeChecker: ts.TypeChecker, symbolNode: ts.Expre
};
}

function readVueComponentDefaultProps(core: vue.LanguageContext, vueFileText: string, printer: ts.Printer | undefined) {
function readVueComponentDefaultProps(core: vue.LanguageContext, vueFileScript: ts.IScriptSnapshot, printer: ts.Printer | undefined) {
let result: Record<string, { default?: string, required?: boolean; }> = {};

scriptSetupWorker();
Expand All @@ -468,7 +468,7 @@ function readVueComponentDefaultProps(core: vue.LanguageContext, vueFileText: st

function scriptSetupWorker() {

const vueSourceFile = vue.createSourceFile('/tmp.vue', vueFileText, {}, ts, core.plugins);
const vueSourceFile = vue.createSourceFile('/tmp.vue', vueFileScript, {}, ts, core.plugins);
const descriptor = vueSourceFile.sfc;
const scriptSetupRanges = descriptor.scriptSetupAst ? parseScriptSetupRanges(ts, descriptor.scriptSetupAst) : undefined;

Expand Down Expand Up @@ -520,7 +520,7 @@ function readVueComponentDefaultProps(core: vue.LanguageContext, vueFileText: st

function scriptWorker() {

const vueSourceFile = vue.createSourceFile('/tmp.vue', vueFileText, {}, ts, core.plugins);
const vueSourceFile = vue.createSourceFile('/tmp.vue', vueFileScript, {}, ts, core.plugins);
const descriptor = vueSourceFile.sfc;

if (descriptor.script) {
Expand Down
56 changes: 30 additions & 26 deletions packages/vue-language-core/src/lsContext.ts
Expand Up @@ -2,7 +2,7 @@ import { posix as path } from 'path';
import type * as ts from 'typescript/lib/tsserverlibrary';
import { LanguageServiceHost, VueCompilerOptions } from './types';
import * as localTypes from './utils/localTypes';
import { createSourceFile, EmbeddedFile, VueLanguagePlugin } from './sourceFile';
import { createSourceFile, EmbeddedFile, SourceFile, VueLanguagePlugin } from './sourceFile';
import { createDocumentRegistry } from './documentRegistry';

import * as useHtmlFilePlugin from './plugins/file-html';
Expand Down Expand Up @@ -58,7 +58,11 @@ export function getPlugins(
compilerOptions,
vueCompilerOptions: vueCompilerOptions,
};
const plugins = _plugins.map(plugin => plugin(pluginCtx));
const plugins = _plugins.map(plugin => plugin(pluginCtx)).sort((a, b) => {
const aOrder = a.order ?? 0;
const bOrder = b.order ?? 0;
return aOrder - bOrder;
});

return plugins;
}
Expand Down Expand Up @@ -93,6 +97,7 @@ export function createLanguageContext(
const sharedTypesScript = ts.ScriptSnapshot.fromString(localTypes.getTypesCode(vueCompilerOptions.target));
const scriptSnapshots = new Map<string, [string, ts.IScriptSnapshot]>();
const fileVersions = new WeakMap<EmbeddedFile, string>();
const vueFileVersions = new WeakMap<SourceFile, string>();
const _tsHost: Partial<ts.LanguageServiceHost> = {
fileExists: host.fileExists
? fileName => {
Expand All @@ -117,7 +122,7 @@ export function createLanguageContext(
if (scriptSnapshot) {
documentRegistry.set(vueFileName, createSourceFile(
vueFileName,
scriptSnapshot.getText(0, scriptSnapshot.getLength()),
scriptSnapshot,
vueCompilerOptions,
ts,
plugins,
Expand Down Expand Up @@ -209,16 +214,17 @@ export function createLanguageContext(

// .vue
for (const vueFile of documentRegistry.getAll()) {
const newSnapshot = host.getScriptSnapshot(vueFile.fileName);
if (!newSnapshot) {
// delete
fileNamesToRemove.push(vueFile.fileName);
}
else {
// update
if (vueFile.text !== newSnapshot.getText(0, newSnapshot.getLength())) {
const newVersion = host.getScriptVersion(vueFile.fileName);
if (vueFileVersions.get(vueFile) !== newVersion) {
vueFileVersions.set(vueFile, newVersion);
if (host.getScriptSnapshot(vueFile.fileName)) {
// update
fileNamesToUpdate.push(vueFile.fileName);
}
else {
// delete
fileNamesToRemove.push(vueFile.fileName);
}
}
}

Expand All @@ -230,19 +236,18 @@ export function createLanguageContext(
}

// .ts / .js / .d.ts / .json ...
for (const tsFileVersion of tsFileVersions) {
if (!tsFileNames.has(tsFileVersion[0]) && !host.getScriptSnapshot(tsFileVersion[0])) {
// delete
tsFileVersions.delete(tsFileVersion[0]);
tsFileUpdated = true;
}
else {
// update
const newVersion = host.getScriptVersion(tsFileVersion[0]);
if (tsFileVersion[1] !== newVersion) {
tsFileVersions.set(tsFileVersion[0], newVersion);
tsFileUpdated = true;
for (const [oldTsFileName, oldTsFileVersion] of [...tsFileVersions]) {
const newVersion = host.getScriptVersion(oldTsFileName);
if (oldTsFileVersion !== newVersion) {
if (!tsFileNames.has(oldTsFileName) && !host.getScriptSnapshot(oldTsFileName)) {
// delete
tsFileVersions.delete(oldTsFileName);
}
else {
// update
tsFileVersions.set(oldTsFileName, newVersion);
}
tsFileUpdated = true;
}
}

Expand Down Expand Up @@ -272,12 +277,11 @@ export function createLanguageContext(
}

const sourceFile = documentRegistry.get(fileName);
const scriptText = scriptSnapshot.getText(0, scriptSnapshot.getLength());

if (!sourceFile) {
documentRegistry.set(fileName, createSourceFile(
fileName,
scriptText,
scriptSnapshot,
vueCompilerOptions,
ts,
plugins,
Expand All @@ -297,7 +301,7 @@ export function createLanguageContext(
}
}

sourceFile.text = scriptText;
sourceFile.update(scriptSnapshot);

if (!tsFileUpdated) {
for (const embedded of sourceFile.allEmbeddeds) {
Expand Down
59 changes: 32 additions & 27 deletions packages/vue-language-core/src/sourceFile.ts
@@ -1,5 +1,5 @@
import { SFCBlock, SFCParseResult, SFCScriptBlock, SFCStyleBlock, SFCTemplateBlock } from '@vue/compiler-sfc';
import { computed, ComputedRef, reactive, ref } from '@vue/reactivity';
import { computed, ComputedRef, reactive, shallowRef as ref } from '@vue/reactivity';
import { EmbeddedFileMappingData, TeleportMappingData, VueCompilerOptions, _VueCompilerOptions } from './types';
import { EmbeddedFileSourceMap, Teleport } from './utils/sourceMaps';

Expand All @@ -15,6 +15,7 @@ export type VueLanguagePlugin = (ctx: {
compilerOptions: ts.CompilerOptions,
vueCompilerOptions: _VueCompilerOptions,
}) => {
order?: number;
parseSFC?(fileName: string, content: string): SFCParseResult | undefined;
compileSFCTemplate?(lang: string, template: string, options?: CompilerDom.CompilerOptions): CompilerDom.CodegenResult | undefined;
getEmbeddedFileNames?(fileName: string, sfc: Sfc): string[];
Expand Down Expand Up @@ -82,29 +83,30 @@ export interface EmbeddedFile {

export function createSourceFile(
fileName: string,
_content: string,
scriptSnapshot: ts.IScriptSnapshot,
vueCompilerOptions: VueCompilerOptions,
ts: typeof import('typescript/lib/tsserverlibrary'),
plugins: ReturnType<VueLanguagePlugin>[],
) {

// refs
const fileContent = ref('');
const snapshot = ref(scriptSnapshot);
const fileContent = computed(() => snapshot.value.getText(0, snapshot.value.getLength()));
const sfc = reactive<Sfc>({
template: null,
script: null,
scriptSetup: null,
styles: [],
customBlocks: [],
get templateAst() {
templateAst: computed(() => {
return compiledSFCTemplate.value?.ast;
},
get scriptAst() {
}) as unknown as Sfc['templateAst'],
scriptAst: computed(() => {
return scriptAst.value;
},
get scriptSetupAst() {
}) as unknown as Sfc['scriptAst'],
scriptSetupAst: computed(() => {
return scriptSetupAst.value;
},
}) as unknown as Sfc['scriptSetupAst'],
}) as Sfc /* avoid Sfc unwrap in .d.ts by reactive */;

// use
Expand Down Expand Up @@ -147,7 +149,6 @@ export function createSourceFile(
errors.push(err);
}


if (ast || errors.length) {
return {
errors,
Expand Down Expand Up @@ -321,16 +322,14 @@ export function createSourceFile(
}
});

update(_content);
update(scriptSnapshot, true);

return {
fileName,
get text() {
return fileContent.value;
},
set text(value) {
update(value);
},
update,
get compiledSFCTemplate() {
return compiledSFCTemplate.value;
},
Expand Down Expand Up @@ -399,12 +398,18 @@ export function createSourceFile(
}
return range;
}
function update(newContent: string) {
function update(newScriptSnapshot: ts.IScriptSnapshot, init = false) {

if (fileContent.value === newContent)
if (newScriptSnapshot === snapshot.value && !init) {
return;
}

fileContent.value = newContent;
const change = newScriptSnapshot.getChangeRange(snapshot.value);
snapshot.value = newScriptSnapshot;

if (change) {
// TODO
}

// TODO: wait for https://github.com/vuejs/core/pull/5912
if (parsedSfc.value) {
Expand All @@ -419,8 +424,8 @@ export function createSourceFile(

const newData: Sfc['template'] | null = block ? {
tag: 'template',
start: newContent.substring(0, block.loc.start.offset).lastIndexOf('<'),
end: block.loc.end.offset + newContent.substring(block.loc.end.offset).indexOf('>') + 1,
start: fileContent.value.substring(0, block.loc.start.offset).lastIndexOf('<'),
end: block.loc.end.offset + fileContent.value.substring(block.loc.end.offset).indexOf('>') + 1,
startTagEnd: block.loc.start.offset,
endTagStart: block.loc.end.offset,
content: block.content,
Expand All @@ -438,8 +443,8 @@ export function createSourceFile(

const newData: Sfc['script'] | null = block ? {
tag: 'script',
start: newContent.substring(0, block.loc.start.offset).lastIndexOf('<'),
end: block.loc.end.offset + newContent.substring(block.loc.end.offset).indexOf('>') + 1,
start: fileContent.value.substring(0, block.loc.start.offset).lastIndexOf('<'),
end: block.loc.end.offset + fileContent.value.substring(block.loc.end.offset).indexOf('>') + 1,
startTagEnd: block.loc.start.offset,
endTagStart: block.loc.end.offset,
content: block.content,
Expand All @@ -458,8 +463,8 @@ export function createSourceFile(

const newData: Sfc['scriptSetup'] | null = block ? {
tag: 'scriptSetup',
start: newContent.substring(0, block.loc.start.offset).lastIndexOf('<'),
end: block.loc.end.offset + newContent.substring(block.loc.end.offset).indexOf('>') + 1,
start: fileContent.value.substring(0, block.loc.start.offset).lastIndexOf('<'),
end: block.loc.end.offset + fileContent.value.substring(block.loc.end.offset).indexOf('>') + 1,
startTagEnd: block.loc.start.offset,
endTagStart: block.loc.end.offset,
content: block.content,
Expand All @@ -479,8 +484,8 @@ export function createSourceFile(
const block = blocks[i];
const newData: Sfc['styles'][number] = {
tag: 'style',
start: newContent.substring(0, block.loc.start.offset).lastIndexOf('<'),
end: block.loc.end.offset + newContent.substring(block.loc.end.offset).indexOf('>') + 1,
start: fileContent.value.substring(0, block.loc.start.offset).lastIndexOf('<'),
end: block.loc.end.offset + fileContent.value.substring(block.loc.end.offset).indexOf('>') + 1,
startTagEnd: block.loc.start.offset,
endTagStart: block.loc.end.offset,
content: block.content,
Expand All @@ -506,8 +511,8 @@ export function createSourceFile(
const block = blocks[i];
const newData: Sfc['customBlocks'][number] = {
tag: 'customBlock',
start: newContent.substring(0, block.loc.start.offset).lastIndexOf('<'),
end: block.loc.end.offset + newContent.substring(block.loc.end.offset).indexOf('>') + 1,
start: fileContent.value.substring(0, block.loc.start.offset).lastIndexOf('<'),
end: block.loc.end.offset + fileContent.value.substring(block.loc.end.offset).indexOf('>') + 1,
startTagEnd: block.loc.start.offset,
endTagStart: block.loc.end.offset,
content: block.content,
Expand Down