diff --git a/src/features/diagnosticsProvider.ts b/src/features/diagnosticsProvider.ts index 136c99776..8e910173b 100644 --- a/src/features/diagnosticsProvider.ts +++ b/src/features/diagnosticsProvider.ts @@ -140,7 +140,7 @@ class DiagnosticsProvider extends AbstractSupport { // Go ahead and check for diagnostics in the currently visible editors. for (let editor of vscode.window.visibleTextEditors) { let document = editor.document; - if (document.languageId === 'csharp' && document.uri.scheme === 'file') { + if (document.languageId === 'csharp') { this._validateDocument(document); } } @@ -172,7 +172,7 @@ class DiagnosticsProvider extends AbstractSupport { } private _onDocumentAddOrChange(document: vscode.TextDocument): void { - if (document.languageId === 'csharp' && document.uri.scheme === 'file') { + if (document.languageId === 'csharp') { this._validateDocument(document); this._validateProject(); } diff --git a/src/features/virtualDocumentTracker.ts b/src/features/virtualDocumentTracker.ts new file mode 100644 index 000000000..67b680dcf --- /dev/null +++ b/src/features/virtualDocumentTracker.ts @@ -0,0 +1,97 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import {workspace, TextDocument, Uri} from 'vscode'; +import {OmniSharpServer} from '../omnisharp/server'; +import * as serverUtils from '../omnisharp/utils'; +import { FileChangeType } from '../omnisharp/protocol'; +import { IDisposable } from '../Disposable'; +import CompositeDisposable from '../CompositeDisposable'; +import { EventStream } from '../EventStream'; +import { DocumentSynchronizationFailure } from '../omnisharp/loggingEvents'; + +function trackCurrentVirtualDocuments(server: OmniSharpServer, eventStream: EventStream) { + let registration = server.onProjectAdded(async () => { + registration.dispose(); + + for (let i = 0; i < workspace.textDocuments.length; i++) { + let document = workspace.textDocuments[i]; + + if (!shouldIgnoreDocument(document, server)) { + await openVirtualDocument(document, server, eventStream); + } + } + }); +} + +function trackFutureVirtualDocuments(server: OmniSharpServer, eventStream: EventStream): IDisposable { + let onTextDocumentOpen = workspace.onDidOpenTextDocument(async document => { + if (shouldIgnoreDocument(document, server)) { + return; + } + + await openVirtualDocument(document, server, eventStream); + }); + + let onTextDocumentClose = workspace.onDidCloseTextDocument(async document => { + if (shouldIgnoreDocument(document, server)) { + return; + } + + await closeVirtualDocument(document, server, eventStream); + }); + + // We already track text document changes for virtual documents in our change forwarder. + return new CompositeDisposable( + onTextDocumentOpen, + onTextDocumentClose); +} + +function shouldIgnoreDocument(document: TextDocument, server: OmniSharpServer): boolean { + if (document.uri.scheme === 'file' || document.languageId !== 'csharp') { + // We're only interested in non-physical CSharp documents. + return true; + } + + if (!server.isRunning()) { + return true; + } + + return false; +} + +async function openVirtualDocument(document: TextDocument, server: OmniSharpServer, eventStream: EventStream) { + let req = { FileName: document.uri.path, changeType: FileChangeType.Create }; + try { + await serverUtils.filesChanged(server, [req]); + await serverUtils.updateBuffer(server, { Buffer: document.getText(), FileName: document.fileName }); + } + catch (error) { + logSynchronizationFailure(document.uri, error, server, eventStream); + } +} + +async function closeVirtualDocument(document: TextDocument, server: OmniSharpServer, eventStream: EventStream) { + let req = { FileName: document.uri.path, changeType: FileChangeType.Delete }; + try { + await serverUtils.filesChanged(server, [req]); + } + catch (error) { + logSynchronizationFailure(document.uri, error, server, eventStream); + } +} + +function logSynchronizationFailure(uri: Uri, error: any, server: OmniSharpServer, eventStream: EventStream) { + if (server.isRunning()) { + eventStream.post(new DocumentSynchronizationFailure(uri.path, error)); + } +} + +export default function trackVirtualDocuments(server: OmniSharpServer, eventStream: EventStream): IDisposable { + trackCurrentVirtualDocuments(server, eventStream); + let disposable = trackFutureVirtualDocuments(server, eventStream); + + return disposable; +} \ No newline at end of file diff --git a/src/observers/CsharpLoggerObserver.ts b/src/observers/CsharpLoggerObserver.ts index 35fd4922d..3cab0023f 100644 --- a/src/observers/CsharpLoggerObserver.ts +++ b/src/observers/CsharpLoggerObserver.ts @@ -52,6 +52,9 @@ export class CsharpLoggerObserver extends BaseLoggerObserver { case Event.DownloadSizeObtained.name: this.handleDownloadSizeObtained(event); break; + case Event.DocumentSynchronizationFailure.name: + this.handleDocumentSynchronizationFailure(event); + break; case Event.LatestBuildDownloadStart.name: this.logger.appendLine("Getting latest OmniSharp version information"); break; @@ -112,4 +115,8 @@ export class CsharpLoggerObserver extends BaseLoggerObserver { this.logger.appendLine(`Installing package '${event.packageDescription}'`); this.logger.appendLine(); } + + private handleDocumentSynchronizationFailure(event: Event.DocumentSynchronizationFailure) { + this.logger.appendLine(`Failed to synchronize document '${event.documentPath}': ${event.errorMessage}`); + } } \ No newline at end of file diff --git a/src/omnisharp/extension.ts b/src/omnisharp/extension.ts index d434514c0..38d7a476a 100644 --- a/src/omnisharp/extension.ts +++ b/src/omnisharp/extension.ts @@ -34,6 +34,7 @@ import { NetworkSettingsProvider } from '../NetworkSettings'; import CompositeDisposable from '../CompositeDisposable'; import Disposable from '../Disposable'; import OptionProvider from '../observers/OptionProvider'; +import trackVirtualDocuments from '../features/virtualDocumentTracker'; import { StructureProvider } from '../features/structureProvider'; export let omnisharp: OmniSharpServer; @@ -41,7 +42,6 @@ export let omnisharp: OmniSharpServer; export async function activate(context: vscode.ExtensionContext, packageJSON: any, platformInfo: PlatformInformation, provider: NetworkSettingsProvider, eventStream: EventStream, optionProvider: OptionProvider, extensionPath: string) { const documentSelector: vscode.DocumentSelector = { language: 'csharp', - scheme: 'file' // only files from disk }; const options = optionProvider.GetLatestOptions(); @@ -81,6 +81,7 @@ export async function activate(context: vscode.ExtensionContext, packageJSON: an localDisposables.add(vscode.languages.registerCodeActionsProvider(documentSelector, codeActionProvider)); localDisposables.add(reportDiagnostics(server, advisor)); localDisposables.add(forwardChanges(server)); + localDisposables.add(trackVirtualDocuments(server, eventStream)); localDisposables.add(vscode.languages.registerFoldingRangeProvider(documentSelector, new StructureProvider(server))); })); diff --git a/src/omnisharp/loggingEvents.ts b/src/omnisharp/loggingEvents.ts index 13eb6b52c..874bf319c 100644 --- a/src/omnisharp/loggingEvents.ts +++ b/src/omnisharp/loggingEvents.ts @@ -147,6 +147,10 @@ export class DotNetTestsInClassDebugStart implements BaseEvent { constructor(public className: string) { } } +export class DocumentSynchronizationFailure implements BaseEvent { + constructor(public documentPath: string, public errorMessage: string) { } +} + export class DebuggerPrerequisiteFailure extends EventWithMessage { } export class DebuggerPrerequisiteWarning extends EventWithMessage { } export class CommandDotNetRestoreProgress extends EventWithMessage { } diff --git a/test/integrationTests/virtualDocumentTracker.integration.test.ts b/test/integrationTests/virtualDocumentTracker.integration.test.ts new file mode 100644 index 000000000..6a467d405 --- /dev/null +++ b/test/integrationTests/virtualDocumentTracker.integration.test.ts @@ -0,0 +1,61 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; + +import { should, expect } from 'chai'; +import { activateCSharpExtension } from './integrationHelpers'; +import testAssetWorkspace from './testAssets/testAssetWorkspace'; +import { IDisposable } from '../../src/Disposable'; + +const chai = require('chai'); +chai.use(require('chai-arrays')); +chai.use(require('chai-fs')); + +suite(`Virtual Document Tracking ${testAssetWorkspace.description}`, function () { + let virtualScheme: string = "virtual"; + let virtualDocumentRegistration: IDisposable; + + suiteSetup(async function () { + should(); + await testAssetWorkspace.restore(); + await activateCSharpExtension(); + + let virtualCSharpDocumentProvider = new VirtualCSharpDocumentProvider(); + virtualDocumentRegistration = vscode.workspace.registerTextDocumentContentProvider(virtualScheme, virtualCSharpDocumentProvider); + }); + + suiteTeardown(async () => { + await testAssetWorkspace.cleanupWorkspace(); + virtualDocumentRegistration.dispose(); + }); + + test("Virtual documents are operated on.", async () => { + let virtualUri = vscode.Uri.parse(`${virtualScheme}://${testAssetWorkspace.projects[0].projectDirectoryPath}/_virtualFile.cs`); + await vscode.workspace.openTextDocument(virtualUri); + + let position = new vscode.Position(2, 4); + let completionItems = await vscode.commands.executeCommand("vscode.executeCompletionItemProvider", virtualUri, position); + + expect(completionItems.items).to.satisfy(() => { + let item = completionItems.items.find(item => { + return item.label === "while"; + }); + + return item; + }); + }); +}); + +class VirtualCSharpDocumentProvider implements vscode.TextDocumentContentProvider { + onDidChange?: vscode.Event; + + provideTextDocumentContent(uri: vscode.Uri, token: vscode.CancellationToken): vscode.ProviderResult { + return `namespace Test +{ + +}`; + } +} \ No newline at end of file