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

refactor: URI-specific changes #182

Merged
merged 4 commits into from
May 24, 2024
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
4 changes: 2 additions & 2 deletions packages/eslint/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { TextDocument } from 'vscode-languageserver-textdocument';
const windowsPath = /\\/g;

export function createProcessor(
languagePlugins: LanguagePlugin[],
languagePlugins: LanguagePlugin<string>[],
caseSensitive: boolean,
extensionsMap: Record<string, string> = {
'javascript': '.js',
Expand All @@ -27,7 +27,7 @@ export function createProcessor(
},
supportsAutofix = true,
): Linter.Processor {
const language = createLanguage(languagePlugins, caseSensitive, () => { });
const language = createLanguage<string>(languagePlugins, new FileMap(caseSensitive), () => { });
const documents = new FileMap<{
sourceDocument: TextDocument;
embeddedDocuments: TextDocument[];
Expand Down
101 changes: 69 additions & 32 deletions packages/kit/lib/createChecker.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
import { CodeActionTriggerKind, Diagnostic, DiagnosticSeverity, DidChangeWatchedFilesParams, FileChangeType, LanguagePlugin, NotificationHandler, LanguageServicePlugin, ServiceEnvironment, createLanguageService, mergeWorkspaceEdits, TypeScriptProjectHost } from '@volar/language-service';
import { CodeActionTriggerKind, Diagnostic, DiagnosticSeverity, DidChangeWatchedFilesParams, FileChangeType, LanguagePlugin, NotificationHandler, LanguageServicePlugin, LanguageServiceEnvironment, createLanguageService, mergeWorkspaceEdits, createLanguage, createUriMap } from '@volar/language-service';
import * as path from 'typesafe-path/posix';
import * as ts from 'typescript';
import { TextDocument } from 'vscode-languageserver-textdocument';
import { createServiceEnvironment } from './createServiceEnvironment';
import { asPosix, defaultCompilerOptions, fileNameToUri, uriToFileName } from './utils';
import { createTypeScriptLanguage } from '@volar/typescript';
import { URI } from 'vscode-uri';
import { TypeScriptProjectHost, createLanguageServiceHost, resolveFileLanguageId } from '@volar/typescript';

export function createTypeScriptChecker(
languagePlugins: LanguagePlugin[],
languagePlugins: LanguagePlugin<URI>[],
languageServicePlugins: LanguageServicePlugin[],
tsconfig: string,
) {
const tsconfigPath = asPosix(tsconfig);
return createTypeScriptCheckerWorker(languagePlugins, languageServicePlugins, env => {
return createTypeScriptCheckerWorker(languagePlugins, languageServicePlugins, tsconfigPath, env => {
return createTypeScriptProjectHost(
tsconfigPath,
env,
() => {
const parsed = ts.parseJsonSourceFileConfigFileContent(
Expand All @@ -34,14 +34,13 @@ export function createTypeScriptChecker(
}

export function createTypeScriptInferredChecker(
languagePlugins: LanguagePlugin[],
languagePlugins: LanguagePlugin<URI>[],
languageServicePlugins: LanguageServicePlugin[],
getScriptFileNames: () => string[],
compilerOptions = defaultCompilerOptions
) {
return createTypeScriptCheckerWorker(languagePlugins, languageServicePlugins, env => {
return createTypeScriptCheckerWorker(languagePlugins, languageServicePlugins, undefined, env => {
return createTypeScriptProjectHost(
undefined,
env,
() => ({
options: compilerOptions,
Expand All @@ -51,10 +50,13 @@ export function createTypeScriptInferredChecker(
});
}

const fsFileSnapshots = createUriMap<[number | undefined, ts.IScriptSnapshot | undefined]>();

function createTypeScriptCheckerWorker(
languagePlugins: LanguagePlugin[],
languagePlugins: LanguagePlugin<URI>[],
languageServicePlugins: LanguageServicePlugin[],
getProjectHost: (env: ServiceEnvironment) => TypeScriptProjectHost
configFileName: string | undefined,
getProjectHost: (env: LanguageServiceEnvironment) => TypeScriptProjectHost,
) {

let settings = {};
Expand All @@ -71,11 +73,52 @@ function createTypeScriptCheckerWorker(
};
};

const language = createTypeScriptLanguage(
ts,
languagePlugins,
getProjectHost(env),
const language = createLanguage(
[
...languagePlugins,
{
getLanguageId(uri) {
return resolveFileLanguageId(uri.fsPath);
},
},
],
createUriMap(ts.sys.useCaseSensitiveFileNames),
uri => {
// fs files
const cache = fsFileSnapshots.get(uri);
const fileName = uriToFileName(uri);
const modifiedTime = ts.sys.getModifiedTime?.(fileName)?.valueOf();
if (!cache || cache[0] !== modifiedTime) {
if (ts.sys.fileExists(fileName)) {
const text = ts.sys.readFile(fileName);
const snapshot = text !== undefined ? ts.ScriptSnapshot.fromString(text) : undefined;
fsFileSnapshots.set(uri, [modifiedTime, snapshot]);
}
else {
fsFileSnapshots.set(uri, [modifiedTime, undefined]);
}
}
const snapshot = fsFileSnapshots.get(uri)?.[1];
if (snapshot) {
language.scripts.set(uri, snapshot);
}
else {
language.scripts.delete(uri);
}
},
);
language.typescript = {
configFileName,
asFileName: uriToFileName,
asScriptId: fileNameToUri,
...createLanguageServiceHost(
ts,
ts.sys,
language,
fileNameToUri,
getProjectHost(env),
),
};
const service = createLanguageService(
language,
languageServicePlugins,
Expand Down Expand Up @@ -112,7 +155,7 @@ function createTypeScriptCheckerWorker(
function fileEvent(fileName: string, type: FileChangeType) {
fileName = asPosix(fileName);
for (const cb of didChangeWatchedFilesCallbacks) {
cb({ changes: [{ uri: fileNameToUri(fileName), type }] });
cb({ changes: [{ uri: fileNameToUri(fileName).toString(), type }] });
}
}

Expand Down Expand Up @@ -141,21 +184,23 @@ function createTypeScriptCheckerWorker(
for (const uri in rootEdit.changes ?? {}) {
const edits = rootEdit.changes![uri];
if (edits.length) {
const editFile = service.context.language.scripts.get(uri);
const parsedUri = URI.parse(uri);
const editFile = service.context.language.scripts.get(parsedUri);
if (editFile) {
const editDocument = service.context.documents.get(uri, editFile.languageId, editFile.snapshot);
const editDocument = service.context.documents.get(parsedUri, editFile.languageId, editFile.snapshot);
const newString = TextDocument.applyEdits(editDocument, edits);
await writeFile(uriToFileName(uri), newString);
await writeFile(uriToFileName(parsedUri), newString);
}
}
}
for (const change of rootEdit.documentChanges ?? []) {
if ('textDocument' in change) {
const editFile = service.context.language.scripts.get(change.textDocument.uri);
const changeUri = URI.parse(change.textDocument.uri);
const editFile = service.context.language.scripts.get(changeUri);
if (editFile) {
const editDocument = service.context.documents.get(change.textDocument.uri, editFile.languageId, editFile.snapshot);
const editDocument = service.context.documents.get(changeUri, editFile.languageId, editFile.snapshot);
const newString = TextDocument.applyEdits(editDocument, change.edits);
await writeFile(uriToFileName(change.textDocument.uri), newString);
await writeFile(uriToFileName(changeUri), newString);
}
}
// TODO: CreateFile | RenameFile | DeleteFile
Expand Down Expand Up @@ -196,22 +241,15 @@ function createTypeScriptCheckerWorker(
}

function createTypeScriptProjectHost(
configFileName: string | undefined,
env: ServiceEnvironment,
env: LanguageServiceEnvironment,
createParsedCommandLine: () => Pick<ts.ParsedCommandLine, 'options' | 'fileNames'>,
) {

let scriptSnapshotsCache: Map<string, ts.IScriptSnapshot | undefined> = new Map();
let parsedCommandLine = createParsedCommandLine();
let projectVersion = 0;
let shouldCheckRootFiles = false;

const host: TypeScriptProjectHost = {
...ts.sys,
configFileName,
getCurrentDirectory: () => {
return uriToFileName(env.workspaceFolder);
},
getCompilationSettings: () => {
return parsedCommandLine.options;
},
Expand All @@ -235,13 +273,12 @@ function createTypeScriptProjectHost(
}
return scriptSnapshotsCache.get(fileName);
},
fileNameToScriptId: env.typescript!.fileNameToUri,
scriptIdToFileName: env.typescript!.uriToFileName,
};

env.onDidChangeWatchedFiles?.(({ changes }) => {
for (const change of changes) {
const fileName = uriToFileName(change.uri);
const changeUri = URI.parse(change.uri);
const fileName = uriToFileName(changeUri);
if (change.type === 2 satisfies typeof FileChangeType.Changed) {
if (scriptSnapshotsCache.has(fileName)) {
projectVersion++;
Expand Down
10 changes: 5 additions & 5 deletions packages/kit/lib/createFormatter.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import { FormattingOptions, LanguagePlugin, LanguageServicePlugin, createLanguage, createLanguageService } from '@volar/language-service';
import { FormattingOptions, LanguagePlugin, LanguageServicePlugin, createLanguage, createLanguageService, createUriMap } from '@volar/language-service';
import * as ts from 'typescript';
import { TextDocument } from 'vscode-languageserver-textdocument';
import { URI } from 'vscode-uri';
import { createServiceEnvironment } from './createServiceEnvironment';

export function createFormatter(
languages: LanguagePlugin[],
languages: LanguagePlugin<URI>[],
services: LanguageServicePlugin[]
) {

let fakeUri = 'file:///dummy.txt';
let settings = {};

const fakeUri = URI.parse('file:///dummy.txt');
const env = createServiceEnvironment(() => settings);
const language = createLanguage(languages, false, () => { });
const language = createLanguage(languages, createUriMap(false), () => { });
const service = createLanguageService(
language,
services,
Expand Down
25 changes: 9 additions & 16 deletions packages/kit/lib/createServiceEnvironment.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import { FileSystem, FileType, ServiceEnvironment } from '@volar/language-service';
import { FileSystem, FileType, LanguageServiceEnvironment } from '@volar/language-service';
import * as fs from 'fs';
import { URI } from 'vscode-uri';
import { fileNameToUri, uriToFileName } from './utils';

export function createServiceEnvironment(getSettings: () => any): ServiceEnvironment {

export function createServiceEnvironment(getSettings: () => any): LanguageServiceEnvironment {
return {
workspaceFolder: URI.file(process.cwd()).toString(),
workspaceFolder: URI.file(process.cwd()),
getConfiguration(section: string) {
const settings = getSettings();
if (section in settings) {
Expand All @@ -33,18 +31,14 @@ export function createServiceEnvironment(getSettings: () => any): ServiceEnviron
},
fs: nodeFs,
console,
typescript: {
fileNameToUri: fileNameToUri,
uriToFileName: uriToFileName,
},
};
}

const nodeFs: FileSystem = {
stat(uri) {
if (uri.startsWith('file://')) {
if (uri.scheme === 'file') {
try {
const stats = fs.statSync(uriToFileName(uri), { throwIfNoEntry: false });
const stats = fs.statSync(uri.fsPath, { throwIfNoEntry: false });
if (stats) {
return {
type: stats.isFile() ? FileType.File
Expand All @@ -63,20 +57,19 @@ const nodeFs: FileSystem = {
}
},
readFile(uri, encoding) {
if (uri.startsWith('file://')) {
if (uri.scheme === 'file') {
try {
return fs.readFileSync(uriToFileName(uri), { encoding: encoding as 'utf-8' ?? 'utf-8' });
return fs.readFileSync(uri.fsPath, { encoding: encoding as 'utf-8' ?? 'utf-8' });
}
catch {
return undefined;
}
}
},
readDirectory(uri) {
if (uri.startsWith('file://')) {
if (uri.scheme === 'file') {
try {
const dirName = uriToFileName(uri);
const files = fs.readdirSync(dirName, { withFileTypes: true });
const files = fs.readdirSync(uri.fsPath, { withFileTypes: true });
return files.map<[string, FileType]>(file => {
return [file.name, file.isFile() ? FileType.File
: file.isDirectory() ? FileType.Directory
Expand Down
4 changes: 2 additions & 2 deletions packages/kit/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,6 @@ export function asPosix(path: string) {
return path.replace(/\\/g, '/') as path.PosixPath;
}

export const uriToFileName = (uri: string) => URI.parse(uri).fsPath.replace(/\\/g, '/');
export const uriToFileName = (uri: URI) => uri.fsPath.replace(/\\/g, '/');

export const fileNameToUri = (fileName: string) => URI.file(fileName).toString();
export const fileNameToUri = (fileName: string) => URI.file(fileName);
Loading
Loading