diff --git a/package.json b/package.json index 4d72e77d8..3f806d4c5 100644 --- a/package.json +++ b/package.json @@ -995,6 +995,11 @@ "default": false, "description": "(EXPERIMENTAL) Enables support for resolving completion edits asynchronously. This can speed up time to show the completion list, particularly override and partial method completion lists, at the cost of slight delays after inserting a completion item. Most completion items will have no noticeable impact with this feature, but typing immediately after inserting an override or partial method completion, before the insert is completed, can have unpredictable results." }, + "omnisharp.analyzeOpenDocumentsOnly": { + "type": "boolean", + "default": false, + "description": "Only run analyzers against open files when 'enableRoslynAnalyzers' is true" + }, "omnisharp.testRunSettings": { "type": [ "string", diff --git a/src/features/fileOpenCloseProvider.ts b/src/features/fileOpenCloseProvider.ts new file mode 100644 index 000000000..3c258fa35 --- /dev/null +++ b/src/features/fileOpenCloseProvider.ts @@ -0,0 +1,74 @@ +import { IDisposable } from "../Disposable"; +import { OmniSharpServer } from "../omnisharp/server"; +import * as vscode from 'vscode'; +import CompositeDisposable from "../CompositeDisposable"; +import * as serverUtils from '../omnisharp/utils'; +import { isVirtualCSharpDocument } from "./virtualDocumentTracker"; + +export default function fileOpenClose(server: OmniSharpServer): IDisposable { + return new FileOpenCloseProvider(server); +} + +class FileOpenCloseProvider implements IDisposable { + private _server: OmniSharpServer; + private _diagnostics: vscode.DiagnosticCollection; + private _disposable: CompositeDisposable; + + constructor(server: OmniSharpServer) { + this._server = server; + this._diagnostics = vscode.languages.createDiagnosticCollection('csharp'); + + setTimeout(async () => { + for (let editor of vscode.window.visibleTextEditors) { + let document = editor.document; + + await this._onDocumentOpen(document); + } + }, 0); + + this._disposable = new CompositeDisposable(this._diagnostics, + vscode.workspace.onDidOpenTextDocument(this._onDocumentOpen, this), + vscode.workspace.onDidCloseTextDocument(this._onDocumentClose, this), + vscode.window.onDidChangeActiveTextEditor(this._onActiveTextEditorChange, this) + ); + } + + private async _onDocumentOpen(e: vscode.TextDocument) { + if (shouldIgnoreDocument(e)) { + return; + } + + await serverUtils.fileOpen(this._server, { FileName: e.fileName }); + } + + private async _onDocumentClose(e: vscode.TextDocument) { + if (shouldIgnoreDocument(e)) { + return; + } + + await serverUtils.fileClose(this._server, { FileName: e.fileName }); + } + + private async _onActiveTextEditorChange(e: vscode.TextEditor) { + if (shouldIgnoreDocument(e.document)) { + return; + } + + await serverUtils.filesChanged(this._server, [{ FileName: e.document.fileName }]); + } + + dispose = () => this._disposable.dispose(); +} + +function shouldIgnoreDocument(document: vscode.TextDocument) { + if (document.languageId !== 'csharp') { + return true; + } + + if (document.uri.scheme !== 'file' && + !isVirtualCSharpDocument(document)) { + return true; + } + + return false; +} \ No newline at end of file diff --git a/src/observers/OptionChangeObserver.ts b/src/observers/OptionChangeObserver.ts index 3b671490b..52b88a7aa 100644 --- a/src/observers/OptionChangeObserver.ts +++ b/src/observers/OptionChangeObserver.ts @@ -24,6 +24,8 @@ const omniSharpOptions: ReadonlyArray = [ "organizeImportsOnFormat", "enableAsyncCompletion", "useModernNet", + "analyzeOpenDocumentsOnly", + "enableRoslynAnalyzers" ]; function OmniSharpOptionChangeObservable(optionObservable: Observable): Observable { diff --git a/src/omnisharp/extension.ts b/src/omnisharp/extension.ts index 97505e497..d5c5894e8 100644 --- a/src/omnisharp/extension.ts +++ b/src/omnisharp/extension.ts @@ -44,6 +44,7 @@ import SemanticTokensProvider from '../features/semanticTokensProvider'; import SourceGeneratedDocumentProvider from '../features/sourceGeneratedDocumentProvider'; import { getDecompilationAuthorization } from './decompilationPrompt'; import { OmniSharpDotnetResolver } from './OmniSharpDotnetResolver'; +import fileOpenClose from '../features/fileOpenCloseProvider'; export interface ActivationResult { readonly server: OmniSharpServer; @@ -111,6 +112,7 @@ export async function activate(context: vscode.ExtensionContext, packageJSON: an localDisposables.add(forwardChanges(server)); localDisposables.add(trackVirtualDocuments(server, eventStream)); localDisposables.add(vscode.languages.registerFoldingRangeProvider(documentSelector, new StructureProvider(server, languageMiddlewareFeature))); + localDisposables.add(fileOpenClose(server)); const semanticTokensProvider = new SemanticTokensProvider(server, optionProvider, languageMiddlewareFeature); localDisposables.add(vscode.languages.registerDocumentSemanticTokensProvider(documentSelector, semanticTokensProvider, semanticTokensProvider.getLegend())); diff --git a/src/omnisharp/options.ts b/src/omnisharp/options.ts index d43a0e7b0..f262027bb 100644 --- a/src/omnisharp/options.ts +++ b/src/omnisharp/options.ts @@ -34,6 +34,7 @@ export class Options { public enableDecompilationSupport: boolean, public enableImportCompletion: boolean, public enableAsyncCompletion: boolean, + public analyzeOpenDocumentsOnly: boolean, public useSemanticHighlighting: boolean, public razorPluginPath?: string, public defaultLaunchSolution?: string, @@ -82,6 +83,7 @@ export class Options { const enableDecompilationSupport = omnisharpConfig.get('enableDecompilationSupport', false); const enableImportCompletion = omnisharpConfig.get('enableImportCompletion', false); const enableAsyncCompletion = omnisharpConfig.get('enableAsyncCompletion', false); + const analyzeOpenDocumentsOnly = omnisharpConfig.get('analyzeOpenDocumentsOnly', false); const useFormatting = csharpConfig.get('format.enable', true); const organizeImportsOnFormat = omnisharpConfig.get('organizeImportsOnFormat', false); @@ -141,6 +143,7 @@ export class Options { enableDecompilationSupport, enableImportCompletion, enableAsyncCompletion, + analyzeOpenDocumentsOnly, useSemanticHighlighting, razorPluginPath, defaultLaunchSolution, diff --git a/src/omnisharp/protocol.ts b/src/omnisharp/protocol.ts index d95c31ea1..3252ffa5a 100644 --- a/src/omnisharp/protocol.ts +++ b/src/omnisharp/protocol.ts @@ -37,6 +37,8 @@ export module Requests { export const SourceGeneratedFile = '/sourcegeneratedfile'; export const UpdateSourceGeneratedFile = '/updatesourcegeneratedfile'; export const SourceGeneratedFileClosed = '/sourcegeneratedfileclosed'; + export const FileOpen = '/open'; + export const FileClose = '/close'; } export namespace WireProtocol { diff --git a/src/omnisharp/server.ts b/src/omnisharp/server.ts index 7b7de5285..19ad83141 100644 --- a/src/omnisharp/server.ts +++ b/src/omnisharp/server.ts @@ -391,6 +391,10 @@ export class OmniSharpServer { args.push('RoslynExtensionsOptions:EnableAsyncCompletion=true'); } + if (options.analyzeOpenDocumentsOnly === true) { + args.push('RoslynExtensionsOptions:AnalyzeOpenDocumentsOnly=true'); + } + let launchInfo: LaunchInfo; try { launchInfo = await this._omnisharpManager.GetOmniSharpLaunchInfo(this.packageJSON.defaults.omniSharp, options.path, /* useFramework */ !options.useModernNet, serverUrl, latestVersionFileServerPath, installPath, this.extensionPath); diff --git a/src/omnisharp/utils.ts b/src/omnisharp/utils.ts index 8a5186f6b..3b07e55be 100644 --- a/src/omnisharp/utils.ts +++ b/src/omnisharp/utils.ts @@ -193,6 +193,14 @@ export async function getCompletionAfterInsert(server: OmniSharpServer, request: return server.makeRequest(protocol.Requests.CompletionAfterInsert, request); } +export async function fileOpen(server: OmniSharpServer, request: protocol.Request) { + return server.makeRequest(protocol.Requests.FileOpen, request); +} + +export async function fileClose(server: OmniSharpServer, request: protocol.Request) { + return server.makeRequest(protocol.Requests.FileClose, request); +} + export async function isNetCoreProject(project: protocol.MSBuildProject) { return project.TargetFrameworks.find(tf => tf.ShortName.startsWith('netcoreapp') || tf.ShortName.startsWith('netstandard')) !== undefined; } diff --git a/test/unitTests/Fakes/FakeOptions.ts b/test/unitTests/Fakes/FakeOptions.ts index 8e0f43637..7164c6a87 100644 --- a/test/unitTests/Fakes/FakeOptions.ts +++ b/test/unitTests/Fakes/FakeOptions.ts @@ -6,5 +6,5 @@ import { Options } from "../../../src/omnisharp/options"; export function getEmptyOptions(): Options { - return new Options("", false, "", false, "", false, 0, 0, false, false, false, false, false, [], false, false, false, 0, 0, false, false, false, false, false, false, false, false, undefined, "", "", ""); + return new Options("", false, "", false, "", false, 0, 0, false, false, false, false, false, [], false, false, false, 0, 0, false, false, false, false, false, false, false, false, false, undefined, "", "", ""); } diff --git a/test/unitTests/options.test.ts b/test/unitTests/options.test.ts index 06e208abe..357a27211 100644 --- a/test/unitTests/options.test.ts +++ b/test/unitTests/options.test.ts @@ -34,6 +34,7 @@ suite("Options tests", () => { options.enableEditorConfigSupport.should.equal(false); options.enableDecompilationSupport.should.equal(false); options.enableImportCompletion.should.equal(false); + options.analyzeOpenDocumentsOnly.should.equal(false); expect(options.testRunSettings).to.be.undefined; expect(options.defaultLaunchSolution).to.be.undefined; });