Skip to content

Commit f916170

Browse files
refactor(language-core): decouple from filename format (#86)
1 parent b642b92 commit f916170

38 files changed

+423
-385
lines changed

.vscode/launch.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
"--extensionDevelopmentPath=${workspaceRoot}/packages/labs"
1212
],
1313
"outFiles": [
14-
"${workspaceRoot}/*/*/out/**/*.js"
14+
"${workspaceRoot}/extensions/labs/dist/**/*.js"
1515
],
1616
},
1717
],

extensions/labs/scripts/build.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22

33
require('esbuild').context({
44
entryPoints: {
5-
extension: './src/extension.js',
5+
extension: './src/extension.ts',
66
},
7+
sourcemap: true,
78
bundle: true,
89
metafile: process.argv.includes('--metafile'),
910
outdir: './dist',

extensions/labs/src/views/virtualFilesView.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,25 +53,25 @@ export function activate(context: vscode.ExtensionContext) {
5353
getTreeItem(element) {
5454
if ('virtualFile' in element) {
5555

56-
const uri = vscode.Uri.file(element.virtualFile.fileName).with({ scheme: element.client.name.replace(/ /g, '_').toLowerCase() });
56+
const uri = vscode.Uri.parse(element.virtualFile.id).with({ scheme: element.client.name.replace(/ /g, '_').toLowerCase() });
5757
virtualUriToSourceUri.set(uri.toString(), element.sourceDocumentUri);
5858

5959
const virtualFileUris = sourceUriToVirtualUris.get(element.sourceDocumentUri) ?? new Set<string>();
6060
virtualFileUris.add(uri.toString());
6161
sourceUriToVirtualUris.set(element.sourceDocumentUri, virtualFileUris);
6262

63-
let label = path.basename(element.virtualFile.fileName);
63+
let label = path.basename(element.virtualFile.id);
6464
const version = (element.virtualFile as any).version;
6565
label += ` (kind: ${element.virtualFile.kind}, version: ${version})`;
6666
return {
6767
iconPath: element.client.clientOptions.initializationOptions.codegenStack ? new vscode.ThemeIcon('debug-breakpoint') : new vscode.ThemeIcon('file'),
6868
label,
6969
collapsibleState: element.virtualFile.embeddedFiles.length ? vscode.TreeItemCollapsibleState.Expanded : vscode.TreeItemCollapsibleState.None,
70-
resourceUri: vscode.Uri.file(element.virtualFile.fileName),
70+
resourceUri: vscode.Uri.parse(element.virtualFile.id),
7171
command: {
7272
command: '_volar.action.openVirtualFile',
7373
title: '',
74-
arguments: [vscode.Uri.file(element.virtualFile.fileName).with({ scheme: element.client.name.replace(/ /g, '_').toLowerCase() })],
74+
arguments: [vscode.Uri.parse(element.virtualFile.id).with({ scheme: element.client.name.replace(/ /g, '_').toLowerCase() })],
7575
},
7676
};
7777
}

packages/kit/lib/createChecker.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ function createTypeScriptCheckerWorker(
7272
};
7373

7474
const projectHost = getTypeScriptProjectHost(env);
75-
const project = createTypeScriptProject(projectHost, languages, resolveCommonLanguageId);
75+
const project = createTypeScriptProject(languages, projectHost, env.fileNameToUri, resolveCommonLanguageId);
7676
const service = createLanguageService(
7777
{ typescript: ts as any },
7878
services,

packages/kit/lib/createFormatter.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ export function createFormatter(
99
) {
1010

1111
let fakeUri = 'file:///dummy.txt';
12-
let fakeFileName = '/dummy.txt';
1312
let settings = {};
1413

1514
const env = createServiceEnvironment(() => settings);
@@ -36,7 +35,7 @@ export function createFormatter(
3635

3736
async function format(content: string, languageId: string, options: FormattingOptions): Promise<string> {
3837

39-
fileProvider.updateSource(fakeFileName, ts.ScriptSnapshot.fromString(content), languageId);
38+
fileProvider.updateSourceFile(fakeUri, ts.ScriptSnapshot.fromString(content), languageId);
4039

4140
const document = service.context.getTextDocument(fakeUri)!;
4241
const edits = await service.format(fakeUri, options, undefined, undefined);
Lines changed: 36 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,32 @@
11
import { SourceMap } from '@volar/source-map';
22
import type * as ts from 'typescript/lib/tsserverlibrary';
33
import { MirrorMap } from './mirrorMap';
4-
import type { FileRangeCapabilities, Language, VirtualFile } from './types';
5-
6-
export interface Source {
7-
fileName: string;
8-
languageId: string;
9-
snapshot: ts.IScriptSnapshot;
10-
root?: VirtualFile;
11-
language?: Language;
12-
}
4+
import type { FileRangeCapabilities, Language, SourceFile, VirtualFile } from './types';
5+
6+
const caseSensitive = false; // TODO: use ts.sys.useCaseSensitiveFileNames
137

148
export function createFileProvider(languages: Language[], sync: () => void) {
159

16-
const sourceFiles = new Map<string, Source>();
17-
const virtualFiles = new Map<string, { virtualFile: VirtualFile, source: Source; }>();
10+
const sourceFileRegistry = new Map<string, SourceFile>();
11+
const virtualFileRegistry = new Map<string, { virtualFile: VirtualFile, source: SourceFile; }>();
1812
const virtualFileMaps = new WeakMap<ts.IScriptSnapshot, Map<string, [ts.IScriptSnapshot, SourceMap<FileRangeCapabilities>]>>();
1913
const virtualFileToMirrorMap = new WeakMap<ts.IScriptSnapshot, MirrorMap | undefined>();
14+
const normalizeId = caseSensitive
15+
? (id: string) => id
16+
: (id: string) => id.toLowerCase();
2017

2118
return {
22-
getAllSources() {
19+
getAllSourceFiles() {
2320
sync();
24-
return sourceFiles;
21+
return sourceFileRegistry.values();
2522
},
26-
updateSource(fileName: string, snapshot: ts.IScriptSnapshot, languageId: string): VirtualFile | undefined {
27-
const key = normalizePath(fileName);
28-
const value = sourceFiles.get(key);
23+
updateSourceFile(id: string, snapshot: ts.IScriptSnapshot, languageId: string): VirtualFile | undefined {
24+
const value = sourceFileRegistry.get(normalizeId(id));
2925
if (value) {
3026
if (value.languageId !== languageId) {
3127
// languageId changed
32-
this.deleteSource(fileName);
33-
return this.updateSource(fileName, snapshot, languageId);
28+
this.deleteSourceFile(id);
29+
return this.updateSourceFile(id, snapshot, languageId);
3430
}
3531
else if (value.snapshot !== snapshot) {
3632
value.snapshot = snapshot;
@@ -46,24 +42,23 @@ export function createFileProvider(languages: Language[], sync: () => void) {
4642
}
4743
}
4844
for (const language of languages) {
49-
const virtualFile = language.createVirtualFile(fileName, snapshot, languageId);
45+
const virtualFile = language.createVirtualFile(id, languageId, snapshot);
5046
if (virtualFile) {
51-
const source: Source = { fileName, languageId, snapshot, root: virtualFile, language };
52-
sourceFiles.set(key, source);
47+
const source: SourceFile = { id: id, languageId, snapshot, root: virtualFile, language };
48+
sourceFileRegistry.set(normalizeId(id), source);
5349
updateVirtualFiles(source);
5450
return virtualFile; // created
5551
}
5652
}
57-
sourceFiles.set(key, { fileName, languageId, snapshot });
53+
sourceFileRegistry.set(normalizeId(id), { id: id, languageId, snapshot });
5854
},
59-
deleteSource(fileName: string) {
60-
const key = normalizePath(fileName);
61-
const value = sourceFiles.get(key);
55+
deleteSourceFile(id: string) {
56+
const value = sourceFileRegistry.get(normalizeId(id));
6257
if (value) {
6358
if (value.language && value.root) {
6459
value.language.deleteVirtualFile?.(value.root);
6560
}
66-
sourceFiles.delete(key); // deleted
61+
sourceFileRegistry.delete(normalizeId(id)); // deleted
6762
deleteVirtualFiles(value);
6863
}
6964
},
@@ -79,62 +74,53 @@ export function createFileProvider(languages: Language[], sync: () => void) {
7974
virtualFileMaps.set(virtualFile.snapshot, new Map());
8075
}
8176

82-
updateVirtualFileMaps(virtualFile, sourceFileName => {
83-
if (sourceFileName) {
84-
const source = sourceFiles.get(normalizePath(sourceFileName))!;
85-
return [sourceFileName, source.snapshot];
77+
updateVirtualFileMaps(virtualFile, sourceId => {
78+
if (sourceId) {
79+
const sourceFile = sourceFileRegistry.get(normalizeId(sourceId))!;
80+
return [sourceId, sourceFile.snapshot];
8681
}
8782
else {
88-
const source = virtualFiles.get(normalizePath(virtualFile.fileName))!.source;
89-
return [source.fileName, source.snapshot];
83+
const source = virtualFileRegistry.get(normalizeId(virtualFile.id))!.source;
84+
return [source.id, source.snapshot];
9085
}
9186
}, virtualFileMaps.get(virtualFile.snapshot));
9287

9388
return virtualFileMaps.get(virtualFile.snapshot)!;
9489
},
95-
getSource(fileName: string) {
96-
sync();
97-
const key = normalizePath(fileName);
98-
return sourceFiles.get(key);
99-
},
100-
hasSource(fileName: string) {
90+
getSourceFile(id: string) {
10191
sync();
102-
return sourceFiles.has(normalizePath(fileName));
92+
return sourceFileRegistry.get(normalizeId(id));
10393
},
104-
hasVirtualFile(fileName: string) {
94+
getVirtualFile(id: string) {
10595
sync();
106-
return !!virtualFiles.get(normalizePath(fileName));
107-
},
108-
getVirtualFile(fileName: string) {
109-
sync();
110-
const sourceAndVirtual = virtualFiles.get(normalizePath(fileName));
96+
const sourceAndVirtual = virtualFileRegistry.get(normalizeId(id));
11197
if (sourceAndVirtual) {
11298
return [sourceAndVirtual.virtualFile, sourceAndVirtual.source] as const;
11399
}
114100
return [undefined, undefined] as const;
115101
},
116102
};
117103

118-
function deleteVirtualFiles(source: Source) {
104+
function deleteVirtualFiles(source: SourceFile) {
119105
if (source.root) {
120106
forEachEmbeddedFile(source.root, file => {
121-
virtualFiles.delete(normalizePath(file.fileName));
107+
virtualFileRegistry.delete(normalizeId(file.id));
122108
});
123109
}
124110
}
125111

126-
function updateVirtualFiles(source: Source) {
112+
function updateVirtualFiles(source: SourceFile) {
127113
if (source.root) {
128114
forEachEmbeddedFile(source.root, file => {
129-
virtualFiles.set(normalizePath(file.fileName), { virtualFile: file, source });
115+
virtualFileRegistry.set(normalizeId(file.id), { virtualFile: file, source });
130116
});
131117
}
132118
}
133119
}
134120

135121
export function updateVirtualFileMaps(
136122
virtualFile: VirtualFile,
137-
getSourceSnapshot: (source: string | undefined) => [string, ts.IScriptSnapshot] | undefined,
123+
getSourceSnapshot: (sourceUri: string | undefined) => [string, ts.IScriptSnapshot] | undefined,
138124
map: Map<string, [ts.IScriptSnapshot, SourceMap<FileRangeCapabilities>]> = new Map(),
139125
) {
140126

@@ -165,7 +151,3 @@ export function forEachEmbeddedFile(file: VirtualFile, cb: (embedded: VirtualFil
165151
forEachEmbeddedFile(embeddedFile, cb);
166152
}
167153
}
168-
169-
function normalizePath(fileName: string) {
170-
return fileName.replace(/\\/g, '/').toLowerCase();
171-
}

packages/language-core/lib/createTypeScriptProject.ts

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,20 @@ import { Language, TypeScriptProjectHost, Project } from './types';
33
import type * as ts from 'typescript/lib/tsserverlibrary';
44

55
export function createTypeScriptProject(
6-
projectHost: TypeScriptProjectHost,
76
languages: Language<any>[],
8-
getLanguageId: (fileName: string) => string,
7+
projectHost: TypeScriptProjectHost,
8+
fileNameToId: (fileName: string) => string,
9+
getLanguageId: (fileName: string) => string
910
): Project {
1011

1112
for (const language of languages) {
12-
if (language.resolveTypeScriptProjectHost) {
13-
projectHost = language.resolveTypeScriptProjectHost(projectHost);
13+
if (language.typescript?.resolveProjectHost) {
14+
projectHost = language.typescript.resolveProjectHost(projectHost);
1415
}
1516
}
1617

1718
let lastRootFiles = new Map<string, ts.IScriptSnapshot | undefined>();
18-
let lastProjectVersion: number | string | undefined;
19+
let lastProjectVersion: string | undefined;
1920

2021
const fileProvider = createFileProvider(languages, () => {
2122

@@ -34,17 +35,19 @@ export function createTypeScriptProject(
3435
for (const [fileName, snapshot] of newRootFiles) {
3536
remainRootFiles.delete(fileName);
3637
if (lastRootFiles.get(fileName) !== newRootFiles.get(fileName)) {
38+
const id = fileNameToId(fileName);
3739
if (snapshot) {
38-
fileProvider.updateSource(fileName, snapshot, getLanguageId(fileName));
40+
fileProvider.updateSourceFile(id, snapshot, getLanguageId(fileName));
3941
}
4042
else {
41-
fileProvider.deleteSource(fileName);
43+
fileProvider.deleteSourceFile(id);
4244
}
4345
}
4446
}
4547

4648
for (const fileName of remainRootFiles) {
47-
fileProvider.deleteSource(fileName);
49+
const id = fileNameToId(fileName);
50+
fileProvider.deleteSourceFile(id);
4851
}
4952

5053
lastRootFiles = newRootFiles;

packages/language-core/lib/types.ts

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -75,10 +75,12 @@ export enum FileKind {
7575
TypeScriptHostFile = 1,
7676
}
7777

78-
export interface VirtualFile {
79-
fileName: string,
80-
snapshot: ts.IScriptSnapshot,
81-
languageId: string,
78+
export interface SourceFile extends BaesFile {
79+
root?: VirtualFile;
80+
language?: Language;
81+
}
82+
83+
export interface VirtualFile extends BaesFile {
8284
kind: FileKind,
8385
capabilities: FileCapabilities,
8486
mappings: Mapping<FileRangeCapabilities>[],
@@ -87,11 +89,24 @@ export interface VirtualFile {
8789
embeddedFiles: VirtualFile[],
8890
}
8991

92+
export interface BaesFile {
93+
/**
94+
* for language-server, kit, monaco, this is uri
95+
*
96+
* for typescript server plugin, tsc, this is fileName
97+
*/
98+
id: string,
99+
languageId: string,
100+
snapshot: ts.IScriptSnapshot,
101+
}
102+
90103
export interface Language<T extends VirtualFile = VirtualFile> {
91-
createVirtualFile(fileName: string, snapshot: ts.IScriptSnapshot, languageId: string): T | undefined;
104+
createVirtualFile(id: string, languageId: string, snapshot: ts.IScriptSnapshot): T | undefined;
92105
updateVirtualFile(virtualFile: T, snapshot: ts.IScriptSnapshot): void;
93106
deleteVirtualFile?(virtualFile: T): void;
94-
resolveTypeScriptProjectHost?<T extends TypeScriptProjectHost>(host: T): T;
107+
typescript?: {
108+
resolveProjectHost?<T extends TypeScriptProjectHost>(host: T): T;
109+
};
95110
}
96111

97112
export interface TypeScriptProjectHost extends Pick<

packages/language-server/lib/project/simpleProject.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ export async function createSimpleServerProject(
1515
let shouldUpdate = true;
1616
let lastSnapshots = new Map<string, ts.IScriptSnapshot | undefined>();
1717

18-
const { uriToFileName } = context.server.runtimeEnv;
1918
const config = await getConfig(context, plugins, serviceEnv, undefined);
2019

2120
context.workspaces.documents.onDidChangeContent(() => {
@@ -55,16 +54,16 @@ export async function createSimpleServerProject(
5554
const newSnapshot = snapshot?.getSnapshot();
5655
if (lastSnapshots.get(uri) !== snapshot) {
5756
if (snapshot && newSnapshot) {
58-
fileProvider.updateSource(uriToFileName(uri), newSnapshot, snapshot.languageId);
57+
fileProvider.updateSourceFile(uri, newSnapshot, snapshot.languageId);
5958
}
6059
else {
61-
fileProvider.deleteSource(uriToFileName(uri));
60+
fileProvider.deleteSourceFile(uri);
6261
}
6362
}
6463
}
6564

6665
for (const uri of remain) {
67-
fileProvider.deleteSource(uriToFileName(uri));
66+
fileProvider.deleteSourceFile(uri);
6867
}
6968

7069
const _newSnapshots = new Map<string, ts.IScriptSnapshot | undefined>();

packages/language-server/lib/project/typescriptProject.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,8 +107,9 @@ export async function createTypeScriptServerProject(
107107
Object.values(config.services ?? {}),
108108
serviceEnv,
109109
createTypeScriptProject(
110-
typescriptProjectHost,
111110
Object.values(config.languages ?? {}),
111+
typescriptProjectHost,
112+
serviceEnv.fileNameToUri,
112113
fileName => context.workspaces.documents.data.pathGet(fileName)?.languageId ?? resolveCommonLanguageId(fileName),
113114
),
114115
);

0 commit comments

Comments
 (0)