Implement global workspace pull

This commit is contained in:
Dirk Baeumer 2021-06-16 14:51:59 +02:00
Родитель 25f9d089dd
Коммит 301224db3c
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: DD95715335E91385
16 изменённых файлов: 375 добавлений и 98 удалений

Просмотреть файл

@ -1,5 +1,9 @@
{
"extends": "../.eslintrc.base.json",
"parserOptions": {
"project": ["src/browser/tsconfig.json", "src/common/tsconfig.json", "src/node/tsconfig.json"]
},
"rules": {
"@typescript-eslint/no-floating-promises": "error"
}
}

Просмотреть файл

@ -294,7 +294,7 @@ class DefaultErrorHandler implements ErrorHandler {
} else {
let diff = this.restarts[this.restarts.length - 1] - this.restarts[0];
if (diff <= 3 * 60 * 1000) {
Window.showErrorMessage(`The ${this.name} server crashed ${this.maxRestartCount+1} times in the last 3 minutes. The server will not be restarted.`);
void Window.showErrorMessage(`The ${this.name} server crashed ${this.maxRestartCount+1} times in the last 3 minutes. The server will not be restarted.`);
return CloseAction.DoNotRestart;
} else {
this.restarts.shift();
@ -1105,13 +1105,13 @@ class DidChangeTextDocumentFeature implements DidChangeTextDocumentFeatureShape
this.forceDelivery();
this._changeDelayer.uri = event.document.uri.toString();
}
this._changeDelayer.delayer.trigger(() => doSend(event));
void this._changeDelayer.delayer.trigger(() => doSend(event));
} else {
this._changeDelayer = {
uri: event.document.uri.toString(),
delayer: new Delayer<void>(200)
};
this._changeDelayer.delayer.trigger(() => doSend(event), -1);
void this._changeDelayer.delayer.trigger(() => doSend(event), -1);
}
};
if (middleware.didChange) {
@ -2992,7 +2992,7 @@ export abstract class BaseLanguageClient {
sendNotification: false,
traceFormat: this._traceFormat
});
});
}, () => this.info(`Setting trace value failed`, undefined, false));
}, () => {
});
}
@ -3045,7 +3045,7 @@ export abstract class BaseLanguageClient {
}
private showNotificationMessage() {
Window.showInformationMessage('A request has failed. See the output for more information.', 'Go to output').then(() => {
void Window.showInformationMessage('A request has failed. See the output for more information.', 'Go to output').then(() => {
this.outputChannel.show(true);
});
}
@ -3119,16 +3119,16 @@ export abstract class BaseLanguageClient {
connection.onShowMessage((message) => {
switch (message.type) {
case MessageType.Error:
Window.showErrorMessage(message.message);
void Window.showErrorMessage(message.message);
break;
case MessageType.Warning:
Window.showWarningMessage(message.message);
void Window.showWarningMessage(message.message);
break;
case MessageType.Info:
Window.showInformationMessage(message.message);
void Window.showInformationMessage(message.message);
break;
default:
Window.showInformationMessage(message.message);
void Window.showInformationMessage(message.message);
}
});
connection.onRequest(ShowMessageRequest.type, (params) => {
@ -3190,11 +3190,13 @@ export abstract class BaseLanguageClient {
this.state = ClientState.StartFailed;
this._onReadyCallbacks.reject(error);
this.error('Starting client failed', error);
Window.showErrorMessage(`Couldn't start client ${this._name}`);
void Window.showErrorMessage(`Couldn't start client ${this._name}`);
});
return new Disposable(() => {
if (this.needsStop()) {
this.stop();
this.stop().then(undefined, (error) => {
this.error(`Stopping server failed.`, error, false);
});
}
});
}
@ -3290,26 +3292,26 @@ export abstract class BaseLanguageClient {
}).then<InitializeResult>(undefined, (error: any) => {
if (this._clientOptions.initializationFailedHandler) {
if (this._clientOptions.initializationFailedHandler(error)) {
this.initialize(connection);
void this.initialize(connection);
} else {
this.stop();
void this.stop();
this._onReadyCallbacks.reject(error);
}
} else if (error instanceof ResponseError && error.data && error.data.retry) {
Window.showErrorMessage(error.message, { title: 'Retry', id: 'retry' }).then(item => {
void Window.showErrorMessage(error.message, { title: 'Retry', id: 'retry' }).then(item => {
if (item && item.id === 'retry') {
this.initialize(connection);
void this.initialize(connection);
} else {
this.stop();
void this.stop();
this._onReadyCallbacks.reject(error);
}
});
} else {
if (error && error.message) {
Window.showErrorMessage(error.message);
void Window.showErrorMessage(error.message);
}
this.error('Server initialization failed.', error);
this.stop();
void this.stop();
this._onReadyCallbacks.reject(error);
}
throw error;
@ -3389,9 +3391,9 @@ export abstract class BaseLanguageClient {
const client = this;
function didChangeWatchedFile(this: void, event: FileEvent) {
client._fileEvents.push(event);
client._fileEventDelayer.trigger(() => {
void client._fileEventDelayer.trigger(() => {
client.onReady().then(() => {
client.resolveConnection().then(connection => {
void client.resolveConnection().then(connection => {
if (client.isConnectionActive()) {
client.forceDocumentSync();
connection.didChangeWatchedFiles({ changes: client._fileEvents });
@ -3497,7 +3499,9 @@ export abstract class BaseLanguageClient {
let action = this._clientOptions.errorHandler!.error(error, message, count);
if (action === ErrorAction.Shutdown) {
this.error('Connection to server is erroring. Shutting down server.');
this.stop();
this.stop().then(undefined, (error) => {
this.error(`Stopping server failed`, error, false);
});
}
}

Просмотреть файл

@ -49,7 +49,7 @@ export class ProgressPart {
private begin(params: WorkDoneProgressBegin): void {
// Since we don't use commands this will be a silent window progress with a hidden notification.
Window.withProgress<void>({ location: ProgressLocation.Window, cancellable: params.cancellable, title: params.title}, async (progress, cancellationToken) => {
void Window.withProgress<void>({ location: ProgressLocation.Window, cancellable: params.cancellable, title: params.title}, async (progress, cancellationToken) => {
this._progress = progress;
this._infinite = params.percentage === undefined;
this._cancellationToken = cancellationToken;

Просмотреть файл

@ -13,9 +13,11 @@ import {
DidSaveTextDocumentNotification, DidCloseTextDocumentNotification, LinkedMap, Touch, RAL
} from 'vscode-languageserver-protocol';
import { generateUuid } from './utils/uuid';
import {
TextDocumentFeature, BaseLanguageClient, Middleware, LSPCancellationError, DiagnosticPullMode
} from './client';
import { PreviousResultId, WorkspaceDocumentDiagnosticReport } from 'vscode-languageserver-protocol/src/common/proposed.diagnostic';
function ensure<T, K extends keyof T>(target: T, key: K): T[K] {
if (target[key] === void 0) {
@ -87,7 +89,7 @@ namespace vscode {
export interface DiagnosticProvider {
onDidChangeDiagnostics: VEvent<void>;
provideDiagnostics(textDocument: TextDocument, previousResultId: string | undefined, token: CancellationToken): ProviderResult<DocumentDiagnosticReport>;
provideWorkspaceDiagnostics?(resultIds: PreviousResultId[], token: CancellationToken, resultReporter: ResultReporter): Promise<void>
provideWorkspaceDiagnostics?(resultIds: PreviousResultId[], token: CancellationToken, resultReporter: ResultReporter): ProviderResult<WorkspaceDiagnosticReport>;
}
}
@ -95,8 +97,13 @@ export interface ProvideDiagnosticSignature {
(this: void, textDocument: TextDocument, previousResultId: string | undefined, token: CancellationToken): ProviderResult<vscode.DocumentDiagnosticReport>;
}
export interface ProvideWorkspaceDiagnosticSignature {
(this: void, resultIds: vscode.PreviousResultId[], token: CancellationToken, resultReporter: vscode.ResultReporter): ProviderResult<vscode.WorkspaceDiagnosticReport>;
}
export interface DiagnosticProviderMiddleware {
provideDiagnostics?: (this: void, document: TextDocument, previousResultId: string | undefined, token: CancellationToken, next: ProvideDiagnosticSignature) => ProviderResult<vscode.DocumentDiagnosticReport>;
provideWorkspaceDiagnostics?: (this: void, resultIds: vscode.PreviousResultId[], token: CancellationToken, resultReporter: vscode.ResultReporter, next: ProvideWorkspaceDiagnosticSignature) => ProviderResult<vscode.WorkspaceDiagnosticReport>;
}
enum RequestStateKind {
@ -154,24 +161,32 @@ interface DocumentPullState {
resultId: string | undefined;
}
enum PullState {
document = 1,
workspace = 2
}
class DocumentPullStateTracker {
private readonly states: Map<string, DocumentPullState>;
private readonly documentPullStates: Map<string, DocumentPullState>;
private readonly workspacePullStates: Map<string, DocumentPullState>;
constructor() {
this.states = new Map();
this.documentPullStates = new Map();
this.workspacePullStates = new Map();
}
public track(textDocument: TextDocument, resultId?: string): DocumentPullState;
public track(uri: string, version?: number, resultId?: string): DocumentPullState;
public track(document: TextDocument | string, arg1?: string | number, arg2?: string): DocumentPullState {
public track(kind: PullState, textDocument: TextDocument, resultId?: string): DocumentPullState;
public track(kind: PullState, uri: string, version?: number, resultId?: string): DocumentPullState;
public track(kind: PullState, document: TextDocument | string, arg1?: string | number, arg2?: string): DocumentPullState {
const states = kind === PullState.document ? this.documentPullStates : this.workspacePullStates;
const [key, uri, version, resultId] = typeof document === 'string'
? [document, Uri.parse(document), arg1 as number, arg2]
: [document.uri.toString(), document.uri, document.version, arg1 as string];
let state = this.states.get(key);
let state = states.get(key);
if (state === undefined) {
state = { document: uri, pulledVersion: version, resultId };
this.states.set(key, state);
states.set(key, state);
} else {
state.pulledVersion = version;
state.resultId = resultId;
@ -179,24 +194,30 @@ class DocumentPullStateTracker {
return state;
}
public unTrack(textDocument: TextDocument): void {
this.states.delete(textDocument.uri.toString());
public unTrack(kind: PullState, textDocument: TextDocument): void {
const states = kind === PullState.document ? this.documentPullStates : this.workspacePullStates;
states.delete(textDocument.uri.toString());
}
public tracks(textDocument: TextDocument): boolean;
public tracks(uri: string): boolean;
public tracks(document: TextDocument | string): boolean {
public tracks(kind: PullState, textDocument: TextDocument): boolean;
public tracks(kind: PullState, uri: string): boolean;
public tracks(kind: PullState, document: TextDocument | string): boolean {
const key = typeof document === 'string' ? document : document.uri.toString();
return this.states.has(key);
const states = kind === PullState.document ? this.documentPullStates : this.workspacePullStates;
return states.has(key);
}
public getResultId(textDocument: TextDocument): string | undefined {
return this.states.get(textDocument.uri.toString())?.resultId;
public getResultId(kind: PullState, textDocument: TextDocument): string | undefined {
const states = kind === PullState.document ? this.documentPullStates : this.workspacePullStates;
return states.get(textDocument.uri.toString())?.resultId;
}
public getAllResultIds(): Proposed.PreviousResultId[] {
const result: Proposed.PreviousResultId[] = [];
for (const [uri, value] of this.states) {
for (let [uri, value] of this.workspacePullStates) {
if (this.documentPullStates.has(uri)) {
value = this.documentPullStates.get(uri)!;
}
if (value.resultId !== undefined) {
result.push({ uri, value: value.resultId });
}
@ -205,8 +226,9 @@ class DocumentPullStateTracker {
}
}
class DiagnosticRequestor {
class DiagnosticRequestor implements Disposable {
private isDisposed: boolean;
private readonly client: BaseLanguageClient;
private readonly editorTracker: EditorTracker;
private readonly options: Proposed.DiagnosticRegistrationOptions;
@ -217,33 +239,50 @@ class DiagnosticRequestor {
private readonly openRequests: Map<string, RequestState>;
private readonly documentStates: DocumentPullStateTracker;
private workspaceErrorCounter: number;
private workspaceCancellation: CancellationTokenSource | undefined;
private workspaceTimeout: Disposable | undefined;
public constructor(client: BaseLanguageClient, editorTracker: EditorTracker, options: Proposed.DiagnosticRegistrationOptions) {
this.client = client;
this.editorTracker = editorTracker;
this.options = options;
this.isDisposed = false;
this.onDidChangeDiagnosticsEmitter = new EventEmitter<void>();
this.provider = this.createProvider();
this.diagnostics = Languages.createDiagnosticCollection(options.identifier);
this.openRequests = new Map();
this.documentStates = new DocumentPullStateTracker();
this.workspaceErrorCounter = 0;
}
public knows(textDocument: TextDocument): boolean {
return this.documentStates.tracks(textDocument);
public knows(kind: PullState, textDocument: TextDocument): boolean {
return this.documentStates.tracks(kind, textDocument);
}
public async pull(textDocument: TextDocument): Promise<void> {
public pull(textDocument: TextDocument, cb?: () => void): void {
this.pullAsync(textDocument).then(() => {
if (cb) {
cb();
}
}, (error) => {
this.client.error(`Document pull failed for text document ${textDocument.uri.toString()}`, error, false);
});
}
private async pullAsync(textDocument: TextDocument): Promise<void> {
const key = textDocument.uri.toString();
const currentRequestState = this.openRequests.get(key);
const documentState = this.documentStates.track(textDocument);
const documentState = this.documentStates.track(PullState.document, textDocument);
if (currentRequestState === undefined) {
const tokenSource = new CancellationTokenSource();
this.openRequests.set(key, { state: RequestStateKind.active, version: textDocument.version, textDocument, tokenSource });
let report: vscode.DocumentDiagnosticReport | undefined;
let afterState: RequestState | undefined;
try {
report = await this.provider.provideDiagnostics(textDocument, this.documentStates.getResultId(textDocument), tokenSource.token) ?? { kind: vscode.DocumentDiagnosticReportKind.full, items: [] };
report = await this.provider.provideDiagnostics(textDocument, this.documentStates.getResultId(PullState.document, textDocument), tokenSource.token) ?? { kind: vscode.DocumentDiagnosticReportKind.full, items: [] };
} catch (error) {
if (error instanceof LSPCancellationError && Proposed.DiagnosticServerCancellationData.is(error.data) && error.data.retriggerRequest === false) {
afterState = { state: RequestStateKind.outDated, textDocument };
@ -263,7 +302,7 @@ class DiagnosticRequestor {
}
this.openRequests.delete(key);
if (!this.editorTracker.isVisible(textDocument)) {
this.documentStates.unTrack(textDocument);
this.documentStates.unTrack(PullState.document, textDocument);
return;
}
if (afterState.state === RequestStateKind.outDated) {
@ -310,8 +349,54 @@ class DiagnosticRequestor {
}
}
pullWorkspace(): void {
public pullWorkspace(): void {
this.pullWorkspaceAsync().then(() => {
this.workspaceTimeout = RAL().timer.setTimeout(() => {
this.pullWorkspace();
}, 2000);
}, (error) => {
if (!(error instanceof LSPCancellationError) && !Proposed.DiagnosticServerCancellationData.is(error.data)) {
this.client.error(`Workspace diagnostic pull failed.`, error, false);
this.workspaceErrorCounter++;
}
if (this.workspaceErrorCounter <= 5) {
this.workspaceTimeout = RAL().timer.setTimeout(() => {
this.pullWorkspace();
}, 2000);
}
});
}
private async pullWorkspaceAsync(): Promise<void> {
if (!this.provider.provideWorkspaceDiagnostics) {
return;
}
if (this.workspaceCancellation !== undefined) {
this.workspaceCancellation.cancel();
this.workspaceCancellation = undefined;
}
this.workspaceCancellation = new CancellationTokenSource();
const previousResultIds: vscode.PreviousResultId[] = this.documentStates.getAllResultIds().map((item) => {
return {
uri: this.client.protocol2CodeConverter.asUri(item.uri),
value: item.value
};
});
await this.provider.provideWorkspaceDiagnostics(previousResultIds, this.workspaceCancellation.token, (chunk) => {
if (!chunk || this.isDisposed) {
return;
}
for (const item of chunk.items) {
if (item.kind === vscode.DocumentDiagnosticReportKind.full) {
// Favour document pull result over workspace results. So skip if it is tracked
// as a document result.
if (!this.documentStates.tracks(PullState.document, item.uri.toString())) {
this.diagnostics.set(item.uri, item.items);
}
}
this.documentStates.track(PullState.workspace, item.uri.toString(), item.version ?? undefined, item.resultId);
}
});
}
private createProvider(): vscode.DiagnosticProvider {
@ -320,11 +405,12 @@ class DiagnosticRequestor {
provideDiagnostics: (textDocument, previousResultId, token) => {
const provideDiagnostics: ProvideDiagnosticSignature = (textDocument, previousResultId, token) => {
const params: Proposed.DocumentDiagnosticParams = {
identifier: this.options.identifier,
textDocument: { uri: this.client.code2ProtocolConverter.asUri(textDocument.uri) },
previousResultId: previousResultId
};
return this.client.sendRequest(Proposed.DocumentDiagnosticRequest.type, params, token).then((result) => {
if (result === undefined || result === null) {
if (result === undefined || result === null || this.isDisposed) {
return { kind: vscode.DocumentDiagnosticReportKind.full, items: [] };
}
if (result.kind === Proposed.DocumentDiagnosticReportKind.full) {
@ -343,9 +429,91 @@ class DiagnosticRequestor {
}
};
if (this.options.workspaceDiagnostics) {
result.provideWorkspaceDiagnostics = (resultIds, token, resultReporter): ProviderResult<vscode.WorkspaceDiagnosticReport> => {
const convertReport = (report: WorkspaceDocumentDiagnosticReport): vscode.WorkspaceDocumentDiagnosticReport => {
if (report.kind === Proposed.DocumentDiagnosticReportKind.full) {
return {
kind: vscode.DocumentDiagnosticReportKind.full,
uri: this.client.protocol2CodeConverter.asUri(report.uri),
resultId: report.resultId,
version: report.version,
items: this.client.protocol2CodeConverter.asDiagnostics(report.items)
};
} else {
return {
kind: vscode.DocumentDiagnosticReportKind.unChanged,
uri: this.client.protocol2CodeConverter.asUri(report.uri),
resultId: report.resultId,
version: report.version
};
}
};
const convertPreviousResultIds = (resultIds: vscode.PreviousResultId[]): PreviousResultId[] => {
const converted: PreviousResultId[] = [];
for (const item of resultIds) {
converted.push({ uri: this.client.code2ProtocolConverter.asUri(item.uri), value: item.value});
}
return converted;
};
const provideDiagnostics: ProvideWorkspaceDiagnosticSignature = (resultIds, token): ProviderResult<vscode.WorkspaceDiagnosticReport> => {
const partialResultToken: string = generateUuid();
const disposable = this.client.onProgress(Proposed.WorkspaceDiagnosticRequest.partialResult, partialResultToken, (partialResult) => {
if (partialResult === undefined || partialResult === null) {
resultReporter(null);
return;
}
const converted: vscode.WorkspaceDiagnosticReportPartialResult = {
items: []
};
for (const item of partialResult.items) {
converted.items.push(convertReport(item));
}
resultReporter(converted);
});
const params: Proposed.WorkspaceDiagnosticParams = {
identifier: this.options.identifier,
previousResultIds: convertPreviousResultIds(resultIds),
partialResultToken: partialResultToken
};
return this.client.sendRequest(Proposed.WorkspaceDiagnosticRequest.type, params, token).then((result): vscode.WorkspaceDiagnosticReport => {
const converted: vscode.WorkspaceDiagnosticReport = {
items: []
};
for (const item of result.items) {
converted.items.push(convertReport(item));
}
disposable.dispose();
resultReporter(converted);
return { items: [] };
}, (error) => {
disposable.dispose();
return this.client.handleFailedRequest(Proposed.DocumentDiagnosticRequest.type, token, error, { items: [] });
});
};
const middleware: Middleware & DiagnosticProviderMiddleware = this.client.clientOptions.middleware!;
return middleware.provideWorkspaceDiagnostics
? middleware.provideWorkspaceDiagnostics(resultIds, token, resultReporter, provideDiagnostics)
: provideDiagnostics(resultIds, token, resultReporter);
};
}
return result;
}
public dispose(): void {
this.isDisposed = true;
// Cancel and clear workspace pull if present.
this.workspaceCancellation?.cancel();
this.workspaceTimeout?.dispose();
// Cancel all request and mark open requests as outdated.
for (const [key, request] of this.openRequests) {
if (request.state === RequestStateKind.active) {
request.tokenSource.cancel();
}
this.openRequests.set(key, { state: RequestStateKind.outDated, textDocument: request.textDocument });
}
}
}
export interface DiagnosticFeatureProvider {
@ -353,7 +521,7 @@ export interface DiagnosticFeatureProvider {
provider: vscode.DiagnosticProvider;
}
class BackgroundScheduler {
class BackgroundScheduler implements Disposable {
private readonly diagnosticRequestor: DiagnosticRequestor;
private endDocument: TextDocument | undefined;
@ -410,6 +578,11 @@ class BackgroundScheduler {
}, 200);
}
public dispose(): void {
this.stop();
this.documents.clear();
}
private stop(): void {
this.intervalHandle?.dispose();
this.intervalHandle = undefined;
@ -460,18 +633,14 @@ class DiagnosticFeatureProviderImpl implements DiagnosticFeatureProvider {
disposables.push(openFeature.onNotificationSent((event) => {
const textDocument = event.original;
if (matches(textDocument)) {
this.diagnosticRequestor.pull(textDocument).then(() => {
addToBackgroundIfNeeded(textDocument);
});
this.diagnosticRequestor.pull(textDocument, () => { addToBackgroundIfNeeded(textDocument); });
}
}));
// Pull all diagnostics for documents that are already open
for (const textDocument of Workspace.textDocuments) {
if (matches(textDocument)) {
this.diagnosticRequestor.pull(textDocument).then(() => {
addToBackgroundIfNeeded(textDocument);
});
this.diagnosticRequestor.pull(textDocument, () => { addToBackgroundIfNeeded(textDocument); });
}
}
@ -479,10 +648,8 @@ class DiagnosticFeatureProviderImpl implements DiagnosticFeatureProvider {
const changeFeature = client.getFeature(DidChangeTextDocumentNotification.method);
disposables.push(changeFeature.onNotificationSent(async (event) => {
const textDocument = event.original.document;
if ((diagnosticPullOptions.filter === undefined || !diagnosticPullOptions.filter(textDocument, DiagnosticPullMode.onType)) && this.diagnosticRequestor.knows(textDocument) && event.original.contentChanges.length > 0) {
this.diagnosticRequestor.pull(textDocument).then(() => {
this.backgroundScheduler.trigger();
});
if ((diagnosticPullOptions.filter === undefined || !diagnosticPullOptions.filter(textDocument, DiagnosticPullMode.onType)) && this.diagnosticRequestor.knows(PullState.document, textDocument) && event.original.contentChanges.length > 0) {
this.diagnosticRequestor.pull(textDocument, () => { this.backgroundScheduler.trigger(); });
}
}));
}
@ -491,10 +658,8 @@ class DiagnosticFeatureProviderImpl implements DiagnosticFeatureProvider {
const saveFeature = client.getFeature(DidSaveTextDocumentNotification.method);
disposables.push(saveFeature.onNotificationSent((event) => {
const textDocument = event.original;
if ((diagnosticPullOptions.filter === undefined || !diagnosticPullOptions.filter(textDocument, DiagnosticPullMode.onSave)) && this.diagnosticRequestor.knows(textDocument)) {
this.diagnosticRequestor.pull(event.original).then(() => {
this.backgroundScheduler.trigger();
});
if ((diagnosticPullOptions.filter === undefined || !diagnosticPullOptions.filter(textDocument, DiagnosticPullMode.onSave)) && this.diagnosticRequestor.knows(PullState.document, textDocument)) {
this.diagnosticRequestor.pull(event.original, () => { this.backgroundScheduler.trigger(); });
}
}));
}
@ -516,7 +681,11 @@ class DiagnosticFeatureProviderImpl implements DiagnosticFeatureProvider {
}
});
this.disposable = Disposable.from(...disposables);
if (options.workspaceDiagnostics === true) {
this.diagnosticRequestor.pullWorkspace();
}
this.disposable = Disposable.from(...disposables, this.backgroundScheduler, this.diagnosticRequestor);
}
public get onDidChangeDiagnosticsEmitter(): EventEmitter<void> {
@ -526,6 +695,8 @@ class DiagnosticFeatureProviderImpl implements DiagnosticFeatureProvider {
public get provider(): vscode.DiagnosticProvider {
return this.diagnosticRequestor.provider;
}
}
export class DiagnosticFeature extends TextDocumentFeature<Proposed.DiagnosticOptions, Proposed.DiagnosticRegistrationOptions, DiagnosticFeatureProvider> {
@ -540,6 +711,10 @@ export class DiagnosticFeature extends TextDocumentFeature<Proposed.DiagnosticOp
public fillClientCapabilities(capabilities: ClientCapabilities & Proposed.$DiagnosticClientCapabilities): void {
let capability = ensure(ensure(capabilities, 'textDocument')!, 'diagnostic')!;
capability.dynamicRegistration = true;
// We first need to decide how a UI will look with related documents.
// An easy implementation would be to only show related diagnostics for
// the active editor.
capability.relatedDocumentSupport = false;
}
public initialize(capabilities: ServerCapabilities & Proposed.$DiagnosticServerCapabilities, documentSelector: DocumentSelector): void {

Просмотреть файл

@ -373,7 +373,7 @@ export class LanguageClient extends CommonLanguageClient {
}
} else {
let pipeName: string | undefined = undefined;
return new Promise<MessageTransports>((resolve, _reject) => {
return new Promise<MessageTransports>((resolve, reject) => {
let args = node.args && node.args.slice() || [];
if (transport === TransportKind.ipc) {
args.push('--node-ipc');
@ -411,8 +411,8 @@ export class LanguageClient extends CommonLanguageClient {
sp.stdout.on('data', data => this.outputChannel.append(Is.string(data) ? data : data.toString(encoding)));
transport.onConnected().then((protocol) => {
resolve({ reader: protocol[0], writer: protocol[1] });
});
});
}, reject);
}, reject);
} else if (Transport.isSocket(transport)) {
createClientSocketTransport(transport.port).then((transport) => {
let sp = cp.fork(node.module, args || [], options);
@ -422,8 +422,8 @@ export class LanguageClient extends CommonLanguageClient {
sp.stdout.on('data', data => this.outputChannel.append(Is.string(data) ? data : data.toString(encoding)));
transport.onConnected().then((protocol) => {
resolve({ reader: protocol[0], writer: protocol[1] });
});
});
}, reject);
}, reject);
}
});
}
@ -529,7 +529,7 @@ export class SettingMonitor {
this.onDidChangeConfiguration();
return new Disposable(() => {
if (this._client.needsStop()) {
this._client.stop();
void this._client.stop();
}
});
}
@ -542,7 +542,7 @@ export class SettingMonitor {
if (enabled && this._client.needsStart()) {
this._client.start();
} else if (!enabled && this._client.needsStop()) {
this._client.stop();
void this._client.stop();
}
}
}

Просмотреть файл

@ -1,6 +1,10 @@
{
"extends": "../.eslintrc.base.json",
"parserOptions": {
"project": ["src/browser/tsconfig.json", "src/common/tsconfig.json", "src/node/tsconfig.json"]
},
"rules": {
"no-console": "error"
"no-console": "error",
"@typescript-eslint/no-floating-promises": "error"
}
}

Просмотреть файл

@ -87,5 +87,6 @@ export namespace Proposed {
export type WorkspaceDocumentDiagnosticReport = diag.WorkspaceDocumentDiagnosticReport;
export type WorkspaceDiagnosticReport = diag.WorkspaceDiagnosticReport;
export type WorkspaceDiagnosticReportPartialResult = diag.WorkspaceDiagnosticReportPartialResult;
export const WorkspaceDiagnosticRequest: typeof diag.WorkspaceDiagnosticRequest = diag.WorkspaceDiagnosticRequest;
export const DiagnosticRefreshRequest: typeof diag.DiagnosticRefreshRequest = diag.DiagnosticRefreshRequest;
}

Просмотреть файл

@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
* ------------------------------------------------------------------------------------------ */
import { RequestHandler0, RequestHandler } from 'vscode-jsonrpc';
import { RequestHandler0, RequestHandler, ProgressType } from 'vscode-jsonrpc';
import { TextDocumentIdentifier, Diagnostic, DocumentUri, integer } from 'vscode-languageserver-types';
import * as Is from './utils/is';
@ -23,6 +23,11 @@ export interface DiagnosticClientCapabilities {
* return value for the corresponding server capability as well.
*/
dynamicRegistration?: boolean;
/**
* Whether the clients supports related documents for document diagnostic pulls.
*/
relatedDocumentSupport?: boolean;
}
export interface $DiagnosticClientCapabilities {
@ -245,6 +250,7 @@ export interface DocumentDiagnosticReportPartialResult {
export namespace DocumentDiagnosticRequest {
export const method: 'textDocument/diagnostic' = 'textDocument/diagnostic';
export const type = new ProtocolRequestType<DocumentDiagnosticParams, DocumentDiagnosticReport, DocumentDiagnosticReportPartialResult, DiagnosticServerCancellationData, DiagnosticRegistrationOptions>(method);
export const partialResult = new ProgressType<DocumentDiagnosticReportPartialResult>();
export type HandlerSignature = RequestHandler<DocumentDiagnosticParams, DocumentDiagnosticReport, void>;
}
@ -356,6 +362,7 @@ export interface WorkspaceDiagnosticReportPartialResult {
export namespace WorkspaceDiagnosticRequest {
export const method: 'workspace/diagnostic' = 'workspace/diagnostic';
export const type = new ProtocolRequestType<WorkspaceDiagnosticParams, WorkspaceDiagnosticReport, WorkspaceDiagnosticReportPartialResult, DiagnosticServerCancellationData, void>(method);
export const partialResult = new ProgressType<WorkspaceDiagnosticReportPartialResult>();
export type HandlerSignature = RequestHandler<WorkspaceDiagnosticParams, WorkspaceDiagnosticReport | null, void>;
}

Просмотреть файл

@ -19,6 +19,11 @@ export interface DiagnosticClientCapabilities {
* return value for the corresponding server capability as well.
*/
dynamicRegistration?: boolean;
/**
* Whether the clients supports related documents for document diagnostic pulls.
*/
relatedDocumentSupport?: boolean;
}
```

Просмотреть файл

@ -21,11 +21,18 @@ export interface DiagnosticsFeatureShape {
refresh(): void;
/**
* Installs a handler for the diagnostic request.
* Installs a handler for the document diagnostic request.
*
* @param handler The corresponding handler.
*/
on(handler: ServerRequestHandler<Proposed.DocumentDiagnosticParams, Proposed.DocumentDiagnosticReport, Proposed.DocumentDiagnosticReportPartialResult, Proposed.DiagnosticServerCancellationData>): void;
/**
* Installs a handler for the workspace diagnostic request.
*
* @param handler The corresponding handler.
*/
onWorkspace(handler: ServerRequestHandler<Proposed.WorkspaceDiagnosticParams, Proposed.WorkspaceDiagnosticReport, Proposed.WorkspaceDiagnosticReportPartialResult, Proposed.DiagnosticServerCancellationData>): void;
}
}
@ -38,7 +45,12 @@ export const DiagnosticFeature: Feature<_Languages, DiagnosticsFeatureShape> = (
},
on: (handler: ServerRequestHandler<Proposed.DocumentDiagnosticParams, Proposed.DocumentDiagnosticReport, Proposed.DocumentDiagnosticReportPartialResult, Proposed.DiagnosticServerCancellationData>): void => {
this.connection.onRequest(Proposed.DocumentDiagnosticRequest.type, (params, cancel) => {
return handler(params, cancel, this.attachWorkDoneProgress(params), undefined);
return handler(params, cancel, this.attachWorkDoneProgress(params), this.attachPartialResultProgress(Proposed.DocumentDiagnosticRequest.partialResult, params));
});
},
onWorkspace: (handler: ServerRequestHandler<Proposed.WorkspaceDiagnosticParams, Proposed.WorkspaceDiagnosticReport, Proposed.WorkspaceDiagnosticReportPartialResult, Proposed.DiagnosticServerCancellationData>): void => {
this.connection.onRequest(Proposed.WorkspaceDiagnosticRequest.type, (params, cancel) => {
return handler(params, cancel, this.attachWorkDoneProgress(params), this.attachPartialResultProgress(Proposed.WorkspaceDiagnosticRequest.partialResult, params));
});
}
};

Просмотреть файл

@ -4,12 +4,9 @@
* ------------------------------------------------------------------------------------------ */
'use strict';
import { promisify } from 'util';
import * as fs from 'fs';
namespace pfs {
export const readFile = promisify(fs.readFile);
}
import * as path from 'path';
import * as _fs from 'fs';
const fs = _fs.promises;
import { URI } from 'vscode-uri';
@ -20,7 +17,8 @@ import {
SignatureHelp, SymbolInformation, SymbolKind, TextDocumentEdit, TextDocuments, TextDocumentSyncKind,
TextEdit, VersionedTextDocumentIdentifier, ProposedFeatures, DiagnosticTag, Proposed, InsertTextFormat,
SelectionRangeRequest, SelectionRange, InsertReplaceEdit, SemanticTokensClientCapabilities, SemanticTokensLegend,
SemanticTokensBuilder, SemanticTokensRegistrationType, SemanticTokensRegistrationOptions, ProtocolNotificationType, ChangeAnnotation, AnnotatedTextEdit, WorkspaceChange,
SemanticTokensBuilder, SemanticTokensRegistrationType, SemanticTokensRegistrationOptions, ProtocolNotificationType, ChangeAnnotation, AnnotatedTextEdit,
WorkspaceChange,
CompletionItemKind, DiagnosticSeverity
} from 'vscode-languageserver/node';
@ -168,7 +166,7 @@ connection.onInitialize((params, cancel, progress): Thenable<InitializeResult> |
diagnosticProvider: {
identifier: 'testbed',
interFileDependencies: true,
workspaceDiagnostics: false
workspaceDiagnostics: true
}
}
};
@ -295,17 +293,7 @@ const patterns = [
/\b[A-Z]{5,}\b/g
];
connection.languages.diagnostics.on(async (param) => {
const uri = URI.parse(param.textDocument.uri);
const document = documents.get(param.textDocument.uri);
const content = document !== undefined
? document.getText()
: uri.scheme === 'file'
? await pfs.readFile(uri.fsPath, { encoding: 'utf8'} )
: undefined;
if (content === undefined) {
return { kind: Proposed.DocumentDiagnosticReportKind.full, items: [] };
}
function computeDiagnostics(content: string): Diagnostic[] {
const result: Diagnostic[] = [];
const lines: string[] = content.match(/^.*(\n|\r\n|\r|$)/gm);
let lineNumber: number = 0;
@ -319,7 +307,56 @@ connection.languages.diagnostics.on(async (param) => {
}
lineNumber++;
}
return { kind: Proposed.DocumentDiagnosticReportKind.full, items: result };
return result;
}
connection.languages.diagnostics.on(async (param) => {
const uri = URI.parse(param.textDocument.uri);
const document = documents.get(param.textDocument.uri);
const content = document !== undefined
? document.getText()
: uri.scheme === 'file'
? await fs.readFile(uri.fsPath, { encoding: 'utf8'} )
: undefined;
if (content === undefined) {
return { kind: Proposed.DocumentDiagnosticReportKind.full, items: [] };
}
return { kind: Proposed.DocumentDiagnosticReportKind.full, items: computeDiagnostics(content) };
});
connection.languages.diagnostics.onWorkspace(async (params, token, _, resultProgress): Promise<Proposed.WorkspaceDiagnosticReport> => {
const fsPath = URI.parse(folder).fsPath;
const toValidate: string[] = [];
for (const child of await fs.readdir(fsPath)) {
if (path.extname(child) === '.bat') {
toValidate.push(path.join(fsPath, child));
}
}
if (toValidate.length === 0) {
return { items: [] };
}
const doValidate = async (index: number) => {
if (index >= toValidate.length) {
index = 0;
}
const diagnostics = computeDiagnostics(await fs.readFile(toValidate[index], { encoding: 'utf8'} ));
resultProgress.report({ items: [
{
kind: Proposed.DocumentDiagnosticReportKind.full,
uri: URI.file(toValidate[index]).toString(),
version: null,
items: diagnostics
}
]});
setTimeout(() => { doValidate(++index); }, 500);
};
doValidate(0);
return new Promise((resolve) => {
setTimeout(resolve, 120000);
});
});
connection.onCompletion((params, token): CompletionItem[] => {

Просмотреть файл

@ -7,4 +7,4 @@ REM .txt extension from . c:\source to c:\textkkk
REM %%f is a variable
FOR %%f IN (*.jpg *.png *.bmp) DO XCOPY C:\source\"%%f" c:\images /m /y
REM This moves any files with a .jpg, .png,
REM or .bmp extension from c:\source to c:\images;;
REM or .bmp extension from c:\source to c:\images;;

Просмотреть файл

@ -0,0 +1,10 @@
REM @ECHO OFF
cd c:\source
REM This is the location of the files that you want to sort
FOR %%f IN (*.doc *.txt) DO XCOPY c:\source\"%%f" c:\text /m /y
REM This moves any files with a .doc or
REM .txt extension from . c:\source to c:\textkkk
REM %%f is a variable
FOR %%f IN (*.jpg *.png *.bmp) DO XCOPY C:\source\"%%f" c:\images /m /y
REM This moves any files with a .jpg, .png,
REM or .bmp extension from c:\source to c:\images;;

Просмотреть файл

@ -0,0 +1,10 @@
REM @ECHO OFF
cd c:\source
REM This is the location of the files that you want to sort
FOR %%f IN (*.doc *.txt) DO XCOPY c:\source\"%%f" c:\text /m /y
REM This moves any files with a .doc or
REM .txt extension from . c:\source to c:\textkkk
REM %%f is a variable
FOR %%f IN (*.jpg *.png *.bmp) DO XCOPY C:\source\"%%f" c:\images /m /y
REM This moves any files with a .jpg, .png,
REM or .bmp extension from c:\source to c:\images;;

Просмотреть файл

@ -1,6 +1,10 @@
{
"extends": "../.eslintrc.base.json",
"parserOptions": {
"project": ["src/tsconfig.json", "src/test/tsconfig.json"]
},
"rules": {
"no-console": "error"
"no-console": "error",
"@typescript-eslint/no-floating-promises": "error"
}
}

Просмотреть файл

@ -1,6 +1,10 @@
{
"extends": "../.eslintrc.base.json",
"parserOptions": {
"project": ["src/tsconfig.json", "src/test/tsconfig.json"]
},
"rules": {
"no-console": "error"
"no-console": "error",
"@typescript-eslint/no-floating-promises": "error"
}
}