diff --git a/package.json b/package.json index 4d72e77d87..3f806d4c54 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 0000000000..3c258fa356 --- /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 3b671490bd..52b88a7aa8 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 97505e4973..d5c5894e8c 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 d43a0e7b0d..f262027bb0 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 d95c31ea1f..3252ffa5a4 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 7b7de52853..19ad83141f 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 8a5186f6b0..3b07e55bee 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 8e0f436370..7164c6a875 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 06e208abe0..357a27211b 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; });