Skip to content

Commit

Permalink
refactor(language-core): simplify VirtualFile (#91)
Browse files Browse the repository at this point in the history
  • Loading branch information
johnsoncodehk authored Nov 21, 2023
1 parent 9438ab7 commit c2c0840
Show file tree
Hide file tree
Showing 61 changed files with 1,266 additions and 1,319 deletions.
4 changes: 2 additions & 2 deletions extensions/labs/src/common/showVirtualFile.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { FileRangeCapabilities } from '@volar/language-server';
import type { CodeInformation } from '@volar/language-server';
import { SourceMap, Stack } from '@volar/source-map';
import { ExportsInfoForLabs, TextDocument } from '@volar/vscode';
import * as vscode from 'vscode';
Expand Down Expand Up @@ -138,7 +138,7 @@ export async function activate(info: ExportsInfoForLabs) {
));
}

const virtualUriToSourceMap = new Map<string, [string, number, SourceMap<FileRangeCapabilities>][]>();
const virtualUriToSourceMap = new Map<string, [string, number, SourceMap<CodeInformation>][]>();
const virtualUriToStacks = new Map<string, Stack[]>();
const virtualDocuments = new Map<string, TextDocument>();

Expand Down
5 changes: 3 additions & 2 deletions extensions/labs/src/views/virtualFilesView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,9 @@ export function activate(context: vscode.ExtensionContext) {
sourceUriToVirtualUris.set(element.sourceDocumentUri, virtualFileUris);

let label = path.basename(element.virtualFile.id);
const version = (element.virtualFile as any).version;
label += ` (kind: ${element.virtualFile.kind}, version: ${version})`;
// @ts-expect-error
const version = element.virtualFile.version;
label += ` (ts: ${!!element.virtualFile.typescript}, version: ${version})`;
return {
iconPath: element.client.clientOptions.initializationOptions.codegenStack ? new vscode.ThemeIcon('debug-breakpoint') : new vscode.ThemeIcon('file'),
label,
Expand Down
4 changes: 2 additions & 2 deletions packages/language-core/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export * from './lib/createFileProvider';
export * from './lib/mirrorMap';
export * from './lib/fileProvider';
export * from './lib/linkedCodeMap';
export * from './lib/types';

export function resolveCommonLanguageId(fileNameOrUri: string) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import { SourceMap } from '@volar/source-map';
import type * as ts from 'typescript/lib/tsserverlibrary';
import { MirrorMap } from './mirrorMap';
import type { FileRangeCapabilities, Language, SourceFile, VirtualFile } from './types';
import { LinkedCodeMap } from './linkedCodeMap';
import type { CodeInformation, Language, SourceFile, VirtualFile } from './types';

export type FileProvider = ReturnType<typeof createFileProvider>;

export function createFileProvider(languages: Language[], caseSensitive: boolean, sync: (sourceFileId: string) => void) {

const sourceFileRegistry = new Map<string, SourceFile>();
const virtualFileRegistry = new Map<string, { virtualFile: VirtualFile, source: SourceFile; }>();
const virtualFileMaps = new WeakMap<ts.IScriptSnapshot, Map<string, [ts.IScriptSnapshot, SourceMap<FileRangeCapabilities>]>>();
const virtualFileToMirrorMap = new WeakMap<ts.IScriptSnapshot, MirrorMap | undefined>();
const virtualFileRegistry = new Map<string, [VirtualFile, SourceFile]>();
const virtualFileToMaps = new WeakMap<ts.IScriptSnapshot, Map<string, [ts.IScriptSnapshot, SourceMap<CodeInformation>]>>();
const virtualFileToLinkedCodeMap = new WeakMap<ts.IScriptSnapshot, LinkedCodeMap | undefined>();
const normalizeId = caseSensitive
? (id: string) => id
: (id: string) => id.toLowerCase();
Expand All @@ -26,9 +28,9 @@ export function createFileProvider(languages: Language[], caseSensitive: boolean
else if (value.snapshot !== snapshot) {
// updated
value.snapshot = snapshot;
if (value.root && value.language) {
deleteVirtualFiles(value);
value.language.updateVirtualFile(value.root, snapshot);
if (value.virtualFile) {
disposeVirtualFiles(value);
value.virtualFile[1].updateVirtualFile(value.virtualFile[0], snapshot);
updateVirtualFiles(value);
}
return value;
Expand All @@ -43,7 +45,12 @@ export function createFileProvider(languages: Language[], caseSensitive: boolean
const virtualFile = language.createVirtualFile(id, languageId, snapshot);
if (virtualFile) {
// created
const source: SourceFile = { id: id, languageId, snapshot, root: virtualFile, language };
const source: SourceFile = {
id,
languageId,
snapshot,
virtualFile: [virtualFile, language],
};
sourceFileRegistry.set(normalizeId(id), source);
updateVirtualFiles(source);
return source;
Expand All @@ -57,23 +64,23 @@ export function createFileProvider(languages: Language[], caseSensitive: boolean
deleteSourceFile(id: string) {
const value = sourceFileRegistry.get(normalizeId(id));
if (value) {
if (value.language && value.root) {
value.language.deleteVirtualFile?.(value.root);
if (value.virtualFile) {
value.virtualFile[1].disposeVirtualFile?.(value.virtualFile[0]);
}
sourceFileRegistry.delete(normalizeId(id)); // deleted
deleteVirtualFiles(value);
disposeVirtualFiles(value);
}
},
getMirrorMap(file: VirtualFile) {
if (!virtualFileToMirrorMap.has(file.snapshot)) {
virtualFileToMirrorMap.set(file.snapshot, file.mirrorBehaviorMappings ? new MirrorMap(file.mirrorBehaviorMappings) : undefined);
if (!virtualFileToLinkedCodeMap.has(file.snapshot)) {
virtualFileToLinkedCodeMap.set(file.snapshot, file.linkedCodeMappings ? new LinkedCodeMap(file.linkedCodeMappings) : undefined);
}
return virtualFileToMirrorMap.get(file.snapshot);
return virtualFileToLinkedCodeMap.get(file.snapshot);
},
getMaps(virtualFile: VirtualFile) {

if (!virtualFileMaps.has(virtualFile.snapshot)) {
virtualFileMaps.set(virtualFile.snapshot, new Map());
if (!virtualFileToMaps.has(virtualFile.snapshot)) {
virtualFileToMaps.set(virtualFile.snapshot, new Map());
}

updateVirtualFileMaps(virtualFile, sourceId => {
Expand All @@ -82,12 +89,12 @@ export function createFileProvider(languages: Language[], caseSensitive: boolean
return [sourceId, sourceFile.snapshot];
}
else {
const source = virtualFileRegistry.get(normalizeId(virtualFile.id))!.source;
return [source.id, source.snapshot];
const sourceFile = virtualFileRegistry.get(normalizeId(virtualFile.id))![1];
return [sourceFile.id, sourceFile.snapshot];
}
}, virtualFileMaps.get(virtualFile.snapshot));
}, virtualFileToMaps.get(virtualFile.snapshot));

return virtualFileMaps.get(virtualFile.snapshot)!;
return virtualFileToMaps.get(virtualFile.snapshot)!;
},
getSourceFile(id: string) {
sync(id);
Expand All @@ -96,28 +103,28 @@ export function createFileProvider(languages: Language[], caseSensitive: boolean
getVirtualFile(id: string) {
let sourceAndVirtual = virtualFileRegistry.get(normalizeId(id));
if (sourceAndVirtual) {
sync(sourceAndVirtual.source.id);
sync(sourceAndVirtual[1].id);
sourceAndVirtual = virtualFileRegistry.get(normalizeId(id));
if (sourceAndVirtual) {
return [sourceAndVirtual.virtualFile, sourceAndVirtual.source] as const;
return sourceAndVirtual;
}
}
return [undefined, undefined] as const;
},
};

function deleteVirtualFiles(source: SourceFile) {
if (source.root) {
for (const file of forEachEmbeddedFile(source.root)) {
function disposeVirtualFiles(source: SourceFile) {
if (source.virtualFile) {
for (const file of forEachEmbeddedFile(source.virtualFile[0])) {
virtualFileRegistry.delete(normalizeId(file.id));
}
}
}

function updateVirtualFiles(source: SourceFile) {
if (source.root) {
for (const file of forEachEmbeddedFile(source.root)) {
virtualFileRegistry.set(normalizeId(file.id), { virtualFile: file, source });
if (source.virtualFile) {
for (const file of forEachEmbeddedFile(source.virtualFile[0])) {
virtualFileRegistry.set(normalizeId(file.id), [file, source]);
}
}
}
Expand All @@ -126,7 +133,7 @@ export function createFileProvider(languages: Language[], caseSensitive: boolean
export function updateVirtualFileMaps(
virtualFile: VirtualFile,
getSourceSnapshot: (sourceUri: string | undefined) => [string, ts.IScriptSnapshot] | undefined,
map: Map<string, [ts.IScriptSnapshot, SourceMap<FileRangeCapabilities>]> = new Map(),
map: Map<string, [ts.IScriptSnapshot, SourceMap<CodeInformation>]> = new Map(),
) {

const sources = new Set<string | undefined>();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import * as SourceMaps from '@volar/source-map';
import { MirrorBehaviorCapabilities } from './types';
import { LinkedCodeTrigger } from './types';

export class MirrorMap extends SourceMaps.SourceMap<[MirrorBehaviorCapabilities, MirrorBehaviorCapabilities]> {
*findMirrorOffsets(start: number) {
export class LinkedCodeMap extends SourceMaps.SourceMap<[LinkedCodeTrigger, LinkedCodeTrigger]> {
*toLinkedOffsets(start: number) {
for (const mapped of this.toGeneratedOffsets(start)) {
yield [mapped[0], mapped[1].data[1]] as const;
}
Expand Down
129 changes: 47 additions & 82 deletions packages/language-core/lib/types.ts
Original file line number Diff line number Diff line change
@@ -1,117 +1,82 @@
import { Mapping, Stack } from '@volar/source-map';
import type * as ts from 'typescript/lib/tsserverlibrary';
import type { createFileProvider } from '../lib/createFileProvider';
import type { FileProvider } from './fileProvider';

export interface FileCapabilities {
diagnostic?: boolean;
foldingRange?: boolean;
documentFormatting?: boolean;
documentSymbol?: boolean;
codeAction?: boolean;
inlayHint?: boolean;
export interface SourceFile extends BaseFile {
virtualFile?: [VirtualFile, Language];
}

export interface FileRangeCapabilities {
hover?: boolean;
references?: boolean;
definition?: boolean;
rename?: boolean | {
normalize?(newName: string): string;
apply?(newName: string): string;
};
completion?: boolean | {
additional?: boolean;
autoImportOnly?: boolean;
};
diagnostic?: boolean | {
shouldReport(): boolean;
export interface VirtualFile extends BaseFile {
mappings: Mapping<CodeInformation>[];
embeddedFiles: VirtualFile[];
typescript?: {
scriptKind: ts.ScriptKind;
};
semanticTokens?: boolean;

// TODO
referencesCodeLens?: boolean;
displayWithLink?: boolean;
codegenStacks?: Stack[];
linkedCodeMappings?: Mapping<[LinkedCodeTrigger, LinkedCodeTrigger]>[];
}

export interface MirrorBehaviorCapabilities {
references?: boolean;
definition?: boolean;
export interface LinkedCodeTrigger {
reference?: boolean;
rename?: boolean;
definition?: boolean;
highlight?: boolean;
}

export namespace FileCapabilities {
export const full: FileCapabilities = {
diagnostic: true,
foldingRange: true,
documentFormatting: true,
documentSymbol: true,
codeAction: true,
inlayHint: true,
export interface CodeInformation {
diagnostics?: boolean | {
shouldReport(): boolean;
};
}

export namespace FileRangeCapabilities {
export const full: FileRangeCapabilities = {
hover: true,
references: true,
definition: true,
rename: true,
completion: true,
diagnostic: true,
semanticTokens: true,
renameEdits?: boolean | {
shouldRename: boolean;
shouldEdit: boolean;
resolveNewName?(newName: string): string;
resolveEditText?(newText: string): string;
};
}

export namespace MirrorBehaviorCapabilities {
export const full: MirrorBehaviorCapabilities = {
references: true,
definition: true,
rename: true,
formattingEdits?: boolean;
completionItems?: boolean | {
isAdditional?: boolean;
onlyImport?: boolean;
};
definitions?: boolean;
references?: boolean;
foldingRanges?: boolean;
inlayHints?: boolean;
codeActions?: boolean;
symbols?: boolean;
selectionRanges?: boolean;
linkedEditingRanges?: boolean;
colors?: boolean;
autoInserts?: boolean;
codeLenses?: boolean;
highlights?: boolean;
links?: boolean;
semanticTokens?: boolean;
hover?: boolean;
signatureHelps?: boolean;
}

export enum FileKind {
TextFile = 0,
TypeScriptHostFile = 1,
}

export interface SourceFile extends BaesFile {
root?: VirtualFile;
language?: Language;
}

export interface VirtualFile extends BaesFile {
kind: FileKind,
capabilities: FileCapabilities,
mappings: Mapping<FileRangeCapabilities>[],
codegenStacks: Stack[],
mirrorBehaviorMappings?: Mapping<[MirrorBehaviorCapabilities, MirrorBehaviorCapabilities]>[],
embeddedFiles: VirtualFile[],
}

export interface BaesFile {
export interface BaseFile {
/**
* for language-server, kit, monaco, this is uri
*
* for typescript server plugin, tsc, this is fileName
*/
id: string,
languageId: string,
snapshot: ts.IScriptSnapshot,
id: string;
languageId: string;
snapshot: ts.IScriptSnapshot;
}

export interface Language<T extends VirtualFile = VirtualFile> {
createVirtualFile(id: string, languageId: string, snapshot: ts.IScriptSnapshot): T | undefined;
updateVirtualFile(virtualFile: T, snapshot: ts.IScriptSnapshot): void;
deleteVirtualFile?(virtualFile: T): void;
disposeVirtualFile?(virtualFile: T): void;
typescript?: {
resolveModuleName?(path: string, impliedNodeFormat?: ts.ResolutionMode): string | undefined;
resolveLanguageServiceHost?(host: ts.LanguageServiceHost): ts.LanguageServiceHost;
};
}

export type FileProvider = ReturnType<typeof createFileProvider>;

export interface Project {
fileProvider: FileProvider;
typescript?: {
Expand Down
4 changes: 2 additions & 2 deletions packages/language-server/lib/documentManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,15 +215,15 @@ export function createDocumentManager(

return {
data: snapshots,
onDidChangeContent: (cb: (params: vscode.DidChangeTextDocumentParams) => void): vscode.Disposable => {
onDidChangeContent(cb: (params: vscode.DidChangeTextDocumentParams) => void): vscode.Disposable {
onDidChangeContents.add(cb);
return {
dispose() {
onDidChangeContents.delete(cb);
},
};
},
onDidClose: (cb: (params: vscode.DidCloseTextDocumentParams) => void): vscode.Disposable => {
onDidClose(cb: (params: vscode.DidCloseTextDocumentParams) => void): vscode.Disposable {
onDidCloses.add(cb);
return {
dispose() {
Expand Down
Loading

0 comments on commit c2c0840

Please sign in to comment.