From ebdacba34274d41169c3e6a2f2abea4a667e2059 Mon Sep 17 00:00:00 2001 From: Piotr Puszkiewicz Date: Mon, 19 Mar 2018 16:57:35 -0700 Subject: [PATCH 01/56] hack --- src/features/status.ts | 4 +-- src/observers/StatusObserver.ts | 63 +++++++++++++++++++++++++++++++++ src/omnisharp/server.ts | 6 +++- 3 files changed, 70 insertions(+), 3 deletions(-) create mode 100644 src/observers/StatusObserver.ts diff --git a/src/features/status.ts b/src/features/status.ts index a5c8d26471..c614476dec 100644 --- a/src/features/status.ts +++ b/src/features/status.ts @@ -44,7 +44,7 @@ class Status { } } -export function reportDocumentStatus(server: OmniSharpServer): vscode.Disposable { +function reportDocumentStatus(server: OmniSharpServer): vscode.Disposable { let disposables: vscode.Disposable[] = []; let localDisposables: vscode.Disposable[]; @@ -208,7 +208,7 @@ export function reportDocumentStatus(server: OmniSharpServer): vscode.Disposable // ---- server status -export function reportServerStatus(server: OmniSharpServer, eventStream: EventStream): vscode.Disposable{ +function reportServerStatus(server: OmniSharpServer, eventStream: EventStream): vscode.Disposable{ let d0 = server.onServerError(err => { diff --git a/src/observers/StatusObserver.ts b/src/observers/StatusObserver.ts new file mode 100644 index 0000000000..be196df39a --- /dev/null +++ b/src/observers/StatusObserver.ts @@ -0,0 +1,63 @@ +/*--------------------------------------------------------------------------------------------- + * 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 '../vscodeAdapter'; +import { BaseEvent, OmnisharpServerOnError } from "../omnisharp/loggingEvents"; + +export class DotNetChannelObserver { + constructor(statusBarItem: vscode.statusBarItem){ + + } + + public post = (event: BaseEvent) => { + switch (event.constructor.name) { + case OmnisharpServerOnError.name: + this.render({ + text: '$(flame) Error starting OmniSharp', + command: 'o.showOutput', + color: '' + }); + break; + } + } + + private render(defaultStatus: any) { + if (!vscode.window.activeTextEditor) { + this.hide(); + return; + } + + let document = vscode.window.activeTextEditor.document; + let status: Status; + + if (projectStatus && vscode.languages.match(projectStatus.selector, document)) { + status = projectStatus; + } else if (defaultStatus.text && vscode.languages.match(defaultStatus.selector, document)) { + status = defaultStatus; + } + + if (status) { + this.text = status.text; + this.command = status.command; + this.color = status.color; + this.show(); + return; + } + + this.hide(); + } + + private text: string; + private command: string; + private color: string; + + private show() { + + } + + private hide() { + + } +} \ No newline at end of file diff --git a/src/omnisharp/server.ts b/src/omnisharp/server.ts index 2baf353582..5e3a3d90f2 100644 --- a/src/omnisharp/server.ts +++ b/src/omnisharp/server.ts @@ -20,7 +20,7 @@ import { PlatformInformation } from '../platform'; import { launchOmniSharp } from './launcher'; import { setTimeout } from 'timers'; import { OmnisharpDownloader } from './OmnisharpDownloader'; -import { OmnisharpDelayTrackerEventMeasures, OmnisharpFailure, OmnisharpInitialisation, OmnisharpLaunch, OmnisharpServerMessage, OmnisharpServerVerboseMessage, OmnisharpEventPacketReceived, OmnisharpRequestMessage } from './loggingEvents'; +import { OmnisharpDelayTrackerEventMeasures, OmnisharpFailure, OmnisharpInitialisation, OmnisharpLaunch, OmnisharpServerMessage, OmnisharpServerVerboseMessage, OmnisharpEventPacketReceived, OmnisharpRequestMessage, OmnisharpServerOnError } from './loggingEvents'; import { EventStream } from '../EventStream'; enum ServerState { @@ -90,6 +90,10 @@ export class OmniSharpServer { this._requestQueue = new RequestQueueCollection(this.eventStream, 8, request => this._makeRequest(request)); let downloader = new OmnisharpDownloader(this.eventStream, packageJSON, platformInfo); this._omnisharpManager = new OmnisharpManager(downloader, platformInfo); + + this.onServerError((err: any) => { + this.eventStream.post(new OmnisharpServerOnError(err)); + }); } public isRunning(): boolean { From 955a63481be8a46182bb2f3185e20f4477343f14 Mon Sep 17 00:00:00 2001 From: Akshita Agarwal Date: Tue, 20 Mar 2018 18:13:33 -0700 Subject: [PATCH 02/56] Refactored status into 4 separate observers --- src/features/status.ts | 106 +----- src/main.ts | 16 + src/observers/DefaultStatusObserver.ts | 53 +++ src/observers/InformationMessageObserver.ts | 48 +++ src/observers/OmnisharpLoggerObserver.ts | 6 +- src/observers/ProjectStatusObserver.ts | 18 + src/observers/ServerStatusObserver.ts | 54 +++ src/observers/StatusObserver.ts | 63 ---- src/observers/status.ts | 44 +++ src/omnisharp/loggingEvents.ts | 72 ++-- src/omnisharp/server.ts | 36 +- src/vscodeAdapter.ts | 334 ++++++++++++++++++ .../logging/OmnisharpLoggerObserver.test.ts | 7 +- 13 files changed, 661 insertions(+), 196 deletions(-) create mode 100644 src/observers/DefaultStatusObserver.ts create mode 100644 src/observers/InformationMessageObserver.ts create mode 100644 src/observers/ProjectStatusObserver.ts create mode 100644 src/observers/ServerStatusObserver.ts delete mode 100644 src/observers/StatusObserver.ts create mode 100644 src/observers/status.ts diff --git a/src/features/status.ts b/src/features/status.ts index c614476dec..bbdc1b6512 100644 --- a/src/features/status.ts +++ b/src/features/status.ts @@ -16,41 +16,16 @@ const debounce = require('lodash.debounce'); export default function reportStatus(server: OmniSharpServer, eventStream: EventStream) { return vscode.Disposable.from( - reportServerStatus(server, eventStream), - forwardOutput(server, eventStream), reportDocumentStatus(server)); } -// --- document status - -let defaultSelector: vscode.DocumentSelector = [ - 'csharp', // c#-files OR - { pattern: '**/project.json' }, // project.json-files OR - { pattern: '**/*.sln' }, // any solution file OR - { pattern: '**/*.csproj' }, // an csproj file - { pattern: '**/*.csx' }, // C# script - { pattern: '**/*.cake' } // Cake script -]; - -class Status { - - selector: vscode.DocumentSelector; - text: string; - command: string; - color: string; - - constructor(selector: vscode.DocumentSelector) { - this.selector = selector; - } -} - function reportDocumentStatus(server: OmniSharpServer): vscode.Disposable { let disposables: vscode.Disposable[] = []; let localDisposables: vscode.Disposable[]; let entry = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, Number.MIN_VALUE); - let defaultStatus = new Status(defaultSelector); + let projectStatus: Status; function render() { @@ -82,33 +57,34 @@ function reportDocumentStatus(server: OmniSharpServer): vscode.Disposable { disposables.push(vscode.window.onDidChangeActiveTextEditor(render)); - disposables.push(server.onServerError(err => { + + /*disposables.push(server.onServerError(err => { defaultStatus.text = '$(flame) Error starting OmniSharp'; defaultStatus.command = 'o.showOutput'; defaultStatus.color = ''; render(); - })); + }));*/ - disposables.push(server.onMultipleLaunchTargets(targets => { + /* disposables.push(server.onMultipleLaunchTargets(targets => { defaultStatus.text = '$(flame) Select project'; defaultStatus.command = 'o.pickProjectAndStart'; defaultStatus.color = 'rgb(90, 218, 90)'; render(); - })); + }));*/ - disposables.push(server.onBeforeServerInstall(() => { + /*disposables.push(server.onBeforeServerInstall(() => { defaultStatus.text = '$(flame) Installing OmniSharp...'; defaultStatus.command = 'o.showOutput'; defaultStatus.color = ''; render(); })); - disposables.push(server.onBeforeServerStart(path => { + /*disposables.push(server.onBeforeServerStart(path => { defaultStatus.text = '$(flame) Starting...'; defaultStatus.command = 'o.showOutput'; defaultStatus.color = ''; render(); - })); + }));*/ disposables.push(server.onServerStop(() => { projectStatus = undefined; @@ -203,68 +179,4 @@ function reportDocumentStatus(server: OmniSharpServer): vscode.Disposable { })); return vscode.Disposable.from(...disposables); -} - - -// ---- server status - -function reportServerStatus(server: OmniSharpServer, eventStream: EventStream): vscode.Disposable{ - - - let d0 = server.onServerError(err => { - eventStream.post(new OmnisharpServerOnServerError('[ERROR] ' + err)); - }); - - let d1 = server.onError(message => { - eventStream.post(new OmnisharpServerOnError(message)); - - showMessageSoon(); - }); - - let d2 = server.onMsBuildProjectDiagnostics(message => { - eventStream.post(new OmnisharpServerMsBuildProjectDiagnostics(message)); - - if (message.Errors.length > 0) { - showMessageSoon(); - } - }); - - let d3 = server.onUnresolvedDependencies(message => { - eventStream.post(new OmnisharpServerUnresolvedDependencies(message)); - - let csharpConfig = vscode.workspace.getConfiguration('csharp'); - if (!csharpConfig.get('suppressDotnetRestoreNotification')) { - let info = `There are unresolved dependencies from '${vscode.workspace.asRelativePath(message.FileName) }'. Please execute the restore command to continue.`; - - return vscode.window.showInformationMessage(info, 'Restore').then(value => { - if (value) { - dotnetRestoreForProject(server, message.FileName, eventStream); - } - }); - } - }); - - return vscode.Disposable.from(d0, d1, d2, d3); -} - -// show user message -let _messageHandle: NodeJS.Timer; -function showMessageSoon() { - clearTimeout(_messageHandle); - _messageHandle = setTimeout(function() { - - let message = "Some projects have trouble loading. Please review the output for more details."; - vscode.window.showWarningMessage(message, { title: "Show Output", command: 'o.showOutput' }).then(value => { - if (value) { - vscode.commands.executeCommand(value.command); - } - }); - }, 1500); -} - -// --- mirror output in channel - -function forwardOutput(server: OmniSharpServer, eventStream: EventStream) { - return vscode.Disposable.from( - server.onStderr(message => eventStream.post(new OmnisharpServerOnStdErr(message)))); } \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index 48418d5e31..c7d9bf55de 100644 --- a/src/main.ts +++ b/src/main.ts @@ -21,6 +21,8 @@ import { DotnetLoggerObserver } from './observers/DotnetLoggerObserver'; import { OmnisharpDebugModeLoggerObserver } from './observers/OmnisharpDebugModeLoggerObserver'; import { ActivationFailure } from './omnisharp/loggingEvents'; import { EventStream } from './EventStream'; +import { OmnisharpServerStatusObserver, ExecuteCommand, ShowWarningMessage } from './observers/ServerStatusObserver'; +import { InformationMessageObserver, ShowInformationMessage, GetConfiguration, WorkspaceAsRelativePath } from './observers/InformationMessageObserver'; export async function activate(context: vscode.ExtensionContext): Promise<{ initializationFinished: Promise }> { @@ -44,6 +46,16 @@ export async function activate(context: vscode.ExtensionContext): Promise<{ init let omnisharpLogObserver = new OmnisharpLoggerObserver(omnisharpChannel); let omnisharpChannelObserver = new OmnisharpChannelObserver(omnisharpChannel); + let entry = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, Number.MIN_VALUE); + let showWarningMessage: ShowWarningMessage = (message: string, ...items: string[]) => vscode.window.showWarningMessage(message, ...items); + let executeCommand: ExecuteCommand = (command: string, ...rest: any[]) => vscode.commands.executeCommand(command, ...rest); + let omnisharpServerStatusObserver = new OmnisharpServerStatusObserver(showWarningMessage, executeCommand); + + let getConfiguration: GetConfiguration = (name: string) => vscode.workspace.getConfiguration(name); + let showInformationMessage: ShowInformationMessage = (message: string, ...items: string[]) => vscode.window.showInformationMessage(message, ...items); + let workspaceAsRelativePath: WorkspaceAsRelativePath = (pathOrUri: string | vscode.Uri, includeWorkspaceFolder?: boolean) => vscode.workspace.asRelativePath(pathOrUri, includeWorkspaceFolder); + let informationMessageObserver = new InformationMessageObserver(getConfiguration, showInformationMessage, workspaceAsRelativePath); + const eventStream = new EventStream(); eventStream.subscribe(dotnetChannelObserver.post); eventStream.subscribe(dotnetLoggerObserver.post); @@ -53,6 +65,10 @@ export async function activate(context: vscode.ExtensionContext): Promise<{ init eventStream.subscribe(omnisharpLogObserver.post); eventStream.subscribe(omnisharpChannelObserver.post); + + eventStream.subscribe(omnisharpServerStatusObserver.post); + eventStream.subscribe(informationMessageObserver.post); + const debugMode = false; if (debugMode) { let omnisharpDebugModeLoggerObserver = new OmnisharpDebugModeLoggerObserver(omnisharpChannel); diff --git a/src/observers/DefaultStatusObserver.ts b/src/observers/DefaultStatusObserver.ts new file mode 100644 index 0000000000..54d7769f7c --- /dev/null +++ b/src/observers/DefaultStatusObserver.ts @@ -0,0 +1,53 @@ +/*--------------------------------------------------------------------------------------------- + * 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 '../vscodeAdapter'; +import * as ObservableEvent from "../omnisharp/loggingEvents"; +import { Status } from './status'; + +let defaultSelector: vscode.DocumentSelector = [ + 'csharp', // c#-files OR + { pattern: '**/project.json' }, // project.json-files OR + { pattern: '**/*.sln' }, // any solution file OR + { pattern: '**/*.csproj' }, // an csproj file + { pattern: '**/*.csx' }, // C# script + { pattern: '**/*.cake' } // Cake script +]; + + +export class DefaultStatusObserver { + private status: Status; + + constructor(private statusBarItem: vscode.StatusBarItem) { + this.status = new Status(defaultSelector); + } + + public post = (event: ObservableEvent.BaseEvent) => { + switch (event.constructor.name) { + case ObservableEvent.OmnisharpServerOnServerError.name: + this.SetStatus('$(flame) Error starting OmniSharp', 'o.showOutput', ''); + render(); + break; + case ObservableEvent.OmnisharpOnMultipleLaunchTargets.name: + this.SetStatus('$(flame) Select project', 'o.pickProjectAndStart', 'rgb(90, 218, 90)'); + render(); + break; + case ObservableEvent.OmnisharpOnBeforeServerInstall.name: + this.SetStatus('$(flame) Installing OmniSharp...', 'o.showOutput', ''); + render(); + break; + case ObservableEvent.OmnisharpOnBeforeServerStart.name: + this.SetStatus('$(flame) Starting...', 'o.showOutput', ''); + render(); + break; + } + } + + private SetStatus(text: string, command: string, color: string) { + this.status.text = text; + this.status.command = command; + this.status.color = color; + } +} \ No newline at end of file diff --git a/src/observers/InformationMessageObserver.ts b/src/observers/InformationMessageObserver.ts new file mode 100644 index 0000000000..2fc2f3cd95 --- /dev/null +++ b/src/observers/InformationMessageObserver.ts @@ -0,0 +1,48 @@ +/*--------------------------------------------------------------------------------------------- + * 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 '../vscodeAdapter'; +import * as ObservableEvent from "../omnisharp/loggingEvents"; +import { dotnetRestoreForProject } from '../features/commands'; + +export interface GetConfiguration { + (value: string): vscode.WorkspaceConfiguration; +} + +export type ShowInformationMessage = + ((message: string, ...items: string[]) => Thenable) | + ((message: string, options: vscode.MessageOptions, ...items: string[]) => Thenable) | + ((message: string, ...items: T[]) => Thenable) | + ((message: string, options: vscode.MessageOptions, ...items: T[]) => Thenable); + +export interface WorkspaceAsRelativePath{ + (pathOrUri: string | Uri, includeWorkspaceFolder?: boolean): string; +} + +export class InformationMessageObserver { + constructor(private getConfiguration: GetConfiguration, private showInformationMessage: ShowInformationMessage, private worksapaceAsRelativePath : WorkspaceAsRelativePath) { + } + + public post = (event: ObservableEvent.BaseEvent) => { + switch (event.constructor.name) { + case ObservableEvent.OmnisharpServerUnresolvedDependencies.name: + this.handleOmnisharpServerUnresolvedDependencies(event); + break; + } + } + + private handleOmnisharpServerUnresolvedDependencies(event: ObservableEvent.OmnisharpServerUnresolvedDependencies) { + let csharpConfig = this.getConfiguration('csharp'); + if (!csharpConfig.get('suppressDotnetRestoreNotification')) { + let info = `There are unresolved dependencies from '${this.worksapaceAsRelativePath(event.unresolvedDependencies.FileName)}'. Please execute the restore command to continue.`; + + return this.showInformationMessage(info, 'Restore').then(value => { + if (value) { + dotnetRestoreForProject(event.server, event.unresolvedDependencies.FileName, event.eventStream); + } + }); + } + } +} diff --git a/src/observers/OmnisharpLoggerObserver.ts b/src/observers/OmnisharpLoggerObserver.ts index 2ccbe6b809..37ffdafc40 100644 --- a/src/observers/OmnisharpLoggerObserver.ts +++ b/src/observers/OmnisharpLoggerObserver.ts @@ -24,7 +24,7 @@ export class OmnisharpLoggerObserver extends BaseLoggerObserver { this.logger.appendLine((event).message); break; case OmnisharpServerOnServerError.name: - this.logger.appendLine((event).message); + this.handleOmnisharpServerOnServerError(event); break; case OmnisharpServerOnError.name: this.handleOmnisharpServerOnError(event); @@ -41,6 +41,10 @@ export class OmnisharpLoggerObserver extends BaseLoggerObserver { } } + private handleOmnisharpServerOnServerError(event: OmnisharpServerOnServerError) { + this.logger.appendLine('[ERROR] ' + event.err); + } + private handleOmnisharpInitialisation(event: OmnisharpInitialisation) { this.logger.appendLine(`Starting OmniSharp server at ${event.timeStamp.toLocaleString()}`); this.logger.increaseIndent(); diff --git a/src/observers/ProjectStatusObserver.ts b/src/observers/ProjectStatusObserver.ts new file mode 100644 index 0000000000..3fcfbbcb5a --- /dev/null +++ b/src/observers/ProjectStatusObserver.ts @@ -0,0 +1,18 @@ +/*--------------------------------------------------------------------------------------------- + * 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 '../vscodeAdapter'; +import * as ObservableEvent from "../omnisharp/loggingEvents"; + +export class ProjectStatusObserver { + + constructor() { + } + + public post = (event: ObservableEvent.BaseEvent) => { + switch (event.constructor.name) { + } + } +} \ No newline at end of file diff --git a/src/observers/ServerStatusObserver.ts b/src/observers/ServerStatusObserver.ts new file mode 100644 index 0000000000..c6163582b0 --- /dev/null +++ b/src/observers/ServerStatusObserver.ts @@ -0,0 +1,54 @@ +/*--------------------------------------------------------------------------------------------- + * 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 '../vscodeAdapter'; +import * as ObservableEvent from "../omnisharp/loggingEvents"; + +export type ShowWarningMessage = + ((message: string, ...items: string[]) => Thenable) | + ((message: string, options: vscode.MessageOptions, ...items: string[]) => Thenable) | + ((message: string, ...items: T[]) => Thenable) | + ((message: string, options: vscode.MessageOptions, ...items: T[])=> Thenable); + +export interface ExecuteCommand{ + (command: string, ...rest: any[]): Thenable; +} + +export class OmnisharpServerStatusObserver { + private _messageHandle: NodeJS.Timer; + + constructor(private showWarningMessage: ShowWarningMessage, private executeCommand: ExecuteCommand) { + } + + public post = (event: ObservableEvent.BaseEvent) => { + switch (event.constructor.name) { + case ObservableEvent.OmnisharpServerOnError.name: + this.showMessageSoon(); + break; + case ObservableEvent.OmnisharpServerMsBuildProjectDiagnostics.name: + this.handleOmnisharpServerMsBuildProjectDiagnostics(event); + break; + } + } + + private handleOmnisharpServerMsBuildProjectDiagnostics(event: ObservableEvent.OmnisharpServerMsBuildProjectDiagnostics) { + if (event.diagnostics.Errors.length > 0) { + this.showMessageSoon(); + } + } + + private showMessageSoon() { + clearTimeout(this._messageHandle); + this._messageHandle = setTimeout(function () { + + let message = "Some projects have trouble loading. Please review the output for more details."; + this.showWarningMessage(message, { title: "Show Output", command: 'o.showOutput' }).then(value => { + if (value) { + this.executeCommand(value.command); + } + }); + }, 1500); + } +} \ No newline at end of file diff --git a/src/observers/StatusObserver.ts b/src/observers/StatusObserver.ts deleted file mode 100644 index be196df39a..0000000000 --- a/src/observers/StatusObserver.ts +++ /dev/null @@ -1,63 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * 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 '../vscodeAdapter'; -import { BaseEvent, OmnisharpServerOnError } from "../omnisharp/loggingEvents"; - -export class DotNetChannelObserver { - constructor(statusBarItem: vscode.statusBarItem){ - - } - - public post = (event: BaseEvent) => { - switch (event.constructor.name) { - case OmnisharpServerOnError.name: - this.render({ - text: '$(flame) Error starting OmniSharp', - command: 'o.showOutput', - color: '' - }); - break; - } - } - - private render(defaultStatus: any) { - if (!vscode.window.activeTextEditor) { - this.hide(); - return; - } - - let document = vscode.window.activeTextEditor.document; - let status: Status; - - if (projectStatus && vscode.languages.match(projectStatus.selector, document)) { - status = projectStatus; - } else if (defaultStatus.text && vscode.languages.match(defaultStatus.selector, document)) { - status = defaultStatus; - } - - if (status) { - this.text = status.text; - this.command = status.command; - this.color = status.color; - this.show(); - return; - } - - this.hide(); - } - - private text: string; - private command: string; - private color: string; - - private show() { - - } - - private hide() { - - } -} \ No newline at end of file diff --git a/src/observers/status.ts b/src/observers/status.ts new file mode 100644 index 0000000000..bda1339386 --- /dev/null +++ b/src/observers/status.ts @@ -0,0 +1,44 @@ +/*--------------------------------------------------------------------------------------------- + * 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 '../vscodeAdapter'; + +export class Status { + + selector: vscode.DocumentSelector; + text: string; + command: string; + color: string; + + constructor(selector: vscode.DocumentSelector) { + this.selector = selector; + } +} + +function render(defaultStatus: Status) { + if (!vscode.window.activeTextEditor) { + this.hide(); + return; + } + + let document = vscode.window.activeTextEditor.document; + let status: Status; + + if (projectStatus && vscode.languages.match(projectStatus.selector, document)) { + status = projectStatus; + } else if (defaultStatus.text && vscode.languages.match(defaultStatus.selector, document)) { + status = defaultStatus; + } + + if (status) { + this.text = status.text; + this.command = status.command; + this.color = status.color; + this.show(); + return; + } + + this.hide(); +} diff --git a/src/omnisharp/loggingEvents.ts b/src/omnisharp/loggingEvents.ts index 9e89be2d66..8c59c20c96 100644 --- a/src/omnisharp/loggingEvents.ts +++ b/src/omnisharp/loggingEvents.ts @@ -6,11 +6,14 @@ import { PlatformInformation } from "../platform"; import { Request } from "./requestQueue"; import * as protocol from './protocol'; +import { OmniSharpServer } from "./server"; +import { EventStream } from "../EventStream"; +import { LaunchTarget } from "./launcher"; -export interface BaseEvent{ +export interface BaseEvent { } -export class TelemetryEventWithMeasures implements BaseEvent{ +export class TelemetryEventWithMeasures implements BaseEvent { constructor(public eventName: string, public measures: { [key: string]: number }) { } } @@ -21,75 +24,83 @@ export class OmnisharpDelayTrackerEventMeasures extends TelemetryEventWithMeasur export class OmnisharpStart extends TelemetryEventWithMeasures { } -export class OmnisharpInitialisation implements BaseEvent{ +export class OmnisharpInitialisation implements BaseEvent { constructor(public timeStamp: Date, public solutionPath: string) { } } -export class OmnisharpLaunch implements BaseEvent{ +export class OmnisharpLaunch implements BaseEvent { constructor(public usingMono: boolean, public command: string, public pid: number) { } } -export class PackageInstallation implements BaseEvent{ +export class PackageInstallation implements BaseEvent { constructor(public packageInfo: string) { } } -export class LogPlatformInfo implements BaseEvent{ +export class LogPlatformInfo implements BaseEvent { constructor(public info: PlatformInformation) { } } -export class InstallationProgress implements BaseEvent{ +export class InstallationProgress implements BaseEvent { constructor(public stage: string, public message: string) { } } -export class InstallationFailure implements BaseEvent{ - constructor(public stage: string,public error: any) { } +export class InstallationFailure implements BaseEvent { + constructor(public stage: string, public error: any) { } } -export class DownloadProgress implements BaseEvent{ +export class DownloadProgress implements BaseEvent { constructor(public downloadPercentage: number) { } } -export class OmnisharpFailure implements BaseEvent{ +export class OmnisharpFailure implements BaseEvent { constructor(public message: string, public error: Error) { } } -export class OmnisharpRequestMessage implements BaseEvent{ +export class OmnisharpRequestMessage implements BaseEvent { constructor(public request: Request, public id: number) { } } -export class TestExecutionCountReport implements BaseEvent{ +export class TestExecutionCountReport implements BaseEvent { constructor(public debugCounts: { [testFrameworkName: string]: number }, public runCounts: { [testFrameworkName: string]: number }) { } } -export class OmnisharpServerOnError implements BaseEvent{ +export class OmnisharpServerOnError implements BaseEvent { constructor(public errorMessage: protocol.ErrorMessage) { } } -export class OmnisharpServerMsBuildProjectDiagnostics implements BaseEvent{ +export class OmnisharpServerMsBuildProjectDiagnostics implements BaseEvent { constructor(public diagnostics: protocol.MSBuildProjectDiagnostics) { } } -export class OmnisharpServerUnresolvedDependencies implements BaseEvent{ - constructor(public unresolvedDependencies: protocol.UnresolvedDependenciesMessage) { } +export class OmnisharpServerUnresolvedDependencies implements BaseEvent { + constructor(public unresolvedDependencies: protocol.UnresolvedDependenciesMessage, public server: OmniSharpServer, public eventStream: EventStream) { } } -export class OmnisharpServerEnqueueRequest implements BaseEvent{ +export class OmnisharpServerEnqueueRequest implements BaseEvent { constructor(public name: string, public command: string) { } } -export class OmnisharpServerDequeueRequest implements BaseEvent{ +export class OmnisharpServerDequeueRequest implements BaseEvent { constructor(public name: string, public command: string, public id: number) { } } -export class OmnisharpServerProcessRequestStart implements BaseEvent{ +export class OmnisharpServerProcessRequestStart implements BaseEvent { constructor(public name: string) { } } -export class OmnisharpEventPacketReceived implements BaseEvent{ +export class OmnisharpEventPacketReceived implements BaseEvent { constructor(public logLevel: string, public name: string, public message: string) { } } -export class EventWithMessage implements BaseEvent{ +export class OmnisharpServerOnServerError implements BaseEvent { + constructor(public err: any) { } +} + +export class OmnisharpOnMultipleLaunchTargets implements BaseEvent { + constructor(private targets: LaunchTarget[]) { } +} + +export class EventWithMessage implements BaseEvent { constructor(public message: string) { } } @@ -103,14 +114,15 @@ export class DownloadSuccess extends EventWithMessage { } export class DownloadFailure extends EventWithMessage { } export class OmnisharpServerOnStdErr extends EventWithMessage { } export class OmnisharpServerMessage extends EventWithMessage { } -export class OmnisharpServerOnServerError extends EventWithMessage { } export class OmnisharpServerVerboseMessage extends EventWithMessage { } -export class ActivationFailure implements BaseEvent{ } -export class CommandShowOutput implements BaseEvent{ } -export class DebuggerNotInstalledFailure implements BaseEvent{ } -export class CommandDotNetRestoreStart implements BaseEvent{ } -export class InstallationSuccess implements BaseEvent{ } -export class OmnisharpServerProcessRequestComplete implements BaseEvent{ } -export class ProjectJsonDeprecatedWarning implements BaseEvent{ } \ No newline at end of file +export class ActivationFailure implements BaseEvent { } +export class CommandShowOutput implements BaseEvent { } +export class DebuggerNotInstalledFailure implements BaseEvent { } +export class CommandDotNetRestoreStart implements BaseEvent { } +export class InstallationSuccess implements BaseEvent { } +export class OmnisharpServerProcessRequestComplete implements BaseEvent { } +export class ProjectJsonDeprecatedWarning implements BaseEvent { } +export class OmnisharpOnBeforeServerStart implements BaseEvent { } +export class OmnisharpOnBeforeServerInstall implements BaseEvent { } \ No newline at end of file diff --git a/src/omnisharp/server.ts b/src/omnisharp/server.ts index 5e3a3d90f2..3df5554aaf 100644 --- a/src/omnisharp/server.ts +++ b/src/omnisharp/server.ts @@ -20,7 +20,7 @@ import { PlatformInformation } from '../platform'; import { launchOmniSharp } from './launcher'; import { setTimeout } from 'timers'; import { OmnisharpDownloader } from './OmnisharpDownloader'; -import { OmnisharpDelayTrackerEventMeasures, OmnisharpFailure, OmnisharpInitialisation, OmnisharpLaunch, OmnisharpServerMessage, OmnisharpServerVerboseMessage, OmnisharpEventPacketReceived, OmnisharpRequestMessage, OmnisharpServerOnError } from './loggingEvents'; +import * as ObservableEvents from './loggingEvents'; import { EventStream } from '../EventStream'; enum ServerState { @@ -91,9 +91,37 @@ export class OmniSharpServer { let downloader = new OmnisharpDownloader(this.eventStream, packageJSON, platformInfo); this._omnisharpManager = new OmnisharpManager(downloader, platformInfo); - this.onServerError((err: any) => { - this.eventStream.post(new OmnisharpServerOnError(err)); - }); + this._disposables.push(this.onServerError(err => + this.eventStream.post(new ObservableEvents.OmnisharpServerOnServerError(err)) + )); + + this._disposables.push(this.onError((message: protocol.ErrorMessage) => + eventStream.post(new ObservableEvents.OmnisharpServerOnError(message)) + )); + + this._disposables.push(this.onMsBuildProjectDiagnostics((message:protocol.MSBuildProjectDiagnostics) => + eventStream.post(new ObservableEvents.OmnisharpServerMsBuildProjectDiagnostics(message)) + )); + + this._disposables.push(this.onUnresolvedDependencies((message: protocol.UnresolvedDependenciesMessage) => + eventStream.post(new ObservableEvents.OmnisharpServerUnresolvedDependencies(message, this, this.eventStream)) + )); + + this._disposables.push(this.onStderr((message:string) => + eventStream.post(new ObservableEvents.OmnisharpServerOnStdErr(message)) + )); + + this._disposables.push(this.onMultipleLaunchTargets((targets: LaunchTarget[]) => + this.eventStream.post(new ObservableEvents.OmnisharpOnMultipleLaunchTargets(targets)) + )); + + this._disposables.push(this.onBeforeServerInstall(() => + this.eventStream.post(new ObservableEvents.OmnisharpOnBeforeServerInstall()) + )); + + this._disposables.push(this.onBeforeServerStart(() => + this.eventStream.post(new ObservableEvents.OmnisharpOnBeforeServerStart()) + )); } public isRunning(): boolean { diff --git a/src/vscodeAdapter.ts b/src/vscodeAdapter.ts index f989ba7980..f796583085 100644 --- a/src/vscodeAdapter.ts +++ b/src/vscodeAdapter.ts @@ -79,3 +79,337 @@ export enum ViewColumn { */ Three = 3 } + +export interface WorkspaceConfiguration { + + /** + * Return a value from this configuration. + * + * @param section Configuration name, supports _dotted_ names. + * @return The value `section` denotes or `undefined`. + */ + get(section: string): T | undefined; + + /** + * Return a value from this configuration. + * + * @param section Configuration name, supports _dotted_ names. + * @param defaultValue A value should be returned when no value could be found, is `undefined`. + * @return The value `section` denotes or the default. + */ + get(section: string, defaultValue: T): T; + + /** + * Check if this configuration has a certain value. + * + * @param section Configuration name, supports _dotted_ names. + * @return `true` if the section doesn't resolve to `undefined`. + */ + has(section: string): boolean; + + /** + * Retrieve all information about a configuration setting. A configuration value + * often consists of a *default* value, a global or installation-wide value, + * a workspace-specific value and a folder-specific value. + * + * The *effective* value (returned by [`get`](#WorkspaceConfiguration.get)) + * is computed like this: `defaultValue` overwritten by `globalValue`, + * `globalValue` overwritten by `workspaceValue`. `workspaceValue` overwritten by `workspaceFolderValue`. + * Refer to [Settings Inheritence](https://code.visualstudio.com/docs/getstarted/settings) + * for more information. + * + * *Note:* The configuration name must denote a leaf in the configuration tree + * (`editor.fontSize` vs `editor`) otherwise no result is returned. + * + * @param section Configuration name, supports _dotted_ names. + * @return Information about a configuration setting or `undefined`. + */ + inspect(section: string): { key: string; defaultValue?: T; globalValue?: T; workspaceValue?: T, workspaceFolderValue?: T } | undefined; + + /** + * Update a configuration value. The updated configuration values are persisted. + * + * A value can be changed in + * + * - [Global configuration](#ConfigurationTarget.Global): Changes the value for all instances of the editor. + * - [Workspace configuration](#ConfigurationTarget.Workspace): Changes the value for current workspace, if available. + * - [Workspace folder configuration](#ConfigurationTarget.WorkspaceFolder): Changes the value for the + * [Workspace folder](#workspace.workspaceFolders) to which the current [configuration](#WorkspaceConfiguration) is scoped to. + * + * *Note 1:* Setting a global value in the presence of a more specific workspace value + * has no observable effect in that workspace, but in others. Setting a workspace value + * in the presence of a more specific folder value has no observable effect for the resources + * under respective [folder](#workspace.workspaceFolders), but in others. Refer to + * [Settings Inheritence](https://code.visualstudio.com/docs/getstarted/settings) for more information. + * + * *Note 2:* To remove a configuration value use `undefined`, like so: `config.update('somekey', undefined)` + * + * Will throw error when + * - Writing a configuration which is not registered. + * - Writing a configuration to workspace or folder target when no workspace is opened + * - Writing a configuration to folder target when there is no folder settings + * - Writing to folder target without passing a resource when getting the configuration (`workspace.getConfiguration(section, resource)`) + * - Writing a window configuration to folder target + * + * @param section Configuration name, supports _dotted_ names. + * @param value The new value. + * @param configurationTarget The [configuration target](#ConfigurationTarget) or a boolean value. + * - If `true` configuration target is `ConfigurationTarget.Global`. + * - If `false` configuration target is `ConfigurationTarget.Workspace`. + * - If `undefined` or `null` configuration target is + * `ConfigurationTarget.WorkspaceFolder` when configuration is resource specific + * `ConfigurationTarget.Workspace` otherwise. + */ + update(section: string, value: any, configurationTarget?: ConfigurationTarget | boolean): Thenable; + + /** + * Readable dictionary that backs this configuration. + */ + readonly [key: string]: any; +} + +export interface StatusBarItem { + + /** + * The alignment of this item. + */ + readonly alignment: StatusBarAlignment; + + /** + * The priority of this item. Higher value means the item should + * be shown more to the left. + */ + readonly priority: number; + + /** + * The text to show for the entry. You can embed icons in the text by leveraging the syntax: + * + * `My text $(icon-name) contains icons like $(icon'name) this one.` + * + * Where the icon-name is taken from the [octicon](https://octicons.github.com) icon set, e.g. + * `light-bulb`, `thumbsup`, `zap` etc. + */ + text: string; + + /** + * The tooltip text when you hover over this entry. + */ + tooltip: string | undefined; + + /** + * The foreground color for this entry. + */ + color: string | ThemeColor | undefined; + + /** + * The identifier of a command to run on click. The command must be + * [known](#commands.getCommands). + */ + command: string | undefined; + + /** + * Shows the entry in the status bar. + */ + show(): void; + + /** + * Hide the entry in the status bar. + */ + hide(): void; + + /** + * Dispose and free associated resources. Call + * [hide](#StatusBarItem.hide). + */ + dispose(): void; +} + +export interface DocumentFilter { + + /** + * A language id, like `typescript`. + */ + language?: string; + + /** + * A Uri [scheme](#Uri.scheme), like `file` or `untitled`. + */ + scheme?: string; + + /** + * A [glob pattern](#GlobPattern) that is matched on the absolute path of the document. Use a [relative pattern](#RelativePattern) + * to filter documents to a [workspace folder](#WorkspaceFolder). + */ + pattern?: GlobPattern; +} + +export class RelativePattern { + + /** + * A base file path to which this pattern will be matched against relatively. + */ + base: string; + + /** + * A file glob pattern like `*.{ts,js}` that will be matched on file paths + * relative to the base path. + * + * Example: Given a base of `/home/work/folder` and a file path of `/home/work/folder/index.js`, + * the file glob pattern will match on `index.js`. + */ + pattern: string; + + /** + * Creates a new relative pattern object with a base path and pattern to match. This pattern + * will be matched on file paths relative to the base path. + * + * @param base A base file path to which this pattern will be matched against relatively. + * @param pattern A file glob pattern like `*.{ts,js}` that will be matched on file paths + * relative to the base path. + */ + constructor(base: WorkspaceFolder | string, pattern: string) +} + +export interface WorkspaceFolder { + + /** + * The associated uri for this workspace folder. + * + * *Note:* The [Uri](#Uri)-type was intentionally chosen such that future releases of the editor can support + * workspace folders that are not stored on the local disk, e.g. `ftp://server/workspaces/foo`. + */ + readonly uri: Uri; + + /** + * The name of this workspace folder. Defaults to + * the basename of its [uri-path](#Uri.path) + */ + readonly name: string; + + /** + * The ordinal number of this workspace folder. + */ + readonly index: number; +} + +export type GlobPattern = string | RelativePattern; + +export type DocumentSelector = string | DocumentFilter | (string | DocumentFilter)[]; + +export interface MessageOptions { + + /** + * Indicates that this message should be modal. + */ + modal?: boolean; +} + +export interface MessageItem { + + /** + * A short title like 'Retry', 'Open Log' etc. + */ + title: string; + + /** + * Indicates that this item replaces the default + * 'Close' action. + */ + isCloseAffordance?: boolean; +} + +export class Uri { + + /** + * Create an URI from a file system path. The [scheme](#Uri.scheme) + * will be `file`. + * + * @param path A file system or UNC path. + * @return A new Uri instance. + */ + static file(path: string): Uri; + + /** + * Create an URI from a string. Will throw if the given value is not + * valid. + * + * @param value The string value of an Uri. + * @return A new Uri instance. + */ + static parse(value: string): Uri; + + /** + * Use the `file` and `parse` factory functions to create new `Uri` objects. + */ + private constructor(scheme: string, authority: string, path: string, query: string, fragment: string); + + /** + * Scheme is the `http` part of `http://www.msft.com/some/path?query#fragment`. + * The part before the first colon. + */ + readonly scheme: string; + + /** + * Authority is the `www.msft.com` part of `http://www.msft.com/some/path?query#fragment`. + * The part between the first double slashes and the next slash. + */ + readonly authority: string; + + /** + * Path is the `/some/path` part of `http://www.msft.com/some/path?query#fragment`. + */ + readonly path: string; + + /** + * Query is the `query` part of `http://www.msft.com/some/path?query#fragment`. + */ + readonly query: string; + + /** + * Fragment is the `fragment` part of `http://www.msft.com/some/path?query#fragment`. + */ + readonly fragment: string; + + /** + * The string representing the corresponding file system path of this Uri. + * + * Will handle UNC paths and normalize windows drive letters to lower-case. Also + * uses the platform specific path separator. Will *not* validate the path for + * invalid characters and semantics. Will *not* look at the scheme of this Uri. + */ + readonly fsPath: string; + + /** + * Derive a new Uri from this Uri. + * + * ```ts + * let file = Uri.parse('before:some/file/path'); + * let other = file.with({ scheme: 'after' }); + * assert.ok(other.toString() === 'after:some/file/path'); + * ``` + * + * @param change An object that describes a change to this Uri. To unset components use `null` or + * the empty string. + * @return A new Uri that reflects the given change. Will return `this` Uri if the change + * is not changing anything. + */ + with(change: { scheme?: string; authority?: string; path?: string; query?: string; fragment?: string }): Uri; + + /** + * Returns a string representation of this Uri. The representation and normalization + * of a URI depends on the scheme. The resulting string can be safely used with + * [Uri.parse](#Uri.parse). + * + * @param skipEncoding Do not percentage-encode the result, defaults to `false`. Note that + * the `#` and `?` characters occuring in the path will always be encoded. + * @returns A string representation of this Uri. + */ + toString(skipEncoding?: boolean): string; + + /** + * Returns a JSON representation of this Uri. + * + * @return An object. + */ + toJSON(): any; +} \ No newline at end of file diff --git a/test/unitTests/logging/OmnisharpLoggerObserver.test.ts b/test/unitTests/logging/OmnisharpLoggerObserver.test.ts index 1d8e20eebb..b0bebb052f 100644 --- a/test/unitTests/logging/OmnisharpLoggerObserver.test.ts +++ b/test/unitTests/logging/OmnisharpLoggerObserver.test.ts @@ -75,7 +75,6 @@ suite("OmnisharpLoggerObserver", () => { [ new OmnisharpServerOnStdErr("on std error message"), new OmnisharpServerMessage("server message"), - new OmnisharpServerOnServerError("on server error message"), ].forEach((event: EventWithMessage) => { test(`${event.constructor.name}: Message is logged`, () => { observer.post(event); @@ -83,6 +82,12 @@ suite("OmnisharpLoggerObserver", () => { }); }); + test(`OmnisharpServerOnServerError: Error is logged`, () => { + let event = new OmnisharpServerOnServerError("on server error message"); + observer.post(event); + expect(logOutput).to.contain(event.err); + }); + [ new OmnisharpInitialisation(new Date(5), "somePath"), ].forEach((event: OmnisharpInitialisation) => { From 1d6934a3f267500d46d1e78a9b38b1308e6f4a66 Mon Sep 17 00:00:00 2001 From: Akshita Agarwal Date: Wed, 21 Mar 2018 11:32:46 -0700 Subject: [PATCH 03/56] Resolved warning and information messages --- src/main.ts | 2 +- src/observers/InformationMessageObserver.ts | 10 ++++------ src/observers/ServerStatusObserver.ts | 8 +++----- 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/src/main.ts b/src/main.ts index c7d9bf55de..928ab1cffe 100644 --- a/src/main.ts +++ b/src/main.ts @@ -53,7 +53,7 @@ export async function activate(context: vscode.ExtensionContext): Promise<{ init let getConfiguration: GetConfiguration = (name: string) => vscode.workspace.getConfiguration(name); let showInformationMessage: ShowInformationMessage = (message: string, ...items: string[]) => vscode.window.showInformationMessage(message, ...items); - let workspaceAsRelativePath: WorkspaceAsRelativePath = (pathOrUri: string | vscode.Uri, includeWorkspaceFolder?: boolean) => vscode.workspace.asRelativePath(pathOrUri, includeWorkspaceFolder); + let workspaceAsRelativePath: WorkspaceAsRelativePath = (path: string, includeWorkspaceFolder?: boolean) => vscode.workspace.asRelativePath(path, includeWorkspaceFolder); let informationMessageObserver = new InformationMessageObserver(getConfiguration, showInformationMessage, workspaceAsRelativePath); const eventStream = new EventStream(); diff --git a/src/observers/InformationMessageObserver.ts b/src/observers/InformationMessageObserver.ts index 2fc2f3cd95..aa6ae5bba6 100644 --- a/src/observers/InformationMessageObserver.ts +++ b/src/observers/InformationMessageObserver.ts @@ -11,14 +11,12 @@ export interface GetConfiguration { (value: string): vscode.WorkspaceConfiguration; } -export type ShowInformationMessage = - ((message: string, ...items: string[]) => Thenable) | - ((message: string, options: vscode.MessageOptions, ...items: string[]) => Thenable) | - ((message: string, ...items: T[]) => Thenable) | - ((message: string, options: vscode.MessageOptions, ...items: T[]) => Thenable); +export interface ShowInformationMessage { + (message: string, ...items: string[]): Thenable; +} export interface WorkspaceAsRelativePath{ - (pathOrUri: string | Uri, includeWorkspaceFolder?: boolean): string; + (path: string, includeWorkspaceFolder?: boolean): string; } export class InformationMessageObserver { diff --git a/src/observers/ServerStatusObserver.ts b/src/observers/ServerStatusObserver.ts index c6163582b0..9e81312031 100644 --- a/src/observers/ServerStatusObserver.ts +++ b/src/observers/ServerStatusObserver.ts @@ -6,11 +6,9 @@ import * as vscode from '../vscodeAdapter'; import * as ObservableEvent from "../omnisharp/loggingEvents"; -export type ShowWarningMessage = - ((message: string, ...items: string[]) => Thenable) | - ((message: string, options: vscode.MessageOptions, ...items: string[]) => Thenable) | - ((message: string, ...items: T[]) => Thenable) | - ((message: string, options: vscode.MessageOptions, ...items: T[])=> Thenable); +export interface ShowWarningMessage{ + (message: string, ...items: string[]): Thenable; +} export interface ExecuteCommand{ (command: string, ...rest: any[]): Thenable; From 980a0c30527b5bf61cec3a99fb90d8da26fe6d7d Mon Sep 17 00:00:00 2001 From: Akshita Agarwal Date: Wed, 21 Mar 2018 14:15:53 -0700 Subject: [PATCH 04/56] Deleted status.ts --- src/features/status.ts | 182 ----------- src/main.ts | 16 +- src/observers/DefaultStatusObserver.ts | 53 ---- ...er.ts => OmnisharpServerStatusObserver.ts} | 4 +- .../OmnisharpStatusBarItemObserver.ts | 186 +++++++++++ src/observers/ProjectStatusObserver.ts | 18 -- src/omnisharp/loggingEvents.ts | 12 +- src/omnisharp/server.ts | 28 +- src/vscodeAdapter.ts | 300 ++++++++++++++---- 9 files changed, 475 insertions(+), 324 deletions(-) delete mode 100644 src/features/status.ts delete mode 100644 src/observers/DefaultStatusObserver.ts rename src/observers/{ServerStatusObserver.ts => OmnisharpServerStatusObserver.ts} (96%) create mode 100644 src/observers/OmnisharpStatusBarItemObserver.ts delete mode 100644 src/observers/ProjectStatusObserver.ts diff --git a/src/features/status.ts b/src/features/status.ts deleted file mode 100644 index bbdc1b6512..0000000000 --- a/src/features/status.ts +++ /dev/null @@ -1,182 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -'use strict'; - -import * as vscode from 'vscode'; -import * as serverUtils from '../omnisharp/utils'; -import {OmniSharpServer} from '../omnisharp/server'; -import {dotnetRestoreForProject} from './commands'; -import {basename} from 'path'; -import { OmnisharpServerOnServerError, OmnisharpServerOnError, OmnisharpServerMsBuildProjectDiagnostics, OmnisharpServerUnresolvedDependencies, OmnisharpServerOnStdErr } from '../omnisharp/loggingEvents'; -import { EventStream } from '../EventStream'; - -const debounce = require('lodash.debounce'); - -export default function reportStatus(server: OmniSharpServer, eventStream: EventStream) { - return vscode.Disposable.from( - reportDocumentStatus(server)); -} - -function reportDocumentStatus(server: OmniSharpServer): vscode.Disposable { - - let disposables: vscode.Disposable[] = []; - let localDisposables: vscode.Disposable[]; - - let entry = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, Number.MIN_VALUE); - - let projectStatus: Status; - - function render() { - - if (!vscode.window.activeTextEditor) { - entry.hide(); - return; - } - - let document = vscode.window.activeTextEditor.document; - let status: Status; - - if (projectStatus && vscode.languages.match(projectStatus.selector, document)) { - status = projectStatus; - } else if (defaultStatus.text && vscode.languages.match(defaultStatus.selector, document)) { - status = defaultStatus; - } - - if (status) { - entry.text = status.text; - entry.command = status.command; - entry.color = status.color; - entry.show(); - return; - } - - entry.hide(); - } - - disposables.push(vscode.window.onDidChangeActiveTextEditor(render)); - - - /*disposables.push(server.onServerError(err => { - defaultStatus.text = '$(flame) Error starting OmniSharp'; - defaultStatus.command = 'o.showOutput'; - defaultStatus.color = ''; - render(); - }));*/ - - /* disposables.push(server.onMultipleLaunchTargets(targets => { - defaultStatus.text = '$(flame) Select project'; - defaultStatus.command = 'o.pickProjectAndStart'; - defaultStatus.color = 'rgb(90, 218, 90)'; - render(); - }));*/ - - /*disposables.push(server.onBeforeServerInstall(() => { - defaultStatus.text = '$(flame) Installing OmniSharp...'; - defaultStatus.command = 'o.showOutput'; - defaultStatus.color = ''; - render(); - })); - - /*disposables.push(server.onBeforeServerStart(path => { - defaultStatus.text = '$(flame) Starting...'; - defaultStatus.command = 'o.showOutput'; - defaultStatus.color = ''; - render(); - }));*/ - - disposables.push(server.onServerStop(() => { - projectStatus = undefined; - defaultStatus.text = undefined; - - if (localDisposables) { - vscode.Disposable.from(...localDisposables).dispose(); - } - - localDisposables = undefined; - })); - - disposables.push(server.onServerStart(path => { - localDisposables = []; - - defaultStatus.text = '$(flame) Running'; - defaultStatus.command = 'o.pickProjectAndStart'; - defaultStatus.color = ''; - render(); - - function updateProjectInfo() { - serverUtils.requestWorkspaceInformation(server).then(info => { - - interface Project { - Path: string; - SourceFiles: string[]; - } - - let fileNames: vscode.DocumentFilter[] = []; - let label: string; - - function addProjectFileNames(project: Project) { - fileNames.push({ pattern: project.Path }); - - if (project.SourceFiles) { - for (let sourceFile of project.SourceFiles) { - fileNames.push({ pattern: sourceFile }); - } - } - } - - function addDnxOrDotNetProjects(projects: Project[]) { - let count = 0; - - for (let project of projects) { - count += 1; - addProjectFileNames(project); - } - - if (!label) { - if (count === 1) { - label = basename(projects[0].Path); //workspace.getRelativePath(info.Dnx.Projects[0].Path); - } - else { - label = `${count} projects`; - } - } - } - - // show sln-file if applicable - if (info.MsBuild && info.MsBuild.SolutionPath) { - label = basename(info.MsBuild.SolutionPath); //workspace.getRelativePath(info.MsBuild.SolutionPath); - fileNames.push({ pattern: info.MsBuild.SolutionPath }); - - for (let project of info.MsBuild.Projects) { - addProjectFileNames(project); - } - } - - // show .NET Core projects if applicable - if (info.DotNet) { - addDnxOrDotNetProjects(info.DotNet.Projects); - } - - // set project info - projectStatus = new Status(fileNames); - projectStatus.text = '$(flame) ' + label; - projectStatus.command = 'o.pickProjectAndStart'; - - // default is to change project - defaultStatus.text = '$(flame) Switch projects'; - defaultStatus.command = 'o.pickProjectAndStart'; - render(); - }); - } - - // Don't allow the same request to slam the server within a "short" window - let debouncedUpdateProjectInfo = debounce(updateProjectInfo, 1500, { leading: true }); - localDisposables.push(server.onProjectAdded(debouncedUpdateProjectInfo)); - localDisposables.push(server.onProjectChange(debouncedUpdateProjectInfo)); - localDisposables.push(server.onProjectRemoved(debouncedUpdateProjectInfo)); - })); - - return vscode.Disposable.from(...disposables); -} \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index 928ab1cffe..6dad77c15b 100644 --- a/src/main.ts +++ b/src/main.ts @@ -19,10 +19,11 @@ import { TelemetryObserver } from './observers/TelemetryObserver'; import { OmnisharpChannelObserver } from './observers/OmnisharpChannelObserver'; import { DotnetLoggerObserver } from './observers/DotnetLoggerObserver'; import { OmnisharpDebugModeLoggerObserver } from './observers/OmnisharpDebugModeLoggerObserver'; -import { ActivationFailure } from './omnisharp/loggingEvents'; +import { ActivationFailure, RenderOmnisharpStatusBarItem } from './omnisharp/loggingEvents'; import { EventStream } from './EventStream'; -import { OmnisharpServerStatusObserver, ExecuteCommand, ShowWarningMessage } from './observers/ServerStatusObserver'; +import { OmnisharpServerStatusObserver, ExecuteCommand, ShowWarningMessage} from './observers/OmnisharpServerStatusObserver'; import { InformationMessageObserver, ShowInformationMessage, GetConfiguration, WorkspaceAsRelativePath } from './observers/InformationMessageObserver'; +import { GetActiveTextEditor, OmnisharpStatusBarItemObserver, Match } from './observers/OmnisharpStatusBarItemObserver'; export async function activate(context: vscode.ExtensionContext): Promise<{ initializationFinished: Promise }> { @@ -46,9 +47,10 @@ export async function activate(context: vscode.ExtensionContext): Promise<{ init let omnisharpLogObserver = new OmnisharpLoggerObserver(omnisharpChannel); let omnisharpChannelObserver = new OmnisharpChannelObserver(omnisharpChannel); - let entry = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, Number.MIN_VALUE); + let showWarningMessage: ShowWarningMessage = (message: string, ...items: string[]) => vscode.window.showWarningMessage(message, ...items); let executeCommand: ExecuteCommand = (command: string, ...rest: any[]) => vscode.commands.executeCommand(command, ...rest); + let omnisharpServerStatusObserver = new OmnisharpServerStatusObserver(showWarningMessage, executeCommand); let getConfiguration: GetConfiguration = (name: string) => vscode.workspace.getConfiguration(name); @@ -56,6 +58,11 @@ export async function activate(context: vscode.ExtensionContext): Promise<{ init let workspaceAsRelativePath: WorkspaceAsRelativePath = (path: string, includeWorkspaceFolder?: boolean) => vscode.workspace.asRelativePath(path, includeWorkspaceFolder); let informationMessageObserver = new InformationMessageObserver(getConfiguration, showInformationMessage, workspaceAsRelativePath); + let omnisharpStatusBar = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, Number.MIN_VALUE); + let getActiveTextEditor: GetActiveTextEditor = () => vscode.window.activeTextEditor; + let match: Match = (selector: vscode.DocumentSelector, document: vscode.TextDocument) => vscode.languages.match(selector, document); + let omnisharpStatusBarObserver = new OmnisharpStatusBarItemObserver(getActiveTextEditor, match, omnisharpStatusBar); + const eventStream = new EventStream(); eventStream.subscribe(dotnetChannelObserver.post); eventStream.subscribe(dotnetLoggerObserver.post); @@ -93,6 +100,9 @@ export async function activate(context: vscode.ExtensionContext): Promise<{ init // register JSON completion & hover providers for project.json context.subscriptions.push(addJSONProviders()); + context.subscriptions.push(vscode.window.onDidChangeActiveTextEditor(() => { + eventStream.post(new RenderOmnisharpStatusBarItem()); + })); let coreClrDebugPromise = Promise.resolve(); if (runtimeDependenciesExist) { diff --git a/src/observers/DefaultStatusObserver.ts b/src/observers/DefaultStatusObserver.ts deleted file mode 100644 index 54d7769f7c..0000000000 --- a/src/observers/DefaultStatusObserver.ts +++ /dev/null @@ -1,53 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * 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 '../vscodeAdapter'; -import * as ObservableEvent from "../omnisharp/loggingEvents"; -import { Status } from './status'; - -let defaultSelector: vscode.DocumentSelector = [ - 'csharp', // c#-files OR - { pattern: '**/project.json' }, // project.json-files OR - { pattern: '**/*.sln' }, // any solution file OR - { pattern: '**/*.csproj' }, // an csproj file - { pattern: '**/*.csx' }, // C# script - { pattern: '**/*.cake' } // Cake script -]; - - -export class DefaultStatusObserver { - private status: Status; - - constructor(private statusBarItem: vscode.StatusBarItem) { - this.status = new Status(defaultSelector); - } - - public post = (event: ObservableEvent.BaseEvent) => { - switch (event.constructor.name) { - case ObservableEvent.OmnisharpServerOnServerError.name: - this.SetStatus('$(flame) Error starting OmniSharp', 'o.showOutput', ''); - render(); - break; - case ObservableEvent.OmnisharpOnMultipleLaunchTargets.name: - this.SetStatus('$(flame) Select project', 'o.pickProjectAndStart', 'rgb(90, 218, 90)'); - render(); - break; - case ObservableEvent.OmnisharpOnBeforeServerInstall.name: - this.SetStatus('$(flame) Installing OmniSharp...', 'o.showOutput', ''); - render(); - break; - case ObservableEvent.OmnisharpOnBeforeServerStart.name: - this.SetStatus('$(flame) Starting...', 'o.showOutput', ''); - render(); - break; - } - } - - private SetStatus(text: string, command: string, color: string) { - this.status.text = text; - this.status.command = command; - this.status.color = color; - } -} \ No newline at end of file diff --git a/src/observers/ServerStatusObserver.ts b/src/observers/OmnisharpServerStatusObserver.ts similarity index 96% rename from src/observers/ServerStatusObserver.ts rename to src/observers/OmnisharpServerStatusObserver.ts index 9e81312031..64c93d03ba 100644 --- a/src/observers/ServerStatusObserver.ts +++ b/src/observers/OmnisharpServerStatusObserver.ts @@ -6,11 +6,11 @@ import * as vscode from '../vscodeAdapter'; import * as ObservableEvent from "../omnisharp/loggingEvents"; -export interface ShowWarningMessage{ +export interface ShowWarningMessage { (message: string, ...items: string[]): Thenable; } -export interface ExecuteCommand{ +export interface ExecuteCommand { (command: string, ...rest: any[]): Thenable; } diff --git a/src/observers/OmnisharpStatusBarItemObserver.ts b/src/observers/OmnisharpStatusBarItemObserver.ts new file mode 100644 index 0000000000..0e6662f3cb --- /dev/null +++ b/src/observers/OmnisharpStatusBarItemObserver.ts @@ -0,0 +1,186 @@ +/*--------------------------------------------------------------------------------------------- + * 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 '../vscodeAdapter'; +import * as ObservableEvent from "../omnisharp/loggingEvents"; +import { Status } from './status'; +import * as serverUtils from '../omnisharp/utils'; +import { basename } from 'path'; + +const debounce = require('lodash.debounce'); + +let defaultSelector: vscode.DocumentSelector = [ + 'csharp', // c#-files OR + { pattern: '**/project.json' }, // project.json-files OR + { pattern: '**/*.sln' }, // any solution file OR + { pattern: '**/*.csproj' }, // an csproj file + { pattern: '**/*.csx' }, // C# script + { pattern: '**/*.cake' } // Cake script +]; + + +export interface GetActiveTextEditor { + (): vscode.TextEditor; +} + +export interface Match { + (selector: vscode.DocumentSelector, document: vscode.TextDocument): number; +} + +export class OmnisharpStatusBarItemObserver { + private defaultStatus: Status; + private projectStatus: Status; + private localDisposables: vscode.Disposable[]; + + constructor(private getActiveTextEditor: GetActiveTextEditor, private match: Match, private statusBar: vscode.StatusBarItem) { + this.defaultStatus = new Status(defaultSelector); + } + + public post = (event: ObservableEvent.BaseEvent) => { + switch (event.constructor.name) { + case ObservableEvent.OmnisharpServerOnServerError.name: + SetStatus(this.defaultStatus, '$(flame) Error starting OmniSharp', 'o.showOutput', ''); + this.render(); + break; + case ObservableEvent.OmnisharpOnMultipleLaunchTargets.name: + SetStatus(this.defaultStatus, '$(flame) Select project', 'o.pickProjectAndStart', 'rgb(90, 218, 90)'); + this.render(); + break; + case ObservableEvent.OmnisharpOnBeforeServerInstall.name: + SetStatus(this.defaultStatus, '$(flame) Installing OmniSharp...', 'o.showOutput', ''); + this.render(); + break; + case ObservableEvent.OmnisharpOnBeforeServerStart.name: + SetStatus(this.defaultStatus, '$(flame) Starting...', 'o.showOutput', ''); + this.render(); + break; + case ObservableEvent.RenderOmnisharpStatusBarItem.name: + this.render(); + break; + case ObservableEvent.OmnisharpServerOnStop.name: + this.handleOmnisharpServerOnStop(); + break; + case ObservableEvent.OmnisharpServerOnStart.name: + this.handleOmnisharpServerOnStart(event); + break; + } + } + + private updateProjectInfo = () => { + serverUtils.requestWorkspaceInformation(server).then(info => { + + interface Project { + Path: string; + SourceFiles: string[]; + } + + let fileNames: vscode.DocumentFilter[] = []; + let label: string; + + function addProjectFileNames(project: Project) { + fileNames.push({ pattern: project.Path }); + + if (project.SourceFiles) { + for (let sourceFile of project.SourceFiles) { + fileNames.push({ pattern: sourceFile }); + } + } + } + + function addDnxOrDotNetProjects(projects: Project[]) { + let count = 0; + + for (let project of projects) { + count += 1; + addProjectFileNames(project); + } + + if (!label) { + if (count === 1) { + label = basename(projects[0].Path); //workspace.getRelativePath(info.Dnx.Projects[0].Path); + } + else { + label = `${count} projects`; + } + } + } + + // show sln-file if applicable + if (info.MsBuild && info.MsBuild.SolutionPath) { + label = basename(info.MsBuild.SolutionPath); //workspace.getRelativePath(info.MsBuild.SolutionPath); + fileNames.push({ pattern: info.MsBuild.SolutionPath }); + + for (let project of info.MsBuild.Projects) { + addProjectFileNames(project); + } + } + + // show .NET Core projects if applicable + if (info.DotNet) { + addDnxOrDotNetProjects(info.DotNet.Projects); + } + + // set project info + this.projectStatus = new Status(fileNames); + SetStatus(this.projectStatus, '$(flame) ' + label, 'o.pickProjectAndStart'); + SetStatus(this.defaultStatus, '$(flame) Switch projects', 'o.pickProjectAndStart'); + this.render(); + }); + } + + private render = () => { + let activeTextEditor = this.getActiveTextEditor(); + if (!activeTextEditor) { + this.statusBar.hide(); + return; + } + + let document = activeTextEditor.document; + let status: Status; + + if (this.projectStatus && this.match(this.projectStatus.selector, document)) { + status = this.projectStatus; + } else if (this.projectStatus.text && this.match(this.projectStatus.selector, document)) { + status = this.projectStatus; + } + + if (status) { + this.statusBar.text = status.text; + this.statusBar.command = status.command; + this.statusBar.color = status.color; + this.statusBar.show(); + return; + } + + this.statusBar.hide(); + } + + private handleOmnisharpServerOnStop() { + this.projectStatus = undefined; + this.defaultStatus.text = undefined; + + if (this.localDisposables) { + vscode.Disposable.from(...this.localDisposables).dispose(); + } + + this.localDisposables = undefined; + } + + private handleOmnisharpServerOnStart(event:ObservableEvent.OmnisharpServerOnStart) { + this.localDisposables = []; + SetStatus(this.defaultStatus, '$(flame) Running', 'o.pickProjectAndStart', ''); + this.render(); + let debouncedUpdateProjectInfo = debounce(this.updateProjectInfo, 1500, { leading: true }); + this.localDisposables.push(event.server.onProjectAdded(debouncedUpdateProjectInfo)); + this.localDisposables.push(event.server.onProjectChange(debouncedUpdateProjectInfo)); + this.localDisposables.push(event.server.onProjectRemoved(debouncedUpdateProjectInfo)); + } +} + +function SetStatus(status: Status, text: string, command: string, color?: string) { + status.text = text; + status.command = command; + status.color = color; +} \ No newline at end of file diff --git a/src/observers/ProjectStatusObserver.ts b/src/observers/ProjectStatusObserver.ts deleted file mode 100644 index 3fcfbbcb5a..0000000000 --- a/src/observers/ProjectStatusObserver.ts +++ /dev/null @@ -1,18 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * 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 '../vscodeAdapter'; -import * as ObservableEvent from "../omnisharp/loggingEvents"; - -export class ProjectStatusObserver { - - constructor() { - } - - public post = (event: ObservableEvent.BaseEvent) => { - switch (event.constructor.name) { - } - } -} \ No newline at end of file diff --git a/src/omnisharp/loggingEvents.ts b/src/omnisharp/loggingEvents.ts index 8c59c20c96..636becf531 100644 --- a/src/omnisharp/loggingEvents.ts +++ b/src/omnisharp/loggingEvents.ts @@ -6,6 +6,7 @@ import { PlatformInformation } from "../platform"; import { Request } from "./requestQueue"; import * as protocol from './protocol'; +import * as vscode from '../vscodeAdapter'; import { OmniSharpServer } from "./server"; import { EventStream } from "../EventStream"; import { LaunchTarget } from "./launcher"; @@ -97,13 +98,17 @@ export class OmnisharpServerOnServerError implements BaseEvent { } export class OmnisharpOnMultipleLaunchTargets implements BaseEvent { - constructor(private targets: LaunchTarget[]) { } + constructor(public targets: LaunchTarget[]) { } } export class EventWithMessage implements BaseEvent { constructor(public message: string) { } } +export class OmnisharpServerOnStart implements BaseEvent { + constructor(public server: OmniSharpServer) { } +} + export class DebuggerPrerequisiteFailure extends EventWithMessage { } export class DebuggerPrerequisiteWarning extends EventWithMessage { } export class CommandDotNetRestoreProgress extends EventWithMessage { } @@ -116,7 +121,6 @@ export class OmnisharpServerOnStdErr extends EventWithMessage { } export class OmnisharpServerMessage extends EventWithMessage { } export class OmnisharpServerVerboseMessage extends EventWithMessage { } - export class ActivationFailure implements BaseEvent { } export class CommandShowOutput implements BaseEvent { } export class DebuggerNotInstalledFailure implements BaseEvent { } @@ -125,4 +129,6 @@ export class InstallationSuccess implements BaseEvent { } export class OmnisharpServerProcessRequestComplete implements BaseEvent { } export class ProjectJsonDeprecatedWarning implements BaseEvent { } export class OmnisharpOnBeforeServerStart implements BaseEvent { } -export class OmnisharpOnBeforeServerInstall implements BaseEvent { } \ No newline at end of file +export class OmnisharpOnBeforeServerInstall implements BaseEvent { } +export class RenderOmnisharpStatusBarItem implements BaseEvent { } +export class OmnisharpServerOnStop implements BaseEvent { } \ No newline at end of file diff --git a/src/omnisharp/server.ts b/src/omnisharp/server.ts index 3df5554aaf..c2de893144 100644 --- a/src/omnisharp/server.ts +++ b/src/omnisharp/server.ts @@ -122,6 +122,14 @@ export class OmniSharpServer { this._disposables.push(this.onBeforeServerStart(() => this.eventStream.post(new ObservableEvents.OmnisharpOnBeforeServerStart()) )); + + this._disposables.push(this.onServerStop(() => + this.eventStream.post(new ObservableEvents.OmnisharpServerOnStop()) + )); + + this._disposables.push(this.onServerStart(() => + this.eventStream.post(new ObservableEvents.OmnisharpServerOnStart(this)) + )); } public isRunning(): boolean { @@ -166,7 +174,7 @@ export class OmniSharpServer { const measures = tracker.getMeasures(); tracker.clearMeasures(); - this.eventStream.post(new OmnisharpDelayTrackerEventMeasures(eventName, measures)); + this.eventStream.post(new ObservableEvents.OmnisharpDelayTrackerEventMeasures(eventName, measures)); } } } @@ -290,16 +298,16 @@ export class OmniSharpServer { launchPath = await this._omnisharpManager.GetOmnisharpPath(this._options.path, this._options.useMono, serverUrl, latestVersionFileServerPath, installPath, extensionPath); } catch (error) { - this.eventStream.post(new OmnisharpFailure(`Error occured in loading omnisharp from omnisharp.path\nCould not start the server due to ${error.toString()}`, error)); + this.eventStream.post(new ObservableEvents.OmnisharpFailure(`Error occured in loading omnisharp from omnisharp.path\nCould not start the server due to ${error.toString()}`, error)); return; } } - this.eventStream.post(new OmnisharpInitialisation(new Date(), solutionPath)); + this.eventStream.post(new ObservableEvents.OmnisharpInitialisation(new Date(), solutionPath)); this._fireEvent(Events.BeforeServerStart, solutionPath); return launchOmniSharp(cwd, args, launchPath).then(value => { - this.eventStream.post(new OmnisharpLaunch(value.usingMono, value.command, value.process.pid)); + this.eventStream.post(new ObservableEvents.OmnisharpLaunch(value.usingMono, value.command, value.process.pid)); this._serverProcess = value.process; this._delayTrackers = {}; @@ -513,7 +521,7 @@ export class OmniSharpServer { line = line.trim(); if (line[0] !== '{') { - this.eventStream.post(new OmnisharpServerMessage(line)); + this.eventStream.post(new ObservableEvents.OmnisharpServerMessage(line)); return; } @@ -539,7 +547,7 @@ export class OmniSharpServer { this._handleEventPacket(packet); break; default: - this.eventStream.post(new OmnisharpServerMessage(`Unknown packet type: ${packet.Type}`)); + this.eventStream.post(new ObservableEvents.OmnisharpServerMessage(`Unknown packet type: ${packet.Type}`)); break; } } @@ -548,11 +556,11 @@ export class OmniSharpServer { const request = this._requestQueue.dequeue(packet.Command, packet.Request_seq); if (!request) { - this.eventStream.post(new OmnisharpServerMessage(`Received response for ${packet.Command} but could not find request.`)); + this.eventStream.post(new ObservableEvents.OmnisharpServerMessage(`Received response for ${packet.Command} but could not find request.`)); return; } - this.eventStream.post(new OmnisharpServerVerboseMessage(`handleResponse: ${packet.Command} (${packet.Request_seq})`)); + this.eventStream.post(new ObservableEvents.OmnisharpServerVerboseMessage(`handleResponse: ${packet.Command} (${packet.Request_seq})`)); if (packet.Success) { request.onSuccess(packet.Body); @@ -567,7 +575,7 @@ export class OmniSharpServer { private _handleEventPacket(packet: protocol.WireProtocol.EventPacket): void { if (packet.Event === 'log') { const entry = <{ LogLevel: string; Name: string; Message: string; }>packet.Body; - this.eventStream.post(new OmnisharpEventPacketReceived(entry.LogLevel, entry.Name, entry.Message)); + this.eventStream.post(new ObservableEvents.OmnisharpEventPacketReceived(entry.LogLevel, entry.Name, entry.Message)); } else { // fwd all other events @@ -585,7 +593,7 @@ export class OmniSharpServer { Arguments: request.data }; - this.eventStream.post(new OmnisharpRequestMessage(request, id)); + this.eventStream.post(new ObservableEvents.OmnisharpRequestMessage(request, id)); this._serverProcess.stdin.write(JSON.stringify(requestPacket) + '\n'); return id; } diff --git a/src/vscodeAdapter.ts b/src/vscodeAdapter.ts index f796583085..dd0784ff2b 100644 --- a/src/vscodeAdapter.ts +++ b/src/vscodeAdapter.ts @@ -168,6 +168,52 @@ export interface WorkspaceConfiguration { readonly [key: string]: any; } +/** + * The configuration target + */ +export enum ConfigurationTarget { + /** + * Global configuration + */ + Global = 1, + + /** + * Workspace configuration + */ + Workspace = 2, + + /** + * Workspace folder configuration + */ + WorkspaceFolder = 3 +} + +/** + * Represents the alignment of status bar items. + */ +export enum StatusBarAlignment { + + /** + * Aligned to the left side. + */ + Left = 1, + + /** + * Aligned to the right side. + */ + Right = 2 +} + +export interface ThemeColor { + + /** + * Creates a reference to a theme color. + * @param id of the color. The available colors are listed in https://code.visualstudio.com/docs/getstarted/theme-color-reference. + */ + constructor(id: string); +} + + export interface StatusBarItem { /** @@ -243,7 +289,7 @@ export interface DocumentFilter { pattern?: GlobPattern; } -export class RelativePattern { +export interface RelativePattern { /** * A base file path to which this pattern will be matched against relatively. @@ -267,7 +313,7 @@ export class RelativePattern { * @param pattern A file glob pattern like `*.{ts,js}` that will be matched on file paths * relative to the base path. */ - constructor(base: WorkspaceFolder | string, pattern: string) + constructor(base: WorkspaceFolder | string, pattern: string); } export interface WorkspaceFolder { @@ -318,98 +364,246 @@ export interface MessageItem { isCloseAffordance?: boolean; } -export class Uri { + +/** + * Represents an editor that is attached to a [document](#TextDocument). + */ +export interface TextEditor { + + /** + * The document associated with this text editor. The document will be the same for the entire lifetime of this text editor. + */ + document: TextDocument; + + /** + * The primary selection on this text editor. Shorthand for `TextEditor.selections[0]`. + */ + selection: Selection; + + /** + * The selections in this text editor. The primary selection is always at index 0. + */ + selections: Selection[]; + + /** + * Text editor options. + */ + options: TextEditorOptions; + + /** + * The column in which this editor shows. Will be `undefined` in case this + * isn't one of the three main editors, e.g an embedded editor. + */ + viewColumn?: ViewColumn; + + /** + * Perform an edit on the document associated with this text editor. + * + * The given callback-function is invoked with an [edit-builder](#TextEditorEdit) which must + * be used to make edits. Note that the edit-builder is only valid while the + * callback executes. + * + * @param callback A function which can create edits using an [edit-builder](#TextEditorEdit). + * @param options The undo/redo behavior around this edit. By default, undo stops will be created before and after this edit. + * @return A promise that resolves with a value indicating if the edits could be applied. + */ + edit(callback: (editBuilder: TextEditorEdit) => void, options?: { undoStopBefore: boolean; undoStopAfter: boolean; }): Thenable; + + /** + * Insert a [snippet](#SnippetString) and put the editor into snippet mode. "Snippet mode" + * means the editor adds placeholders and additionals cursors so that the user can complete + * or accept the snippet. + * + * @param snippet The snippet to insert in this edit. + * @param location Position or range at which to insert the snippet, defaults to the current editor selection or selections. + * @param options The undo/redo behavior around this edit. By default, undo stops will be created before and after this edit. + * @return A promise that resolves with a value indicating if the snippet could be inserted. Note that the promise does not signal + * that the snippet is completely filled-in or accepted. + */ + insertSnippet(snippet: SnippetString, location?: Position | Range | Position[] | Range[], options?: { undoStopBefore: boolean; undoStopAfter: boolean; }): Thenable; + + /** + * Adds a set of decorations to the text editor. If a set of decorations already exists with + * the given [decoration type](#TextEditorDecorationType), they will be replaced. + * + * @see [createTextEditorDecorationType](#window.createTextEditorDecorationType). + * + * @param decorationType A decoration type. + * @param rangesOrOptions Either [ranges](#Range) or more detailed [options](#DecorationOptions). + */ + setDecorations(decorationType: TextEditorDecorationType, rangesOrOptions: Range[] | DecorationOptions[]): void; + + /** + * Scroll as indicated by `revealType` in order to reveal the given range. + * + * @param range A range. + * @param revealType The scrolling strategy for revealing `range`. + */ + revealRange(range: Range, revealType?: TextEditorRevealType): void; /** - * Create an URI from a file system path. The [scheme](#Uri.scheme) - * will be `file`. + * ~~Show the text editor.~~ + * + * @deprecated Use [window.showTextDocument](#window.showTextDocument) * - * @param path A file system or UNC path. - * @return A new Uri instance. + * @param column The [column](#ViewColumn) in which to show this editor. + * instead. This method shows unexpected behavior and will be removed in the next major update. */ - static file(path: string): Uri; + show(column?: ViewColumn): void; /** - * Create an URI from a string. Will throw if the given value is not - * valid. + * ~~Hide the text editor.~~ * - * @param value The string value of an Uri. - * @return A new Uri instance. + * @deprecated Use the command `workbench.action.closeActiveEditor` instead. + * This method shows unexpected behavior and will be removed in the next major update. */ - static parse(value: string): Uri; + hide(): void; +} +export interface TextDocument { + + /** + * The associated URI for this document. Most documents have the __file__-scheme, indicating that they + * represent files on disk. However, some documents may have other schemes indicating that they are not + * available on disk. + */ + readonly uri: Uri; /** - * Use the `file` and `parse` factory functions to create new `Uri` objects. + * The file system path of the associated resource. Shorthand + * notation for [TextDocument.uri.fsPath](#TextDocument.uri). Independent of the uri scheme. */ - private constructor(scheme: string, authority: string, path: string, query: string, fragment: string); + readonly fileName: string; /** - * Scheme is the `http` part of `http://www.msft.com/some/path?query#fragment`. - * The part before the first colon. + * Is this document representing an untitled file. */ - readonly scheme: string; + readonly isUntitled: boolean; /** - * Authority is the `www.msft.com` part of `http://www.msft.com/some/path?query#fragment`. - * The part between the first double slashes and the next slash. + * The identifier of the language associated with this document. */ - readonly authority: string; + readonly languageId: string; /** - * Path is the `/some/path` part of `http://www.msft.com/some/path?query#fragment`. + * The version number of this document (it will strictly increase after each + * change, including undo/redo). */ - readonly path: string; + readonly version: number; /** - * Query is the `query` part of `http://www.msft.com/some/path?query#fragment`. + * `true` if there are unpersisted changes. */ - readonly query: string; + readonly isDirty: boolean; /** - * Fragment is the `fragment` part of `http://www.msft.com/some/path?query#fragment`. + * `true` if the document have been closed. A closed document isn't synchronized anymore + * and won't be re-used when the same resource is opened again. */ - readonly fragment: string; + readonly isClosed: boolean; /** - * The string representing the corresponding file system path of this Uri. + * Save the underlying file. * - * Will handle UNC paths and normalize windows drive letters to lower-case. Also - * uses the platform specific path separator. Will *not* validate the path for - * invalid characters and semantics. Will *not* look at the scheme of this Uri. + * @return A promise that will resolve to true when the file + * has been saved. If the file was not dirty or the save failed, + * will return false. + */ + save(): Thenable; + + /** + * The [end of line](#EndOfLine) sequence that is predominately + * used in this document. + */ + readonly eol: EndOfLine; + + /** + * The number of lines in this document. */ - readonly fsPath: string; + readonly lineCount: number; /** - * Derive a new Uri from this Uri. + * Returns a text line denoted by the line number. Note + * that the returned object is *not* live and changes to the + * document are not reflected. + * + * @param line A line number in [0, lineCount). + * @return A [line](#TextLine). + */ + lineAt(line: number): TextLine; + + /** + * Returns a text line denoted by the position. Note + * that the returned object is *not* live and changes to the + * document are not reflected. + * + * The position will be [adjusted](#TextDocument.validatePosition). + * + * @see [TextDocument.lineAt](#TextDocument.lineAt) + * @param position A position. + * @return A [line](#TextLine). + */ + lineAt(position: Position): TextLine; + + /** + * Converts the position to a zero-based offset. + * + * The position will be [adjusted](#TextDocument.validatePosition). + * + * @param position A position. + * @return A valid zero-based offset. + */ + offsetAt(position: Position): number; + + /** + * Converts a zero-based offset to a position. + * + * @param offset A zero-based offset. + * @return A valid [position](#Position). + */ + positionAt(offset: number): Position; + + /** + * Get the text of this document. A substring can be retrieved by providing + * a range. The range will be [adjusted](#TextDocument.validateRange). + * + * @param range Include only the text included by the range. + * @return The text inside the provided range or the entire text. + */ + getText(range?: Range): string; + + /** + * Get a word-range at the given position. By default words are defined by + * common separators, like space, -, _, etc. In addition, per languge custom + * [word definitions](#LanguageConfiguration.wordPattern) can be defined. It + * is also possible to provide a custom regular expression. + * + * * *Note 1:* A custom regular expression must not match the empty string and + * if it does, it will be ignored. + * * *Note 2:* A custom regular expression will fail to match multiline strings + * and in the name of speed regular expressions should not match words with + * spaces. Use [`TextLine.text`](#TextLine.text) for more complex, non-wordy, scenarios. * - * ```ts - * let file = Uri.parse('before:some/file/path'); - * let other = file.with({ scheme: 'after' }); - * assert.ok(other.toString() === 'after:some/file/path'); - * ``` + * The position will be [adjusted](#TextDocument.validatePosition). * - * @param change An object that describes a change to this Uri. To unset components use `null` or - * the empty string. - * @return A new Uri that reflects the given change. Will return `this` Uri if the change - * is not changing anything. + * @param position A position. + * @param regex Optional regular expression that describes what a word is. + * @return A range spanning a word, or `undefined`. */ - with(change: { scheme?: string; authority?: string; path?: string; query?: string; fragment?: string }): Uri; + getWordRangeAtPosition(position: Position, regex?: RegExp): Range | undefined; /** - * Returns a string representation of this Uri. The representation and normalization - * of a URI depends on the scheme. The resulting string can be safely used with - * [Uri.parse](#Uri.parse). + * Ensure a range is completely contained in this document. * - * @param skipEncoding Do not percentage-encode the result, defaults to `false`. Note that - * the `#` and `?` characters occuring in the path will always be encoded. - * @returns A string representation of this Uri. + * @param range A range. + * @return The given range or a new, adjusted range. */ - toString(skipEncoding?: boolean): string; + validateRange(range: Range): Range; /** - * Returns a JSON representation of this Uri. + * Ensure a position is contained in the range of this document. * - * @return An object. + * @param position A position. + * @return The given position or a new, adjusted position. */ - toJSON(): any; + validatePosition(position: Position): Position; } \ No newline at end of file From 356826ef8e83b03d463e93ae59166ad871bf81b4 Mon Sep 17 00:00:00 2001 From: Akshita Agarwal Date: Wed, 21 Mar 2018 14:22:07 -0700 Subject: [PATCH 05/56] Changes to retain this context --- src/observers/OmnisharpStatusBarItemObserver.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/observers/OmnisharpStatusBarItemObserver.ts b/src/observers/OmnisharpStatusBarItemObserver.ts index 0e6662f3cb..64ad19acf5 100644 --- a/src/observers/OmnisharpStatusBarItemObserver.ts +++ b/src/observers/OmnisharpStatusBarItemObserver.ts @@ -8,6 +8,7 @@ import * as ObservableEvent from "../omnisharp/loggingEvents"; import { Status } from './status'; import * as serverUtils from '../omnisharp/utils'; import { basename } from 'path'; +import { OmniSharpServer } from '../omnisharp/server'; const debounce = require('lodash.debounce'); @@ -68,7 +69,7 @@ export class OmnisharpStatusBarItemObserver { } } - private updateProjectInfo = () => { + private updateProjectInfo = (server: OmniSharpServer) => { serverUtils.requestWorkspaceInformation(server).then(info => { interface Project { @@ -168,11 +169,12 @@ export class OmnisharpStatusBarItemObserver { this.localDisposables = undefined; } - private handleOmnisharpServerOnStart(event:ObservableEvent.OmnisharpServerOnStart) { + private handleOmnisharpServerOnStart(event: ObservableEvent.OmnisharpServerOnStart) { this.localDisposables = []; SetStatus(this.defaultStatus, '$(flame) Running', 'o.pickProjectAndStart', ''); this.render(); - let debouncedUpdateProjectInfo = debounce(this.updateProjectInfo, 1500, { leading: true }); + let updateProjectInfoFunction = () => this.updateProjectInfo(event.server); + let debouncedUpdateProjectInfo = debounce(updateProjectInfoFunction, 1500, { leading: true }); this.localDisposables.push(event.server.onProjectAdded(debouncedUpdateProjectInfo)); this.localDisposables.push(event.server.onProjectChange(debouncedUpdateProjectInfo)); this.localDisposables.push(event.server.onProjectRemoved(debouncedUpdateProjectInfo)); From f3bb9a1106b9dec2cc145764e51fa7b99e664eb4 Mon Sep 17 00:00:00 2001 From: Akshita Agarwal Date: Wed, 21 Mar 2018 16:36:16 -0700 Subject: [PATCH 06/56] Created fascade for statusbar and texteditor --- src/main.ts | 22 +- .../OmnisharpStatusBarItemObserver.ts | 22 +- src/observers/status.ts | 28 +- src/omnisharp/extension.ts | 2 - src/omnisharp/server.ts | 5 +- src/statusBarItemAdapter.ts | 33 ++ src/textEditorAdapter.ts | 17 + src/vscodeAdapter.ts | 314 +----------------- 8 files changed, 80 insertions(+), 363 deletions(-) create mode 100644 src/statusBarItemAdapter.ts create mode 100644 src/textEditorAdapter.ts diff --git a/src/main.ts b/src/main.ts index 6dad77c15b..49dd7ae521 100644 --- a/src/main.ts +++ b/src/main.ts @@ -21,9 +21,11 @@ import { DotnetLoggerObserver } from './observers/DotnetLoggerObserver'; import { OmnisharpDebugModeLoggerObserver } from './observers/OmnisharpDebugModeLoggerObserver'; import { ActivationFailure, RenderOmnisharpStatusBarItem } from './omnisharp/loggingEvents'; import { EventStream } from './EventStream'; -import { OmnisharpServerStatusObserver, ExecuteCommand, ShowWarningMessage} from './observers/OmnisharpServerStatusObserver'; +import { OmnisharpServerStatusObserver, ExecuteCommand, ShowWarningMessage } from './observers/OmnisharpServerStatusObserver'; import { InformationMessageObserver, ShowInformationMessage, GetConfiguration, WorkspaceAsRelativePath } from './observers/InformationMessageObserver'; import { GetActiveTextEditor, OmnisharpStatusBarItemObserver, Match } from './observers/OmnisharpStatusBarItemObserver'; +import { TextEditorAdapter } from './textEditorAdapter'; +import { StatusBarItemAdapter } from './statusBarItemAdapter'; export async function activate(context: vscode.ExtensionContext): Promise<{ initializationFinished: Promise }> { @@ -34,7 +36,7 @@ export async function activate(context: vscode.ExtensionContext): Promise<{ init const reporter = new TelemetryReporter(extensionId, extensionVersion, aiKey); util.setExtensionPath(extension.extensionPath); - + let dotnetChannel = vscode.window.createOutputChannel('.NET'); let dotnetChannelObserver = new DotNetChannelObserver(dotnetChannel); let dotnetLoggerObserver = new DotnetLoggerObserver(dotnetChannel); @@ -47,20 +49,18 @@ export async function activate(context: vscode.ExtensionContext): Promise<{ init let omnisharpLogObserver = new OmnisharpLoggerObserver(omnisharpChannel); let omnisharpChannelObserver = new OmnisharpChannelObserver(omnisharpChannel); - let showWarningMessage: ShowWarningMessage = (message: string, ...items: string[]) => vscode.window.showWarningMessage(message, ...items); let executeCommand: ExecuteCommand = (command: string, ...rest: any[]) => vscode.commands.executeCommand(command, ...rest); - - let omnisharpServerStatusObserver = new OmnisharpServerStatusObserver(showWarningMessage, executeCommand); - + let omnisharpServerStatusObserver = new OmnisharpServerStatusObserver(showWarningMessage, executeCommand); + let getConfiguration: GetConfiguration = (name: string) => vscode.workspace.getConfiguration(name); let showInformationMessage: ShowInformationMessage = (message: string, ...items: string[]) => vscode.window.showInformationMessage(message, ...items); - let workspaceAsRelativePath: WorkspaceAsRelativePath = (path: string, includeWorkspaceFolder?: boolean) => vscode.workspace.asRelativePath(path, includeWorkspaceFolder); + let workspaceAsRelativePath: WorkspaceAsRelativePath = (path: string, includeWorkspaceFolder?: boolean) => vscode.workspace.asRelativePath(path, includeWorkspaceFolder); let informationMessageObserver = new InformationMessageObserver(getConfiguration, showInformationMessage, workspaceAsRelativePath); - let omnisharpStatusBar = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, Number.MIN_VALUE); - let getActiveTextEditor: GetActiveTextEditor = () => vscode.window.activeTextEditor; - let match: Match = (selector: vscode.DocumentSelector, document: vscode.TextDocument) => vscode.languages.match(selector, document); + let omnisharpStatusBar = new StatusBarItemAdapter(vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, Number.MIN_VALUE)); + let getActiveTextEditor: GetActiveTextEditor = () => new TextEditorAdapter(vscode.window.activeTextEditor); + let match: Match = (selector: vscode.DocumentSelector, document: any) => vscode.languages.match(selector, document); let omnisharpStatusBarObserver = new OmnisharpStatusBarItemObserver(getActiveTextEditor, match, omnisharpStatusBar); const eventStream = new EventStream(); @@ -81,7 +81,7 @@ export async function activate(context: vscode.ExtensionContext): Promise<{ init let omnisharpDebugModeLoggerObserver = new OmnisharpDebugModeLoggerObserver(omnisharpChannel); eventStream.subscribe(omnisharpDebugModeLoggerObserver.post); } - + let platformInfo: PlatformInformation; try { platformInfo = await PlatformInformation.GetCurrent(); diff --git a/src/observers/OmnisharpStatusBarItemObserver.ts b/src/observers/OmnisharpStatusBarItemObserver.ts index 64ad19acf5..bc6b789e1b 100644 --- a/src/observers/OmnisharpStatusBarItemObserver.ts +++ b/src/observers/OmnisharpStatusBarItemObserver.ts @@ -9,6 +9,8 @@ import { Status } from './status'; import * as serverUtils from '../omnisharp/utils'; import { basename } from 'path'; import { OmniSharpServer } from '../omnisharp/server'; +import { CompositeDisposable, Subject } from 'rx'; +import { ProjectInformationResponse } from '../omnisharp/protocol'; const debounce = require('lodash.debounce'); @@ -27,13 +29,13 @@ export interface GetActiveTextEditor { } export interface Match { - (selector: vscode.DocumentSelector, document: vscode.TextDocument): number; + (selector: vscode.DocumentSelector, document: any): number; } export class OmnisharpStatusBarItemObserver { private defaultStatus: Status; private projectStatus: Status; - private localDisposables: vscode.Disposable[]; + private localDisposables: CompositeDisposable; constructor(private getActiveTextEditor: GetActiveTextEditor, private match: Match, private statusBar: vscode.StatusBarItem) { this.defaultStatus = new Status(defaultSelector); @@ -161,23 +163,23 @@ export class OmnisharpStatusBarItemObserver { private handleOmnisharpServerOnStop() { this.projectStatus = undefined; this.defaultStatus.text = undefined; + let disposables = this.localDisposables; + this.localDisposables = undefined; - if (this.localDisposables) { - vscode.Disposable.from(...this.localDisposables).dispose(); + if (disposables) { + disposables.dispose(); } - - this.localDisposables = undefined; } private handleOmnisharpServerOnStart(event: ObservableEvent.OmnisharpServerOnStart) { - this.localDisposables = []; + this.localDisposables = new CompositeDisposable(); SetStatus(this.defaultStatus, '$(flame) Running', 'o.pickProjectAndStart', ''); this.render(); let updateProjectInfoFunction = () => this.updateProjectInfo(event.server); let debouncedUpdateProjectInfo = debounce(updateProjectInfoFunction, 1500, { leading: true }); - this.localDisposables.push(event.server.onProjectAdded(debouncedUpdateProjectInfo)); - this.localDisposables.push(event.server.onProjectChange(debouncedUpdateProjectInfo)); - this.localDisposables.push(event.server.onProjectRemoved(debouncedUpdateProjectInfo)); + this.localDisposables.add(event.server.onProjectAdded(debouncedUpdateProjectInfo)); + this.localDisposables.add(event.server.onProjectChange(debouncedUpdateProjectInfo)); + this.localDisposables.add(event.server.onProjectRemoved(debouncedUpdateProjectInfo)); } } diff --git a/src/observers/status.ts b/src/observers/status.ts index bda1339386..6074edca30 100644 --- a/src/observers/status.ts +++ b/src/observers/status.ts @@ -15,30 +15,4 @@ export class Status { constructor(selector: vscode.DocumentSelector) { this.selector = selector; } -} - -function render(defaultStatus: Status) { - if (!vscode.window.activeTextEditor) { - this.hide(); - return; - } - - let document = vscode.window.activeTextEditor.document; - let status: Status; - - if (projectStatus && vscode.languages.match(projectStatus.selector, document)) { - status = projectStatus; - } else if (defaultStatus.text && vscode.languages.match(defaultStatus.selector, document)) { - status = defaultStatus; - } - - if (status) { - this.text = status.text; - this.command = status.command; - this.color = status.color; - this.show(); - return; - } - - this.hide(); -} +} \ No newline at end of file diff --git a/src/omnisharp/extension.ts b/src/omnisharp/extension.ts index 12e68b4f05..5e9884c865 100644 --- a/src/omnisharp/extension.ts +++ b/src/omnisharp/extension.ts @@ -28,7 +28,6 @@ import TestManager from '../features/dotnetTest'; import WorkspaceSymbolProvider from '../features/workspaceSymbolProvider'; import forwardChanges from '../features/changeForwarding'; import registerCommands from '../features/commands'; -import reportStatus from '../features/status'; import { PlatformInformation } from '../platform'; import { ProjectJsonDeprecatedWarning, OmnisharpStart } from './loggingEvents'; import { EventStream } from '../EventStream'; @@ -87,7 +86,6 @@ export function activate(context: vscode.ExtensionContext, eventStream: EventStr })); disposables.push(registerCommands(server, eventStream,platformInfo)); - disposables.push(reportStatus(server, eventStream)); if (!context.workspaceState.get('assetPromptDisabled')) { disposables.push(server.onServerStart(() => { diff --git a/src/omnisharp/server.ts b/src/omnisharp/server.ts index c2de893144..edf0ab00e2 100644 --- a/src/omnisharp/server.ts +++ b/src/omnisharp/server.ts @@ -22,6 +22,7 @@ import { setTimeout } from 'timers'; import { OmnisharpDownloader } from './OmnisharpDownloader'; import * as ObservableEvents from './loggingEvents'; import { EventStream } from '../EventStream'; +import { Disposable } from 'rx'; enum ServerState { Starting, @@ -258,10 +259,10 @@ export class OmniSharpServer { return this._addListener(Events.Started, listener); } - private _addListener(event: string, listener: (e: any) => any, thisArg?: any): vscode.Disposable { + private _addListener(event: string, listener: (e: any) => any, thisArg?: any): Disposable { listener = thisArg ? listener.bind(thisArg) : listener; this._eventBus.addListener(event, listener); - return new vscode.Disposable(() => this._eventBus.removeListener(event, listener)); + return Disposable.create(() => this._eventBus.removeListener(event, listener)); } protected _fireEvent(event: string, args: any): void { diff --git a/src/statusBarItemAdapter.ts b/src/statusBarItemAdapter.ts new file mode 100644 index 0000000000..cdd5dd12ed --- /dev/null +++ b/src/statusBarItemAdapter.ts @@ -0,0 +1,33 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscodeAdapter from './vscodeAdapter'; +import * as vscode from 'vscode'; + +export class StatusBarItemAdapter implements vscodeAdapter.StatusBarItem { + + alignment: vscodeAdapter.StatusBarAlignment; + priority: number; + text: string; + tooltip: string; + get color(): string{ + return this.statusBarItem.color as string; + } + set color(value: string) { + this.statusBarItem.color = value; + } + command: string; + show(): void { + this.statusBarItem.show(); + } + hide(): void { + this.statusBarItem.hide(); + } + dispose(): void { + this.statusBarItem.dispose(); + } + constructor(private statusBarItem: vscode.StatusBarItem) { + } +} \ No newline at end of file diff --git a/src/textEditorAdapter.ts b/src/textEditorAdapter.ts new file mode 100644 index 0000000000..c08b196c51 --- /dev/null +++ b/src/textEditorAdapter.ts @@ -0,0 +1,17 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscodeAdapter from './vscodeAdapter'; +import * as vscode from 'vscode'; + +export class TextEditorAdapter implements vscodeAdapter.TextEditor { + + get document(): any { + return this.textEditor.document; + } + + constructor(private textEditor: vscode.TextEditor) { + } +} \ No newline at end of file diff --git a/src/vscodeAdapter.ts b/src/vscodeAdapter.ts index dd0784ff2b..01f71c6a66 100644 --- a/src/vscodeAdapter.ts +++ b/src/vscodeAdapter.ts @@ -204,15 +204,6 @@ export enum StatusBarAlignment { Right = 2 } -export interface ThemeColor { - - /** - * Creates a reference to a theme color. - * @param id of the color. The available colors are listed in https://code.visualstudio.com/docs/getstarted/theme-color-reference. - */ - constructor(id: string); -} - export interface StatusBarItem { @@ -245,7 +236,7 @@ export interface StatusBarItem { /** * The foreground color for this entry. */ - color: string | ThemeColor | undefined; + color: string | undefined; /** * The identifier of a command to run on click. The command must be @@ -289,56 +280,7 @@ export interface DocumentFilter { pattern?: GlobPattern; } -export interface RelativePattern { - - /** - * A base file path to which this pattern will be matched against relatively. - */ - base: string; - - /** - * A file glob pattern like `*.{ts,js}` that will be matched on file paths - * relative to the base path. - * - * Example: Given a base of `/home/work/folder` and a file path of `/home/work/folder/index.js`, - * the file glob pattern will match on `index.js`. - */ - pattern: string; - - /** - * Creates a new relative pattern object with a base path and pattern to match. This pattern - * will be matched on file paths relative to the base path. - * - * @param base A base file path to which this pattern will be matched against relatively. - * @param pattern A file glob pattern like `*.{ts,js}` that will be matched on file paths - * relative to the base path. - */ - constructor(base: WorkspaceFolder | string, pattern: string); -} - -export interface WorkspaceFolder { - - /** - * The associated uri for this workspace folder. - * - * *Note:* The [Uri](#Uri)-type was intentionally chosen such that future releases of the editor can support - * workspace folders that are not stored on the local disk, e.g. `ftp://server/workspaces/foo`. - */ - readonly uri: Uri; - - /** - * The name of this workspace folder. Defaults to - * the basename of its [uri-path](#Uri.path) - */ - readonly name: string; - - /** - * The ordinal number of this workspace folder. - */ - readonly index: number; -} - -export type GlobPattern = string | RelativePattern; +export type GlobPattern = string; export type DocumentSelector = string | DocumentFilter | (string | DocumentFilter)[]; @@ -350,260 +292,10 @@ export interface MessageOptions { modal?: boolean; } -export interface MessageItem { - - /** - * A short title like 'Retry', 'Open Log' etc. - */ - title: string; - - /** - * Indicates that this item replaces the default - * 'Close' action. - */ - isCloseAffordance?: boolean; -} - - -/** - * Represents an editor that is attached to a [document](#TextDocument). - */ export interface TextEditor { /** * The document associated with this text editor. The document will be the same for the entire lifetime of this text editor. */ - document: TextDocument; - - /** - * The primary selection on this text editor. Shorthand for `TextEditor.selections[0]`. - */ - selection: Selection; - - /** - * The selections in this text editor. The primary selection is always at index 0. - */ - selections: Selection[]; - - /** - * Text editor options. - */ - options: TextEditorOptions; - - /** - * The column in which this editor shows. Will be `undefined` in case this - * isn't one of the three main editors, e.g an embedded editor. - */ - viewColumn?: ViewColumn; - - /** - * Perform an edit on the document associated with this text editor. - * - * The given callback-function is invoked with an [edit-builder](#TextEditorEdit) which must - * be used to make edits. Note that the edit-builder is only valid while the - * callback executes. - * - * @param callback A function which can create edits using an [edit-builder](#TextEditorEdit). - * @param options The undo/redo behavior around this edit. By default, undo stops will be created before and after this edit. - * @return A promise that resolves with a value indicating if the edits could be applied. - */ - edit(callback: (editBuilder: TextEditorEdit) => void, options?: { undoStopBefore: boolean; undoStopAfter: boolean; }): Thenable; - - /** - * Insert a [snippet](#SnippetString) and put the editor into snippet mode. "Snippet mode" - * means the editor adds placeholders and additionals cursors so that the user can complete - * or accept the snippet. - * - * @param snippet The snippet to insert in this edit. - * @param location Position or range at which to insert the snippet, defaults to the current editor selection or selections. - * @param options The undo/redo behavior around this edit. By default, undo stops will be created before and after this edit. - * @return A promise that resolves with a value indicating if the snippet could be inserted. Note that the promise does not signal - * that the snippet is completely filled-in or accepted. - */ - insertSnippet(snippet: SnippetString, location?: Position | Range | Position[] | Range[], options?: { undoStopBefore: boolean; undoStopAfter: boolean; }): Thenable; - - /** - * Adds a set of decorations to the text editor. If a set of decorations already exists with - * the given [decoration type](#TextEditorDecorationType), they will be replaced. - * - * @see [createTextEditorDecorationType](#window.createTextEditorDecorationType). - * - * @param decorationType A decoration type. - * @param rangesOrOptions Either [ranges](#Range) or more detailed [options](#DecorationOptions). - */ - setDecorations(decorationType: TextEditorDecorationType, rangesOrOptions: Range[] | DecorationOptions[]): void; - - /** - * Scroll as indicated by `revealType` in order to reveal the given range. - * - * @param range A range. - * @param revealType The scrolling strategy for revealing `range`. - */ - revealRange(range: Range, revealType?: TextEditorRevealType): void; - - /** - * ~~Show the text editor.~~ - * - * @deprecated Use [window.showTextDocument](#window.showTextDocument) - * - * @param column The [column](#ViewColumn) in which to show this editor. - * instead. This method shows unexpected behavior and will be removed in the next major update. - */ - show(column?: ViewColumn): void; - - /** - * ~~Hide the text editor.~~ - * - * @deprecated Use the command `workbench.action.closeActiveEditor` instead. - * This method shows unexpected behavior and will be removed in the next major update. - */ - hide(): void; -} -export interface TextDocument { - - /** - * The associated URI for this document. Most documents have the __file__-scheme, indicating that they - * represent files on disk. However, some documents may have other schemes indicating that they are not - * available on disk. - */ - readonly uri: Uri; - - /** - * The file system path of the associated resource. Shorthand - * notation for [TextDocument.uri.fsPath](#TextDocument.uri). Independent of the uri scheme. - */ - readonly fileName: string; - - /** - * Is this document representing an untitled file. - */ - readonly isUntitled: boolean; - - /** - * The identifier of the language associated with this document. - */ - readonly languageId: string; - - /** - * The version number of this document (it will strictly increase after each - * change, including undo/redo). - */ - readonly version: number; - - /** - * `true` if there are unpersisted changes. - */ - readonly isDirty: boolean; - - /** - * `true` if the document have been closed. A closed document isn't synchronized anymore - * and won't be re-used when the same resource is opened again. - */ - readonly isClosed: boolean; - - /** - * Save the underlying file. - * - * @return A promise that will resolve to true when the file - * has been saved. If the file was not dirty or the save failed, - * will return false. - */ - save(): Thenable; - - /** - * The [end of line](#EndOfLine) sequence that is predominately - * used in this document. - */ - readonly eol: EndOfLine; - - /** - * The number of lines in this document. - */ - readonly lineCount: number; - - /** - * Returns a text line denoted by the line number. Note - * that the returned object is *not* live and changes to the - * document are not reflected. - * - * @param line A line number in [0, lineCount). - * @return A [line](#TextLine). - */ - lineAt(line: number): TextLine; - - /** - * Returns a text line denoted by the position. Note - * that the returned object is *not* live and changes to the - * document are not reflected. - * - * The position will be [adjusted](#TextDocument.validatePosition). - * - * @see [TextDocument.lineAt](#TextDocument.lineAt) - * @param position A position. - * @return A [line](#TextLine). - */ - lineAt(position: Position): TextLine; - - /** - * Converts the position to a zero-based offset. - * - * The position will be [adjusted](#TextDocument.validatePosition). - * - * @param position A position. - * @return A valid zero-based offset. - */ - offsetAt(position: Position): number; - - /** - * Converts a zero-based offset to a position. - * - * @param offset A zero-based offset. - * @return A valid [position](#Position). - */ - positionAt(offset: number): Position; - - /** - * Get the text of this document. A substring can be retrieved by providing - * a range. The range will be [adjusted](#TextDocument.validateRange). - * - * @param range Include only the text included by the range. - * @return The text inside the provided range or the entire text. - */ - getText(range?: Range): string; - - /** - * Get a word-range at the given position. By default words are defined by - * common separators, like space, -, _, etc. In addition, per languge custom - * [word definitions](#LanguageConfiguration.wordPattern) can be defined. It - * is also possible to provide a custom regular expression. - * - * * *Note 1:* A custom regular expression must not match the empty string and - * if it does, it will be ignored. - * * *Note 2:* A custom regular expression will fail to match multiline strings - * and in the name of speed regular expressions should not match words with - * spaces. Use [`TextLine.text`](#TextLine.text) for more complex, non-wordy, scenarios. - * - * The position will be [adjusted](#TextDocument.validatePosition). - * - * @param position A position. - * @param regex Optional regular expression that describes what a word is. - * @return A range spanning a word, or `undefined`. - */ - getWordRangeAtPosition(position: Position, regex?: RegExp): Range | undefined; - - /** - * Ensure a range is completely contained in this document. - * - * @param range A range. - * @return The given range or a new, adjusted range. - */ - validateRange(range: Range): Range; - - /** - * Ensure a position is contained in the range of this document. - * - * @param position A position. - * @return The given position or a new, adjusted position. - */ - validatePosition(position: Position): Position; + document: any; } \ No newline at end of file From af75cd70d3b5f07bd90f867f996ec20289ab29e5 Mon Sep 17 00:00:00 2001 From: Akshita Agarwal Date: Wed, 21 Mar 2018 16:42:58 -0700 Subject: [PATCH 07/56] Subscribe to event stream --- src/main.ts | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/src/main.ts b/src/main.ts index 49dd7ae521..0138fc5ddb 100644 --- a/src/main.ts +++ b/src/main.ts @@ -37,44 +37,42 @@ export async function activate(context: vscode.ExtensionContext): Promise<{ init util.setExtensionPath(extension.extensionPath); + const eventStream = new EventStream(); + let dotnetChannel = vscode.window.createOutputChannel('.NET'); let dotnetChannelObserver = new DotNetChannelObserver(dotnetChannel); let dotnetLoggerObserver = new DotnetLoggerObserver(dotnetChannel); + eventStream.subscribe(dotnetChannelObserver.post); + eventStream.subscribe(dotnetLoggerObserver.post); let csharpChannel = vscode.window.createOutputChannel('C#'); let csharpchannelObserver = new CsharpChannelObserver(csharpChannel); let csharpLogObserver = new CsharpLoggerObserver(csharpChannel); + eventStream.subscribe(csharpchannelObserver.post); + eventStream.subscribe(csharpLogObserver.post); let omnisharpChannel = vscode.window.createOutputChannel('OmniSharp Log'); let omnisharpLogObserver = new OmnisharpLoggerObserver(omnisharpChannel); let omnisharpChannelObserver = new OmnisharpChannelObserver(omnisharpChannel); + eventStream.subscribe(omnisharpLogObserver.post); + eventStream.subscribe(omnisharpChannelObserver.post); let showWarningMessage: ShowWarningMessage = (message: string, ...items: string[]) => vscode.window.showWarningMessage(message, ...items); let executeCommand: ExecuteCommand = (command: string, ...rest: any[]) => vscode.commands.executeCommand(command, ...rest); let omnisharpServerStatusObserver = new OmnisharpServerStatusObserver(showWarningMessage, executeCommand); + eventStream.subscribe(omnisharpServerStatusObserver.post); let getConfiguration: GetConfiguration = (name: string) => vscode.workspace.getConfiguration(name); let showInformationMessage: ShowInformationMessage = (message: string, ...items: string[]) => vscode.window.showInformationMessage(message, ...items); let workspaceAsRelativePath: WorkspaceAsRelativePath = (path: string, includeWorkspaceFolder?: boolean) => vscode.workspace.asRelativePath(path, includeWorkspaceFolder); let informationMessageObserver = new InformationMessageObserver(getConfiguration, showInformationMessage, workspaceAsRelativePath); + eventStream.subscribe(informationMessageObserver.post); let omnisharpStatusBar = new StatusBarItemAdapter(vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, Number.MIN_VALUE)); let getActiveTextEditor: GetActiveTextEditor = () => new TextEditorAdapter(vscode.window.activeTextEditor); let match: Match = (selector: vscode.DocumentSelector, document: any) => vscode.languages.match(selector, document); let omnisharpStatusBarObserver = new OmnisharpStatusBarItemObserver(getActiveTextEditor, match, omnisharpStatusBar); - - const eventStream = new EventStream(); - eventStream.subscribe(dotnetChannelObserver.post); - eventStream.subscribe(dotnetLoggerObserver.post); - - eventStream.subscribe(csharpchannelObserver.post); - eventStream.subscribe(csharpLogObserver.post); - - eventStream.subscribe(omnisharpLogObserver.post); - eventStream.subscribe(omnisharpChannelObserver.post); - - eventStream.subscribe(omnisharpServerStatusObserver.post); - eventStream.subscribe(informationMessageObserver.post); + eventStream.subscribe(omnisharpStatusBarObserver.post); const debugMode = false; if (debugMode) { From 7baa2593b8aab9d7ae9bad7a2ae2e7082b8aa07f Mon Sep 17 00:00:00 2001 From: Akshita Agarwal Date: Wed, 21 Mar 2018 19:47:01 -0700 Subject: [PATCH 08/56] Working! --- .../OmnisharpStatusBarItemObserver.ts | 4 +- src/omnisharp/server.ts | 97 ++++++++++--------- src/statusBarItemAdapter.ts | 42 +++++++- 3 files changed, 89 insertions(+), 54 deletions(-) diff --git a/src/observers/OmnisharpStatusBarItemObserver.ts b/src/observers/OmnisharpStatusBarItemObserver.ts index bc6b789e1b..a974c110b7 100644 --- a/src/observers/OmnisharpStatusBarItemObserver.ts +++ b/src/observers/OmnisharpStatusBarItemObserver.ts @@ -145,8 +145,8 @@ export class OmnisharpStatusBarItemObserver { if (this.projectStatus && this.match(this.projectStatus.selector, document)) { status = this.projectStatus; - } else if (this.projectStatus.text && this.match(this.projectStatus.selector, document)) { - status = this.projectStatus; + } else if (this.defaultStatus.text && this.match(this.defaultStatus.selector, document)) { + status = this.defaultStatus; } if (status) { diff --git a/src/omnisharp/server.ts b/src/omnisharp/server.ts index edf0ab00e2..eb87426b46 100644 --- a/src/omnisharp/server.ts +++ b/src/omnisharp/server.ts @@ -22,7 +22,7 @@ import { setTimeout } from 'timers'; import { OmnisharpDownloader } from './OmnisharpDownloader'; import * as ObservableEvents from './loggingEvents'; import { EventStream } from '../EventStream'; -import { Disposable } from 'rx'; +import { Disposable, CompositeDisposable } from 'rx'; enum ServerState { Starting, @@ -69,9 +69,8 @@ const latestVersionFileServerPath = 'releases/versioninfo.txt'; export class OmniSharpServer { private static _nextId = 1; - private _readLine: ReadLine; - private _disposables: vscode.Disposable[] = []; + private _disposables: CompositeDisposable = new CompositeDisposable(Disposable.empty); private _delayTrackers: { [requestName: string]: DelayTracker }; private _telemetryIntervalId: NodeJS.Timer = undefined; @@ -91,46 +90,6 @@ export class OmniSharpServer { this._requestQueue = new RequestQueueCollection(this.eventStream, 8, request => this._makeRequest(request)); let downloader = new OmnisharpDownloader(this.eventStream, packageJSON, platformInfo); this._omnisharpManager = new OmnisharpManager(downloader, platformInfo); - - this._disposables.push(this.onServerError(err => - this.eventStream.post(new ObservableEvents.OmnisharpServerOnServerError(err)) - )); - - this._disposables.push(this.onError((message: protocol.ErrorMessage) => - eventStream.post(new ObservableEvents.OmnisharpServerOnError(message)) - )); - - this._disposables.push(this.onMsBuildProjectDiagnostics((message:protocol.MSBuildProjectDiagnostics) => - eventStream.post(new ObservableEvents.OmnisharpServerMsBuildProjectDiagnostics(message)) - )); - - this._disposables.push(this.onUnresolvedDependencies((message: protocol.UnresolvedDependenciesMessage) => - eventStream.post(new ObservableEvents.OmnisharpServerUnresolvedDependencies(message, this, this.eventStream)) - )); - - this._disposables.push(this.onStderr((message:string) => - eventStream.post(new ObservableEvents.OmnisharpServerOnStdErr(message)) - )); - - this._disposables.push(this.onMultipleLaunchTargets((targets: LaunchTarget[]) => - this.eventStream.post(new ObservableEvents.OmnisharpOnMultipleLaunchTargets(targets)) - )); - - this._disposables.push(this.onBeforeServerInstall(() => - this.eventStream.post(new ObservableEvents.OmnisharpOnBeforeServerInstall()) - )); - - this._disposables.push(this.onBeforeServerStart(() => - this.eventStream.post(new ObservableEvents.OmnisharpOnBeforeServerStart()) - )); - - this._disposables.push(this.onServerStop(() => - this.eventStream.post(new ObservableEvents.OmnisharpServerOnStop()) - )); - - this._disposables.push(this.onServerStart(() => - this.eventStream.post(new ObservableEvents.OmnisharpServerOnStart(this)) - )); } public isRunning(): boolean { @@ -272,6 +231,52 @@ export class OmniSharpServer { // --- start, stop, and connect private async _start(launchTarget: LaunchTarget): Promise { + + let disposables = new CompositeDisposable(); + disposables.add(this.onServerError(err => + this.eventStream.post(new ObservableEvents.OmnisharpServerOnServerError(err)) + )); + + disposables.add(this.onError((message: protocol.ErrorMessage) => + this.eventStream.post(new ObservableEvents.OmnisharpServerOnError(message)) + )); + + disposables.add(this.onMsBuildProjectDiagnostics((message:protocol.MSBuildProjectDiagnostics) => + this.eventStream.post(new ObservableEvents.OmnisharpServerMsBuildProjectDiagnostics(message)) + )); + + disposables.add(this.onUnresolvedDependencies((message: protocol.UnresolvedDependenciesMessage) => + this.eventStream.post(new ObservableEvents.OmnisharpServerUnresolvedDependencies(message, this, this.eventStream)) + )); + + disposables.add(this.onStderr((message:string) => + this.eventStream.post(new ObservableEvents.OmnisharpServerOnStdErr(message)) + )); + + disposables.add(this.onMultipleLaunchTargets((targets: LaunchTarget[]) => + this.eventStream.post(new ObservableEvents.OmnisharpOnMultipleLaunchTargets(targets)) + )); + + disposables.add(this.onBeforeServerInstall(() => + this.eventStream.post(new ObservableEvents.OmnisharpOnBeforeServerInstall()) + )); + + disposables.add(this.onBeforeServerStart(() => + this.eventStream.post(new ObservableEvents.OmnisharpOnBeforeServerStart()) + )); + + disposables.add(this.onServerStop(() => + this.eventStream.post(new ObservableEvents.OmnisharpServerOnStop()) + )); + + disposables.add(this.onServerStart(() => { + this.eventStream.post(new ObservableEvents.OmnisharpServerOnStart(this)); + })); + + this._disposables = new CompositeDisposable(Disposable.create(() => { + disposables.dispose(); + })); + this._setState(ServerState.Starting); this._launchTarget = launchTarget; @@ -329,9 +334,7 @@ export class OmniSharpServer { public stop(): Promise { - while (this._disposables.length) { - this._disposables.pop().dispose(); - } + this._disposables.dispose(); let cleanupPromise: Promise; @@ -511,7 +514,7 @@ export class OmniSharpServer { this._readLine.addListener('line', lineReceived); - this._disposables.push(new vscode.Disposable(() => { + this._disposables.add(Disposable.create(() => { this._readLine.removeListener('line', lineReceived); })); diff --git a/src/statusBarItemAdapter.ts b/src/statusBarItemAdapter.ts index cdd5dd12ed..f91d56054a 100644 --- a/src/statusBarItemAdapter.ts +++ b/src/statusBarItemAdapter.ts @@ -8,26 +8,58 @@ import * as vscode from 'vscode'; export class StatusBarItemAdapter implements vscodeAdapter.StatusBarItem { - alignment: vscodeAdapter.StatusBarAlignment; - priority: number; - text: string; - tooltip: string; + get alignment(): vscodeAdapter.StatusBarAlignment{ + return this.statusBarItem.alignment; + } + + get priority(): number{ + return this.statusBarItem.priority; + } + + get text(): string{ + return this.statusBarItem.text; + } + + set text(value: string) { + this.statusBarItem.text = value; + } + + get tooltip(): string{ + return this.statusBarItem.tooltip; + } + + set tooltip(value: string){ + this.statusBarItem.tooltip = value; + } + get color(): string{ return this.statusBarItem.color as string; } + set color(value: string) { this.statusBarItem.color = value; } - command: string; + + get command(): string{ + return this.statusBarItem.command; + } + + set command(value: string) { + this.statusBarItem.command = value; + } + show(): void { this.statusBarItem.show(); } + hide(): void { this.statusBarItem.hide(); } + dispose(): void { this.statusBarItem.dispose(); } + constructor(private statusBarItem: vscode.StatusBarItem) { } } \ No newline at end of file From f1a02e90ab3d365f6825c6880d4be7487cc9d36e Mon Sep 17 00:00:00 2001 From: Akshita Agarwal Date: Thu, 22 Mar 2018 14:09:45 -0700 Subject: [PATCH 09/56] Nits --- src/main.ts | 8 ++++---- src/observers/InformationMessageObserver.ts | 4 ++-- ...usBarItemObserver.ts => OmnisharpStatusBarObserver.ts} | 5 ++--- src/omnisharp/loggingEvents.ts | 2 +- 4 files changed, 9 insertions(+), 10 deletions(-) rename src/observers/{OmnisharpStatusBarItemObserver.ts => OmnisharpStatusBarObserver.ts} (98%) diff --git a/src/main.ts b/src/main.ts index 0138fc5ddb..3ea1e16af0 100644 --- a/src/main.ts +++ b/src/main.ts @@ -19,11 +19,11 @@ import { TelemetryObserver } from './observers/TelemetryObserver'; import { OmnisharpChannelObserver } from './observers/OmnisharpChannelObserver'; import { DotnetLoggerObserver } from './observers/DotnetLoggerObserver'; import { OmnisharpDebugModeLoggerObserver } from './observers/OmnisharpDebugModeLoggerObserver'; -import { ActivationFailure, RenderOmnisharpStatusBarItem } from './omnisharp/loggingEvents'; +import { ActivationFailure, ActiveTextEditorChanged } from './omnisharp/loggingEvents'; import { EventStream } from './EventStream'; import { OmnisharpServerStatusObserver, ExecuteCommand, ShowWarningMessage } from './observers/OmnisharpServerStatusObserver'; import { InformationMessageObserver, ShowInformationMessage, GetConfiguration, WorkspaceAsRelativePath } from './observers/InformationMessageObserver'; -import { GetActiveTextEditor, OmnisharpStatusBarItemObserver, Match } from './observers/OmnisharpStatusBarItemObserver'; +import { GetActiveTextEditor, OmnisharpStatusBarObserver, Match } from './observers/OmnisharpStatusBarObserver'; import { TextEditorAdapter } from './textEditorAdapter'; import { StatusBarItemAdapter } from './statusBarItemAdapter'; @@ -71,7 +71,7 @@ export async function activate(context: vscode.ExtensionContext): Promise<{ init let omnisharpStatusBar = new StatusBarItemAdapter(vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, Number.MIN_VALUE)); let getActiveTextEditor: GetActiveTextEditor = () => new TextEditorAdapter(vscode.window.activeTextEditor); let match: Match = (selector: vscode.DocumentSelector, document: any) => vscode.languages.match(selector, document); - let omnisharpStatusBarObserver = new OmnisharpStatusBarItemObserver(getActiveTextEditor, match, omnisharpStatusBar); + let omnisharpStatusBarObserver = new OmnisharpStatusBarObserver(getActiveTextEditor, match, omnisharpStatusBar); eventStream.subscribe(omnisharpStatusBarObserver.post); const debugMode = false; @@ -99,7 +99,7 @@ export async function activate(context: vscode.ExtensionContext): Promise<{ init // register JSON completion & hover providers for project.json context.subscriptions.push(addJSONProviders()); context.subscriptions.push(vscode.window.onDidChangeActiveTextEditor(() => { - eventStream.post(new RenderOmnisharpStatusBarItem()); + eventStream.post(new ActiveTextEditorChanged()); })); let coreClrDebugPromise = Promise.resolve(); diff --git a/src/observers/InformationMessageObserver.ts b/src/observers/InformationMessageObserver.ts index aa6ae5bba6..9153de005d 100644 --- a/src/observers/InformationMessageObserver.ts +++ b/src/observers/InformationMessageObserver.ts @@ -20,7 +20,7 @@ export interface WorkspaceAsRelativePath{ } export class InformationMessageObserver { - constructor(private getConfiguration: GetConfiguration, private showInformationMessage: ShowInformationMessage, private worksapaceAsRelativePath : WorkspaceAsRelativePath) { + constructor(private getConfiguration: GetConfiguration, private showInformationMessage: ShowInformationMessage, private workspaceAsRelativePath : WorkspaceAsRelativePath) { } public post = (event: ObservableEvent.BaseEvent) => { @@ -34,7 +34,7 @@ export class InformationMessageObserver { private handleOmnisharpServerUnresolvedDependencies(event: ObservableEvent.OmnisharpServerUnresolvedDependencies) { let csharpConfig = this.getConfiguration('csharp'); if (!csharpConfig.get('suppressDotnetRestoreNotification')) { - let info = `There are unresolved dependencies from '${this.worksapaceAsRelativePath(event.unresolvedDependencies.FileName)}'. Please execute the restore command to continue.`; + let info = `There are unresolved dependencies from '${this.workspaceAsRelativePath(event.unresolvedDependencies.FileName)}'. Please execute the restore command to continue.`; return this.showInformationMessage(info, 'Restore').then(value => { if (value) { diff --git a/src/observers/OmnisharpStatusBarItemObserver.ts b/src/observers/OmnisharpStatusBarObserver.ts similarity index 98% rename from src/observers/OmnisharpStatusBarItemObserver.ts rename to src/observers/OmnisharpStatusBarObserver.ts index a974c110b7..aeb666bfec 100644 --- a/src/observers/OmnisharpStatusBarItemObserver.ts +++ b/src/observers/OmnisharpStatusBarObserver.ts @@ -23,7 +23,6 @@ let defaultSelector: vscode.DocumentSelector = [ { pattern: '**/*.cake' } // Cake script ]; - export interface GetActiveTextEditor { (): vscode.TextEditor; } @@ -32,7 +31,7 @@ export interface Match { (selector: vscode.DocumentSelector, document: any): number; } -export class OmnisharpStatusBarItemObserver { +export class OmnisharpStatusBarObserver { private defaultStatus: Status; private projectStatus: Status; private localDisposables: CompositeDisposable; @@ -59,7 +58,7 @@ export class OmnisharpStatusBarItemObserver { SetStatus(this.defaultStatus, '$(flame) Starting...', 'o.showOutput', ''); this.render(); break; - case ObservableEvent.RenderOmnisharpStatusBarItem.name: + case ObservableEvent.ActiveTextEditorChanged.name: this.render(); break; case ObservableEvent.OmnisharpServerOnStop.name: diff --git a/src/omnisharp/loggingEvents.ts b/src/omnisharp/loggingEvents.ts index 636becf531..adf508494e 100644 --- a/src/omnisharp/loggingEvents.ts +++ b/src/omnisharp/loggingEvents.ts @@ -130,5 +130,5 @@ export class OmnisharpServerProcessRequestComplete implements BaseEvent { } export class ProjectJsonDeprecatedWarning implements BaseEvent { } export class OmnisharpOnBeforeServerStart implements BaseEvent { } export class OmnisharpOnBeforeServerInstall implements BaseEvent { } -export class RenderOmnisharpStatusBarItem implements BaseEvent { } +export class ActiveTextEditorChanged implements BaseEvent { } export class OmnisharpServerOnStop implements BaseEvent { } \ No newline at end of file From bb23931c97c25c4017d48b26983d347437294b6c Mon Sep 17 00:00:00 2001 From: Akshita Agarwal Date: Thu, 22 Mar 2018 15:29:35 -0700 Subject: [PATCH 10/56] Mocking warning message --- test/unitTests/logging/Fakes.ts | 30 ++++++++++++++++- .../logging/OmnisharpLoggerObserver.test.ts | 33 +++++++------------ .../OmnisharpServerStatusObserver.test.ts | 29 ++++++++++++++++ 3 files changed, 69 insertions(+), 23 deletions(-) create mode 100644 test/unitTests/logging/OmnisharpServerStatusObserver.test.ts diff --git a/test/unitTests/logging/Fakes.ts b/test/unitTests/logging/Fakes.ts index 7bc15add53..87ccded7e4 100644 --- a/test/unitTests/logging/Fakes.ts +++ b/test/unitTests/logging/Fakes.ts @@ -5,6 +5,8 @@ import * as vscode from '../../../src/vscodeAdapter'; import { ITelemetryReporter } from '../../../src/observers/TelemetryObserver'; +import { MSBuildDiagnosticsMessage } from '../../../src/omnisharp/protocol'; +import { OmnisharpServerMsBuildProjectDiagnostics } from '../../../src/omnisharp/loggingEvents'; export const getNullChannel = (): vscode.OutputChannel => { let returnChannel: vscode.OutputChannel = { @@ -25,4 +27,30 @@ export const getNullTelemetryReporter = (): ITelemetryReporter => { }; return reporter; -}; \ No newline at end of file +}; + +export function getOmnisharpMSBuildProjectDiagnostics(fileName: string, warnings: MSBuildDiagnosticsMessage[], errors: MSBuildDiagnosticsMessage[]): OmnisharpServerMsBuildProjectDiagnostics { + return new OmnisharpServerMsBuildProjectDiagnostics({ + FileName: fileName, + Warnings: warnings, + Errors: errors + }); +} + +export function getMSBuildDiagnosticsMessage(logLevel: string, + fileName: string, + text: string, + startLine: number, + startColumn: number, + endLine: number, + endColumn: number): MSBuildDiagnosticsMessage{ + return { + LogLevel: logLevel, + FileName: fileName, + Text: text, + StartLine: startLine, + StartColumn: startColumn, + EndLine: endLine, + EndColumn: endColumn + }; +} \ No newline at end of file diff --git a/test/unitTests/logging/OmnisharpLoggerObserver.test.ts b/test/unitTests/logging/OmnisharpLoggerObserver.test.ts index b0bebb052f..40228aadb9 100644 --- a/test/unitTests/logging/OmnisharpLoggerObserver.test.ts +++ b/test/unitTests/logging/OmnisharpLoggerObserver.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { should, expect } from 'chai'; -import { getNullChannel } from './Fakes'; +import { getNullChannel, getMSBuildDiagnosticsMessage, getOmnisharpMSBuildProjectDiagnostics } from './Fakes'; import { OmnisharpLoggerObserver } from '../../../src/observers/OmnisharpLoggerObserver'; import { OmnisharpServerMsBuildProjectDiagnostics, EventWithMessage, OmnisharpServerOnStdErr, OmnisharpServerMessage, OmnisharpServerOnServerError, OmnisharpInitialisation, OmnisharpLaunch, OmnisharpServerOnError, OmnisharpFailure, OmnisharpEventPacketReceived } from '../../../src/omnisharp/loggingEvents'; import { MSBuildDiagnosticsMessage } from '../../../src/omnisharp/protocol'; @@ -21,35 +21,24 @@ suite("OmnisharpLoggerObserver", () => { logOutput = ""; }); - [ - new OmnisharpServerMsBuildProjectDiagnostics({ - FileName: "someFile", - Warnings: [{ FileName: "warningFile", LogLevel: "", Text: "", StartLine: 0, EndLine: 0, StartColumn: 0, EndColumn: 0 }], - Errors: [] - }) - ].forEach((event: OmnisharpServerMsBuildProjectDiagnostics) => { - test(`${event.constructor.name}: Logged message contains the Filename if there is atleast one error or warning`, () => { - observer.post(event); - expect(logOutput).to.contain(event.diagnostics.FileName); - }); + test(`OmnisharpServerMsBuildProjectDiagnostics: Logged message contains the Filename if there is atleast one error or warning`, () => { + let event = getOmnisharpMSBuildProjectDiagnostics("someFile", + [getMSBuildDiagnosticsMessage("warningFile", "", "", 0, 0, 0, 0)], + []); + observer.post(event); + expect(logOutput).to.contain(event.diagnostics.FileName); }); test("OmnisharpServerMsBuildProjectDiagnostics: Logged message is empty if there are no warnings and erros", () => { - let event = new OmnisharpServerMsBuildProjectDiagnostics({ - FileName: "someFile", - Warnings: [], - Errors: [] - }); + let event = getOmnisharpMSBuildProjectDiagnostics("someFile", [], []); observer.post(event); expect(logOutput).to.be.empty; }); [ - new OmnisharpServerMsBuildProjectDiagnostics({ - FileName: "someFile", - Warnings: [{ FileName: "warningFile", LogLevel: "", Text: "someWarningText", StartLine: 1, EndLine: 2, StartColumn: 3, EndColumn: 4 }], - Errors: [{ FileName: "errorFile", LogLevel: "", Text: "someErrorText", StartLine: 5, EndLine: 6, StartColumn: 7, EndColumn: 8 }] - }) + getOmnisharpMSBuildProjectDiagnostics("someFile", + [getMSBuildDiagnosticsMessage("warningFile", "", "someWarningText", 1, 2, 3, 4)], + [getMSBuildDiagnosticsMessage("errorFile", "", "someErrorText", 5, 6, 7, 8)]) ].forEach((event: OmnisharpServerMsBuildProjectDiagnostics) => { test(`${event.constructor.name}: Logged message contains the Filename, StartColumn, StartLine and Text for the diagnostic warnings`, () => { observer.post(event); diff --git a/test/unitTests/logging/OmnisharpServerStatusObserver.test.ts b/test/unitTests/logging/OmnisharpServerStatusObserver.test.ts new file mode 100644 index 0000000000..ffcbaa2fa1 --- /dev/null +++ b/test/unitTests/logging/OmnisharpServerStatusObserver.test.ts @@ -0,0 +1,29 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { should, expect } from 'chai'; +import { OmnisharpServerStatusObserver } from '../../../src/observers/OmnisharpServerStatusObserver'; + +suite('OmnisharpServerStatusObserver', () => { + suiteSetup(() => should()); + let output = ''; + setup(() => { + output = ''; + }); + let observer = new OmnisharpServerStatusObserver((message, ...items) => { + output += message; + output += items; + return { then: () => { }}; + }, (command, ...rest) => { + + }); + + test('OmnisharpServerMsBuildProjectDiagnostics', () => { + let event = getOmnisharpMSBuildProjectDiagnostics("someFile", + [getMSBuildDiagnosticsMessage("warningFile", "", "", 0, 0, 0, 0)], + []); + observer.post(event); + }); +}); \ No newline at end of file From 93274a7a8b6242c8186115a167b9ab2c219146ab Mon Sep 17 00:00:00 2001 From: Akshita Agarwal Date: Thu, 22 Mar 2018 18:12:00 -0700 Subject: [PATCH 11/56] Tried mocking setTimeOut --- src/main.ts | 2 +- .../OmnisharpServerStatusObserver.ts | 19 ++++-- .../OmnisharpServerStatusObserver.test.ts | 38 +++++++++-- .../OmnisharpStatusBarObserver.test.ts | 66 +++++++++++++++++++ 4 files changed, 113 insertions(+), 12 deletions(-) create mode 100644 test/unitTests/logging/OmnisharpStatusBarObserver.test.ts diff --git a/src/main.ts b/src/main.ts index 3ea1e16af0..8e2e951667 100644 --- a/src/main.ts +++ b/src/main.ts @@ -59,7 +59,7 @@ export async function activate(context: vscode.ExtensionContext): Promise<{ init let showWarningMessage: ShowWarningMessage = (message: string, ...items: string[]) => vscode.window.showWarningMessage(message, ...items); let executeCommand: ExecuteCommand = (command: string, ...rest: any[]) => vscode.commands.executeCommand(command, ...rest); - let omnisharpServerStatusObserver = new OmnisharpServerStatusObserver(showWarningMessage, executeCommand); + let omnisharpServerStatusObserver = new OmnisharpServerStatusObserver(showWarningMessage, executeCommand, clearTimeout, setTimeout); eventStream.subscribe(omnisharpServerStatusObserver.post); let getConfiguration: GetConfiguration = (name: string) => vscode.workspace.getConfiguration(name); diff --git a/src/observers/OmnisharpServerStatusObserver.ts b/src/observers/OmnisharpServerStatusObserver.ts index 64c93d03ba..955638fec0 100644 --- a/src/observers/OmnisharpServerStatusObserver.ts +++ b/src/observers/OmnisharpServerStatusObserver.ts @@ -7,17 +7,25 @@ import * as vscode from '../vscodeAdapter'; import * as ObservableEvent from "../omnisharp/loggingEvents"; export interface ShowWarningMessage { - (message: string, ...items: string[]): Thenable; + showWarningMessage(message: string, ...items: string[]): Thenable; } export interface ExecuteCommand { (command: string, ...rest: any[]): Thenable; } +export interface ClearTimeOut { + (timeoutId: NodeJS.Timer): void; +} + +export interface SetTimeOut { + (callback: (...args: any[]) => void, ms: number, ...args: any[]): NodeJS.Timer; +} + export class OmnisharpServerStatusObserver { private _messageHandle: NodeJS.Timer; - constructor(private showWarningMessage: ShowWarningMessage, private executeCommand: ExecuteCommand) { + constructor(private showWarningMessage: ShowWarningMessage, private executeCommand: ExecuteCommand, private clearTimeOut: ClearTimeOut, private setTimeOut : SetTimeOut ) { } public post = (event: ObservableEvent.BaseEvent) => { @@ -38,8 +46,8 @@ export class OmnisharpServerStatusObserver { } private showMessageSoon() { - clearTimeout(this._messageHandle); - this._messageHandle = setTimeout(function () { + this.clearTimeOut(this._messageHandle); + let functionToCall = () => { let message = "Some projects have trouble loading. Please review the output for more details."; this.showWarningMessage(message, { title: "Show Output", command: 'o.showOutput' }).then(value => { @@ -47,6 +55,7 @@ export class OmnisharpServerStatusObserver { this.executeCommand(value.command); } }); - }, 1500); + }; + this._messageHandle = this.setTimeOut(function\, 1500); } } \ No newline at end of file diff --git a/test/unitTests/logging/OmnisharpServerStatusObserver.test.ts b/test/unitTests/logging/OmnisharpServerStatusObserver.test.ts index ffcbaa2fa1..f8c5e503bc 100644 --- a/test/unitTests/logging/OmnisharpServerStatusObserver.test.ts +++ b/test/unitTests/logging/OmnisharpServerStatusObserver.test.ts @@ -5,6 +5,8 @@ import { should, expect } from 'chai'; import { OmnisharpServerStatusObserver } from '../../../src/observers/OmnisharpServerStatusObserver'; +import { resolve } from 'path'; +import { getOmnisharpMSBuildProjectDiagnostics, getMSBuildDiagnosticsMessage } from './Fakes'; suite('OmnisharpServerStatusObserver', () => { suiteSetup(() => should()); @@ -12,18 +14,42 @@ suite('OmnisharpServerStatusObserver', () => { setup(() => { output = ''; }); - let observer = new OmnisharpServerStatusObserver((message, ...items) => { + + let warningFunction = (message, ...items) => { output += message; output += items; - return { then: () => { }}; - }, (command, ...rest) => { - - }); + return Promise.resolve("hello"); + }; + + let executeCommand = (command, ...rest) => { + return new Promise((resolve, reject) => { resolve(); }); + }; + + let clearTimeOut = (timeoutid: NodeJS.Timer) => { + output += timeoutid; + }; + + let setTimeout = (callback: (...args: any[]) => void, ms: number, ...args: any[]) => { + callback(); + let x: NodeJS.Timer; + return x; + }; + + let observer = new OmnisharpServerStatusObserver(warningFunction, executeCommand, clearTimeOut, setTimeout); - test('OmnisharpServerMsBuildProjectDiagnostics', () => { + test('OmnisharpServerMsBuildProjectDiagnostics: No action is taken if the errors arry is empty', () => { let event = getOmnisharpMSBuildProjectDiagnostics("someFile", [getMSBuildDiagnosticsMessage("warningFile", "", "", 0, 0, 0, 0)], []); observer.post(event); + expect(output).to.be.empty; + }); + + test('OmnisharpServerMsBuildProjectDiagnostics: No action is taken if the errors arry is empty', () => { + let event = getOmnisharpMSBuildProjectDiagnostics("someFile", + [getMSBuildDiagnosticsMessage("warningFile", "", "", 1, 2, 3, 4)], + [getMSBuildDiagnosticsMessage("errorFile", "", "", 5, 6, 7, 8)]); + observer.post(event); + expect(output).to.be.empty; }); }); \ No newline at end of file diff --git a/test/unitTests/logging/OmnisharpStatusBarObserver.test.ts b/test/unitTests/logging/OmnisharpStatusBarObserver.test.ts new file mode 100644 index 0000000000..92e942644b --- /dev/null +++ b/test/unitTests/logging/OmnisharpStatusBarObserver.test.ts @@ -0,0 +1,66 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { should, expect } from 'chai'; +import { OmnisharpStatusBarObserver, GetActiveTextEditor, Match } from '../../../src/observers/OmnisharpStatusBarObserver'; +import * as vscode from '../../../src/vscodeAdapter'; +import { OmnisharpServerOnServerError, OmnisharpOnBeforeServerInstall, OmnisharpOnBeforeServerStart, OmnisharpOnMultipleLaunchTargets } from '../../../src/omnisharp/loggingEvents'; + +suite('OmnisharpServerStatusObserver', () => { + suiteSetup(() => should()); + let output = ''; + let showCalled: boolean; + setup(() => { + output = ''; + showCalled = false; + }); + + let getActiveTextEditor: GetActiveTextEditor = () => { return { document: "hello" }; }; + let matchFunction: Match = (selector: vscode.DocumentSelector, document: any) => { return 2; }; + let statusBar = { + show: () => { showCalled = true;} + }; + + let observer = new OmnisharpStatusBarObserver(getActiveTextEditor, matchFunction, statusBar); + + test('OnServerError: If there is no project status, default status should be shown which includes error and flame', () => { + let event = new OmnisharpServerOnServerError("someError"); + observer.post(event); + expect(showCalled).to.be.true; + expect(statusBar.text.toLowerCase()).to.contain("error"); + expect(statusBar.text).to.contain("$(flame)"); //omnisharp flame + }); + + test('OnBeforeServerInstall: If there is no project status, default status should be shown which includes install and flame', () => { + let event = new OmnisharpOnBeforeServerInstall(); + observer.post(event); + expect(showCalled).to.be.true; + expect(statusBar.text.toLowerCase()).to.contain("install"); + expect(statusBar.text).to.contain("$(flame)"); + }); + + test('OnBeforeServerStart: If there is no project status, default status should be shown which includes start and flame', () => { + let event = new OmnisharpOnBeforeServerStart(); + observer.post(event); + expect(showCalled).to.be.true; + expect(statusBar.text.toLowerCase()).to.contain("start"); + expect(statusBar.text).to.contain("$(flame)"); + }); + + test('OnMultipleLaunchTargets: If there is no project status, default status should be shown which includes error and flame', () => { + let event = new OmnisharpOnMultipleLaunchTargets([]); + observer.post(event); + expect(showCalled).to.be.true; + expect(statusBar.text.toLowerCase()).to.contain("select project"); + expect(statusBar.text).to.contain("$(flame)"); + }); + + + + + + + +}); \ No newline at end of file From ad3e810db6a6ebf94b18499c466795c043765d18 Mon Sep 17 00:00:00 2001 From: Akshita Agarwal Date: Fri, 23 Mar 2018 08:16:15 -0700 Subject: [PATCH 12/56] warning message changes --- src/observers/OmnisharpServerStatusObserver.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/observers/OmnisharpServerStatusObserver.ts b/src/observers/OmnisharpServerStatusObserver.ts index 955638fec0..4303a12cd0 100644 --- a/src/observers/OmnisharpServerStatusObserver.ts +++ b/src/observers/OmnisharpServerStatusObserver.ts @@ -7,7 +7,7 @@ import * as vscode from '../vscodeAdapter'; import * as ObservableEvent from "../omnisharp/loggingEvents"; export interface ShowWarningMessage { - showWarningMessage(message: string, ...items: string[]): Thenable; + (message: string, options: vscode.MessageOptions, ...items: string[]): Thenable; } export interface ExecuteCommand { @@ -56,6 +56,6 @@ export class OmnisharpServerStatusObserver { } }); }; - this._messageHandle = this.setTimeOut(function\, 1500); + this._messageHandle = this.setTimeOut(functionToCall, 1500); } } \ No newline at end of file From c70bff1567bb10de2881fa4c503f965a8106ccc5 Mon Sep 17 00:00:00 2001 From: Akshita Agarwal Date: Fri, 23 Mar 2018 08:23:09 -0700 Subject: [PATCH 13/56] warning message correct definition --- src/observers/OmnisharpServerStatusObserver.ts | 3 +-- src/vscodeAdapter.ts | 16 +++++++++++++++- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/observers/OmnisharpServerStatusObserver.ts b/src/observers/OmnisharpServerStatusObserver.ts index 4303a12cd0..5f6e240ed8 100644 --- a/src/observers/OmnisharpServerStatusObserver.ts +++ b/src/observers/OmnisharpServerStatusObserver.ts @@ -7,7 +7,7 @@ import * as vscode from '../vscodeAdapter'; import * as ObservableEvent from "../omnisharp/loggingEvents"; export interface ShowWarningMessage { - (message: string, options: vscode.MessageOptions, ...items: string[]): Thenable; + (message: string, ...items: T[]): Thenable; } export interface ExecuteCommand { @@ -48,7 +48,6 @@ export class OmnisharpServerStatusObserver { private showMessageSoon() { this.clearTimeOut(this._messageHandle); let functionToCall = () => { - let message = "Some projects have trouble loading. Please review the output for more details."; this.showWarningMessage(message, { title: "Show Output", command: 'o.showOutput' }).then(value => { if (value) { diff --git a/src/vscodeAdapter.ts b/src/vscodeAdapter.ts index 01f71c6a66..37f16b0442 100644 --- a/src/vscodeAdapter.ts +++ b/src/vscodeAdapter.ts @@ -298,4 +298,18 @@ export interface TextEditor { * The document associated with this text editor. The document will be the same for the entire lifetime of this text editor. */ document: any; -} \ No newline at end of file +} + +export interface MessageItem { + + /** + * A short title like 'Retry', 'Open Log' etc. + */ + title: string; + + /** + * Indicates that this item replaces the default + * 'Close' action. + */ + isCloseAffordance?: boolean; +} From d1cf7728622443cd53b82cfc3ebb3a363bcad305 Mon Sep 17 00:00:00 2001 From: Akshita Agarwal Date: Fri, 23 Mar 2018 14:31:41 -0700 Subject: [PATCH 14/56] virtual time running --- src/main.ts | 8 +-- .../OmnisharpServerStatusObserver.ts | 47 ++++++-------- src/omnisharp/server.ts | 6 +- .../OmnisharpServerStatusObserver.test.ts | 64 +++++++++++-------- 4 files changed, 67 insertions(+), 58 deletions(-) diff --git a/src/main.ts b/src/main.ts index 8e2e951667..1199c28e78 100644 --- a/src/main.ts +++ b/src/main.ts @@ -21,7 +21,7 @@ import { DotnetLoggerObserver } from './observers/DotnetLoggerObserver'; import { OmnisharpDebugModeLoggerObserver } from './observers/OmnisharpDebugModeLoggerObserver'; import { ActivationFailure, ActiveTextEditorChanged } from './omnisharp/loggingEvents'; import { EventStream } from './EventStream'; -import { OmnisharpServerStatusObserver, ExecuteCommand, ShowWarningMessage } from './observers/OmnisharpServerStatusObserver'; +import { OmnisharpServerStatusObserver, ExecuteCommand, ShowWarningMessage, MessageItemWithCommand } from './observers/OmnisharpServerStatusObserver'; import { InformationMessageObserver, ShowInformationMessage, GetConfiguration, WorkspaceAsRelativePath } from './observers/InformationMessageObserver'; import { GetActiveTextEditor, OmnisharpStatusBarObserver, Match } from './observers/OmnisharpStatusBarObserver'; import { TextEditorAdapter } from './textEditorAdapter'; @@ -57,9 +57,9 @@ export async function activate(context: vscode.ExtensionContext): Promise<{ init eventStream.subscribe(omnisharpLogObserver.post); eventStream.subscribe(omnisharpChannelObserver.post); - let showWarningMessage: ShowWarningMessage = (message: string, ...items: string[]) => vscode.window.showWarningMessage(message, ...items); - let executeCommand: ExecuteCommand = (command: string, ...rest: any[]) => vscode.commands.executeCommand(command, ...rest); - let omnisharpServerStatusObserver = new OmnisharpServerStatusObserver(showWarningMessage, executeCommand, clearTimeout, setTimeout); + let showWarningMessage: ShowWarningMessage = (message: string, ...items: T[]) => vscode.window.showWarningMessage(message, ...items); + let executeCommand: ExecuteCommand = (command: string, ...rest: any[]) => vscode.commands.executeCommand(command, ...rest); + let omnisharpServerStatusObserver = new OmnisharpServerStatusObserver(showWarningMessage, executeCommand); eventStream.subscribe(omnisharpServerStatusObserver.post); let getConfiguration: GetConfiguration = (name: string) => vscode.workspace.getConfiguration(name); diff --git a/src/observers/OmnisharpServerStatusObserver.ts b/src/observers/OmnisharpServerStatusObserver.ts index 5f6e240ed8..c8a2ab1ec1 100644 --- a/src/observers/OmnisharpServerStatusObserver.ts +++ b/src/observers/OmnisharpServerStatusObserver.ts @@ -5,33 +5,40 @@ import * as vscode from '../vscodeAdapter'; import * as ObservableEvent from "../omnisharp/loggingEvents"; +import { Scheduler, Subject } from 'rx'; -export interface ShowWarningMessage { - (message: string, ...items: T[]): Thenable; +export interface MessageItemWithCommand extends vscode.MessageItem{ + command: string; } -export interface ExecuteCommand { - (command: string, ...rest: any[]): Thenable; +export interface ShowWarningMessage { + (message: string, ...items: T[]): Thenable; } -export interface ClearTimeOut { - (timeoutId: NodeJS.Timer): void; -} - -export interface SetTimeOut { - (callback: (...args: any[]) => void, ms: number, ...args: any[]): NodeJS.Timer; +export interface ExecuteCommand { + (command: string, ...rest: any[]): Thenable; } export class OmnisharpServerStatusObserver { private _messageHandle: NodeJS.Timer; + private subject: Subject; - constructor(private showWarningMessage: ShowWarningMessage, private executeCommand: ExecuteCommand, private clearTimeOut: ClearTimeOut, private setTimeOut : SetTimeOut ) { + constructor(private showWarningMessage: ShowWarningMessage, private executeCommand: ExecuteCommand, scheduler?: Scheduler) { + this.subject = new Subject(); + this.subject.debounce(1500).subscribe(async (event) => { + let message = "Some projects have trouble loading. Please review the output for more details."; + let value = await this.showWarningMessage(message, { title: "Show Output", command: 'o.showOutput' }); + if (value) { + console.log(value); + this.executeCommand(value.command); + } + }); } public post = (event: ObservableEvent.BaseEvent) => { switch (event.constructor.name) { case ObservableEvent.OmnisharpServerOnError.name: - this.showMessageSoon(); + this.subject.onNext(event); break; case ObservableEvent.OmnisharpServerMsBuildProjectDiagnostics.name: this.handleOmnisharpServerMsBuildProjectDiagnostics(event); @@ -41,20 +48,8 @@ export class OmnisharpServerStatusObserver { private handleOmnisharpServerMsBuildProjectDiagnostics(event: ObservableEvent.OmnisharpServerMsBuildProjectDiagnostics) { if (event.diagnostics.Errors.length > 0) { - this.showMessageSoon(); + //this.showMessageSoon(); + this.subject.onNext(event); } } - - private showMessageSoon() { - this.clearTimeOut(this._messageHandle); - let functionToCall = () => { - let message = "Some projects have trouble loading. Please review the output for more details."; - this.showWarningMessage(message, { title: "Show Output", command: 'o.showOutput' }).then(value => { - if (value) { - this.executeCommand(value.command); - } - }); - }; - this._messageHandle = this.setTimeOut(functionToCall, 1500); - } } \ No newline at end of file diff --git a/src/omnisharp/server.ts b/src/omnisharp/server.ts index eb87426b46..6dd8f2c62f 100644 --- a/src/omnisharp/server.ts +++ b/src/omnisharp/server.ts @@ -261,9 +261,9 @@ export class OmniSharpServer { this.eventStream.post(new ObservableEvents.OmnisharpOnBeforeServerInstall()) )); - disposables.add(this.onBeforeServerStart(() => - this.eventStream.post(new ObservableEvents.OmnisharpOnBeforeServerStart()) - )); + disposables.add(this.onBeforeServerStart(() => { + this.eventStream.post(new ObservableEvents.OmnisharpOnBeforeServerStart()); + })); disposables.add(this.onServerStop(() => this.eventStream.post(new ObservableEvents.OmnisharpServerOnStop()) diff --git a/test/unitTests/logging/OmnisharpServerStatusObserver.test.ts b/test/unitTests/logging/OmnisharpServerStatusObserver.test.ts index f8c5e503bc..33b996db19 100644 --- a/test/unitTests/logging/OmnisharpServerStatusObserver.test.ts +++ b/test/unitTests/logging/OmnisharpServerStatusObserver.test.ts @@ -4,40 +4,49 @@ *--------------------------------------------------------------------------------------------*/ import { should, expect } from 'chai'; -import { OmnisharpServerStatusObserver } from '../../../src/observers/OmnisharpServerStatusObserver'; +import * as rx from 'rx'; +import { OmnisharpServerStatusObserver, ShowWarningMessage, MessageItemWithCommand, ExecuteCommand } from '../../../src/observers/OmnisharpServerStatusObserver'; import { resolve } from 'path'; import { getOmnisharpMSBuildProjectDiagnostics, getMSBuildDiagnosticsMessage } from './Fakes'; +import * as vscode from '../../../src/vscodeAdapter'; suite('OmnisharpServerStatusObserver', () => { suiteSetup(() => should()); let output = ''; - setup(() => { - output = ''; - }); - - let warningFunction = (message, ...items) => { + let scheduler: rx.HistoricalScheduler; + let observer: OmnisharpServerStatusObserver; + + let warningFunction : ShowWarningMessage = (message, ...items) => { + output += "show warning message called"; output += message; - output += items; - return Promise.resolve("hello"); - }; + items.forEach(element => { + output += element.title; + output += element.command; + }); - let executeCommand = (command, ...rest) => { - return new Promise((resolve, reject) => { resolve(); }); - }; + let testMessage : MessageItemWithCommand = { + title: "myTitle", + command: "myCommand" + }; + + return Promise.resolve(testMessage); + }; - let clearTimeOut = (timeoutid: NodeJS.Timer) => { - output += timeoutid; + let executeCommand: ExecuteCommand = (command, ...rest) => { + output += "execute command called"; + output += command; + return Promise.resolve("execute command resolved"); }; + + setup(() => { + output = ''; + scheduler = new rx.HistoricalScheduler(0, (x, y) => { + return x > y ? 1 : -1; + }); + observer = new OmnisharpServerStatusObserver(warningFunction, executeCommand, scheduler); + }); - let setTimeout = (callback: (...args: any[]) => void, ms: number, ...args: any[]) => { - callback(); - let x: NodeJS.Timer; - return x; - }; - - let observer = new OmnisharpServerStatusObserver(warningFunction, executeCommand, clearTimeOut, setTimeout); - - test('OmnisharpServerMsBuildProjectDiagnostics: No action is taken if the errors arry is empty', () => { + test('OmnisharpServerMsBuildProjectDiagnostics: No action is taken if the errors array is empty', () => { let event = getOmnisharpMSBuildProjectDiagnostics("someFile", [getMSBuildDiagnosticsMessage("warningFile", "", "", 0, 0, 0, 0)], []); @@ -45,11 +54,16 @@ suite('OmnisharpServerStatusObserver', () => { expect(output).to.be.empty; }); - test('OmnisharpServerMsBuildProjectDiagnostics: No action is taken if the errors arry is empty', () => { + test('OmnisharpServerMsBuildProjectDiagnostics: No action is taken if the errors array is empty', () => { let event = getOmnisharpMSBuildProjectDiagnostics("someFile", [getMSBuildDiagnosticsMessage("warningFile", "", "", 1, 2, 3, 4)], [getMSBuildDiagnosticsMessage("errorFile", "", "", 5, 6, 7, 8)]); observer.post(event); - expect(output).to.be.empty; + scheduler.advanceBy(1500); + console.log(output); + expect(output).to.contain("Show Output"); + expect(output).to.contain("o.showOutput"); + expect(output).to.contain("show warning message called"); + expect(output).to.contain("execute command called"); }); }); \ No newline at end of file From 4377f689981bb233757a4e7f55cd1ccf2d35d4c0 Mon Sep 17 00:00:00 2001 From: Akshita Agarwal Date: Fri, 23 Mar 2018 17:58:31 -0700 Subject: [PATCH 15/56] done called multiple time --- .../OmnisharpServerStatusObserver.ts | 15 ++-- test/unitTests/logging/Fakes.ts | 17 ++++- .../logging/OmnisharpLoggerObserver.test.ts | 8 +- .../OmnisharpServerStatusObserver.test.ts | 75 ++++++++++++++----- 4 files changed, 81 insertions(+), 34 deletions(-) diff --git a/src/observers/OmnisharpServerStatusObserver.ts b/src/observers/OmnisharpServerStatusObserver.ts index c8a2ab1ec1..a0b0efc54d 100644 --- a/src/observers/OmnisharpServerStatusObserver.ts +++ b/src/observers/OmnisharpServerStatusObserver.ts @@ -7,7 +7,7 @@ import * as vscode from '../vscodeAdapter'; import * as ObservableEvent from "../omnisharp/loggingEvents"; import { Scheduler, Subject } from 'rx'; -export interface MessageItemWithCommand extends vscode.MessageItem{ +export interface MessageItemWithCommand extends vscode.MessageItem { command: string; } @@ -25,13 +25,13 @@ export class OmnisharpServerStatusObserver { constructor(private showWarningMessage: ShowWarningMessage, private executeCommand: ExecuteCommand, scheduler?: Scheduler) { this.subject = new Subject(); - this.subject.debounce(1500).subscribe(async (event) => { + this.subject.debounce(1500, scheduler).subscribe(event => { let message = "Some projects have trouble loading. Please review the output for more details."; - let value = await this.showWarningMessage(message, { title: "Show Output", command: 'o.showOutput' }); - if (value) { - console.log(value); - this.executeCommand(value.command); - } + this.showWarningMessage(message, { title: "Show Output", command: 'o.showOutput' }).then(value => { + if (value) { + this.executeCommand(value.command); + } + }); }); } @@ -48,7 +48,6 @@ export class OmnisharpServerStatusObserver { private handleOmnisharpServerMsBuildProjectDiagnostics(event: ObservableEvent.OmnisharpServerMsBuildProjectDiagnostics) { if (event.diagnostics.Errors.length > 0) { - //this.showMessageSoon(); this.subject.onNext(event); } } diff --git a/test/unitTests/logging/Fakes.ts b/test/unitTests/logging/Fakes.ts index 87ccded7e4..898c7d2919 100644 --- a/test/unitTests/logging/Fakes.ts +++ b/test/unitTests/logging/Fakes.ts @@ -6,7 +6,7 @@ import * as vscode from '../../../src/vscodeAdapter'; import { ITelemetryReporter } from '../../../src/observers/TelemetryObserver'; import { MSBuildDiagnosticsMessage } from '../../../src/omnisharp/protocol'; -import { OmnisharpServerMsBuildProjectDiagnostics } from '../../../src/omnisharp/loggingEvents'; +import { OmnisharpServerMsBuildProjectDiagnostics, OmnisharpServerOnError } from '../../../src/omnisharp/loggingEvents'; export const getNullChannel = (): vscode.OutputChannel => { let returnChannel: vscode.OutputChannel = { @@ -29,7 +29,7 @@ export const getNullTelemetryReporter = (): ITelemetryReporter => { return reporter; }; -export function getOmnisharpMSBuildProjectDiagnostics(fileName: string, warnings: MSBuildDiagnosticsMessage[], errors: MSBuildDiagnosticsMessage[]): OmnisharpServerMsBuildProjectDiagnostics { +export function getOmnisharpMSBuildProjectDiagnosticsEvent(fileName: string, warnings: MSBuildDiagnosticsMessage[], errors: MSBuildDiagnosticsMessage[]): OmnisharpServerMsBuildProjectDiagnostics { return new OmnisharpServerMsBuildProjectDiagnostics({ FileName: fileName, Warnings: warnings, @@ -43,7 +43,7 @@ export function getMSBuildDiagnosticsMessage(logLevel: string, startLine: number, startColumn: number, endLine: number, - endColumn: number): MSBuildDiagnosticsMessage{ + endColumn: number): MSBuildDiagnosticsMessage { return { LogLevel: logLevel, FileName: fileName, @@ -53,4 +53,13 @@ export function getMSBuildDiagnosticsMessage(logLevel: string, EndLine: endLine, EndColumn: endColumn }; -} \ No newline at end of file +} + +export function getOmnisharpServerOnErrorEvent(text: string, fileName: string, line: number, column: number): OmnisharpServerOnError { + return new OmnisharpServerOnError({ + Text: text, + FileName: fileName, + Line: line, + Column: column + }); +} \ No newline at end of file diff --git a/test/unitTests/logging/OmnisharpLoggerObserver.test.ts b/test/unitTests/logging/OmnisharpLoggerObserver.test.ts index 40228aadb9..fa295254ae 100644 --- a/test/unitTests/logging/OmnisharpLoggerObserver.test.ts +++ b/test/unitTests/logging/OmnisharpLoggerObserver.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { should, expect } from 'chai'; -import { getNullChannel, getMSBuildDiagnosticsMessage, getOmnisharpMSBuildProjectDiagnostics } from './Fakes'; +import { getNullChannel, getMSBuildDiagnosticsMessage, getOmnisharpMSBuildProjectDiagnosticsEvent } from './Fakes'; import { OmnisharpLoggerObserver } from '../../../src/observers/OmnisharpLoggerObserver'; import { OmnisharpServerMsBuildProjectDiagnostics, EventWithMessage, OmnisharpServerOnStdErr, OmnisharpServerMessage, OmnisharpServerOnServerError, OmnisharpInitialisation, OmnisharpLaunch, OmnisharpServerOnError, OmnisharpFailure, OmnisharpEventPacketReceived } from '../../../src/omnisharp/loggingEvents'; import { MSBuildDiagnosticsMessage } from '../../../src/omnisharp/protocol'; @@ -22,7 +22,7 @@ suite("OmnisharpLoggerObserver", () => { }); test(`OmnisharpServerMsBuildProjectDiagnostics: Logged message contains the Filename if there is atleast one error or warning`, () => { - let event = getOmnisharpMSBuildProjectDiagnostics("someFile", + let event = getOmnisharpMSBuildProjectDiagnosticsEvent("someFile", [getMSBuildDiagnosticsMessage("warningFile", "", "", 0, 0, 0, 0)], []); observer.post(event); @@ -30,13 +30,13 @@ suite("OmnisharpLoggerObserver", () => { }); test("OmnisharpServerMsBuildProjectDiagnostics: Logged message is empty if there are no warnings and erros", () => { - let event = getOmnisharpMSBuildProjectDiagnostics("someFile", [], []); + let event = getOmnisharpMSBuildProjectDiagnosticsEvent("someFile", [], []); observer.post(event); expect(logOutput).to.be.empty; }); [ - getOmnisharpMSBuildProjectDiagnostics("someFile", + getOmnisharpMSBuildProjectDiagnosticsEvent("someFile", [getMSBuildDiagnosticsMessage("warningFile", "", "someWarningText", 1, 2, 3, 4)], [getMSBuildDiagnosticsMessage("errorFile", "", "someErrorText", 5, 6, 7, 8)]) ].forEach((event: OmnisharpServerMsBuildProjectDiagnostics) => { diff --git a/test/unitTests/logging/OmnisharpServerStatusObserver.test.ts b/test/unitTests/logging/OmnisharpServerStatusObserver.test.ts index 33b996db19..fad5c9b14f 100644 --- a/test/unitTests/logging/OmnisharpServerStatusObserver.test.ts +++ b/test/unitTests/logging/OmnisharpServerStatusObserver.test.ts @@ -7,16 +7,18 @@ import { should, expect } from 'chai'; import * as rx from 'rx'; import { OmnisharpServerStatusObserver, ShowWarningMessage, MessageItemWithCommand, ExecuteCommand } from '../../../src/observers/OmnisharpServerStatusObserver'; import { resolve } from 'path'; -import { getOmnisharpMSBuildProjectDiagnostics, getMSBuildDiagnosticsMessage } from './Fakes'; +import { getOmnisharpMSBuildProjectDiagnosticsEvent, getMSBuildDiagnosticsMessage, getOmnisharpServerOnErrorEvent } from './Fakes'; import * as vscode from '../../../src/vscodeAdapter'; +import { BaseEvent } from '../../../src/omnisharp/loggingEvents'; suite('OmnisharpServerStatusObserver', () => { suiteSetup(() => should()); let output = ''; let scheduler: rx.HistoricalScheduler; let observer: OmnisharpServerStatusObserver; + let commandExecuted: () => void; - let warningFunction : ShowWarningMessage = (message, ...items) => { + let warningFunction: ShowWarningMessage = (message, ...items) => { output += "show warning message called"; output += message; items.forEach(element => { @@ -24,20 +26,24 @@ suite('OmnisharpServerStatusObserver', () => { output += element.command; }); - let testMessage : MessageItemWithCommand = { + let testMessage: MessageItemWithCommand = { title: "myTitle", command: "myCommand" }; - + return Promise.resolve(testMessage); - }; + }; let executeCommand: ExecuteCommand = (command, ...rest) => { output += "execute command called"; output += command; - return Promise.resolve("execute command resolved"); + console.log(output); + return new Promise(resolve => { + resolve("execute command resolved"); + commandExecuted(); + }); }; - + setup(() => { output = ''; scheduler = new rx.HistoricalScheduler(0, (x, y) => { @@ -47,23 +53,56 @@ suite('OmnisharpServerStatusObserver', () => { }); test('OmnisharpServerMsBuildProjectDiagnostics: No action is taken if the errors array is empty', () => { - let event = getOmnisharpMSBuildProjectDiagnostics("someFile", + let event = getOmnisharpMSBuildProjectDiagnosticsEvent("someFile", [getMSBuildDiagnosticsMessage("warningFile", "", "", 0, 0, 0, 0)], []); observer.post(event); expect(output).to.be.empty; }); - test('OmnisharpServerMsBuildProjectDiagnostics: No action is taken if the errors array is empty', () => { - let event = getOmnisharpMSBuildProjectDiagnostics("someFile", + + [ + getOmnisharpMSBuildProjectDiagnosticsEvent("someFile", [getMSBuildDiagnosticsMessage("warningFile", "", "", 1, 2, 3, 4)], - [getMSBuildDiagnosticsMessage("errorFile", "", "", 5, 6, 7, 8)]); - observer.post(event); - scheduler.advanceBy(1500); - console.log(output); - expect(output).to.contain("Show Output"); - expect(output).to.contain("o.showOutput"); - expect(output).to.contain("show warning message called"); - expect(output).to.contain("execute command called"); + [getMSBuildDiagnosticsMessage("errorFile", "", "", 5, 6, 7, 8)]), + getOmnisharpServerOnErrorEvent("someText", "someFile", 1, 2) + ].forEach((event: BaseEvent) => { + test(`${event.constructor.name}: Debouce function`, () => { + let event = getOmnisharpMSBuildProjectDiagnosticsEvent("someFile", + [getMSBuildDiagnosticsMessage("warningFile", "", "", 1, 2, 3, 4)], + [getMSBuildDiagnosticsMessage("errorFile", "", "", 5, 6, 7, 8)]); + observer.post(event); + scheduler.advanceBy(1000); //since the debounce time is 1500 no output should be there + expect(output).to.be.empty; + }); + + test(`${event.constructor.name}: If a event is fired within 1500ms the first event is debounced`, () => { + observer.post(event); + scheduler.advanceBy(1000); + expect(output).to.be.empty; + observer.post(event); + scheduler.advanceBy(500); + expect(output).to.be.empty; + scheduler.advanceBy(1000); + expect(output).to.not.be.empty; + //once there is a silence for 1500 ms the function will be invoked + }); + + test(`${event.constructor.name}: Show warning message and execute command are called`, (done) => { + let event = getOmnisharpMSBuildProjectDiagnosticsEvent("someFile", + [getMSBuildDiagnosticsMessage("warningFile", "", "", 1, 2, 3, 4)], + [getMSBuildDiagnosticsMessage("errorFile", "", "", 5, 6, 7, 8)]); + + commandExecuted = () => { + expect(output).to.contain("Show Output"); + expect(output).to.contain("o.showOutput"); + expect(output).to.contain("show warning message called"); + expect(output).to.contain("execute command called"); + done(); + }; + + observer.post(event); + scheduler.advanceBy(1500); + }); }); }); \ No newline at end of file From cbf33eb5d662f8ee45f76819f2f42deb77a6c185 Mon Sep 17 00:00:00 2001 From: Akshita Agarwal Date: Sat, 24 Mar 2018 13:47:42 -0700 Subject: [PATCH 16/56] renamed observer and subject --- src/main.ts | 4 ++-- ...sObserver.ts => WarningMessageObserver.ts} | 24 +++++++++---------- ...test.ts => WarningMessageObserver.test.ts} | 6 ++--- 3 files changed, 17 insertions(+), 17 deletions(-) rename src/observers/{OmnisharpServerStatusObserver.ts => WarningMessageObserver.ts} (69%) rename test/unitTests/logging/{OmnisharpServerStatusObserver.test.ts => WarningMessageObserver.test.ts} (93%) diff --git a/src/main.ts b/src/main.ts index 1199c28e78..d7488cced3 100644 --- a/src/main.ts +++ b/src/main.ts @@ -21,7 +21,7 @@ import { DotnetLoggerObserver } from './observers/DotnetLoggerObserver'; import { OmnisharpDebugModeLoggerObserver } from './observers/OmnisharpDebugModeLoggerObserver'; import { ActivationFailure, ActiveTextEditorChanged } from './omnisharp/loggingEvents'; import { EventStream } from './EventStream'; -import { OmnisharpServerStatusObserver, ExecuteCommand, ShowWarningMessage, MessageItemWithCommand } from './observers/OmnisharpServerStatusObserver'; +import { WarningMessageObserver, ExecuteCommand, ShowWarningMessage, MessageItemWithCommand } from './observers/WarningMessageObserver'; import { InformationMessageObserver, ShowInformationMessage, GetConfiguration, WorkspaceAsRelativePath } from './observers/InformationMessageObserver'; import { GetActiveTextEditor, OmnisharpStatusBarObserver, Match } from './observers/OmnisharpStatusBarObserver'; import { TextEditorAdapter } from './textEditorAdapter'; @@ -59,7 +59,7 @@ export async function activate(context: vscode.ExtensionContext): Promise<{ init let showWarningMessage: ShowWarningMessage = (message: string, ...items: T[]) => vscode.window.showWarningMessage(message, ...items); let executeCommand: ExecuteCommand = (command: string, ...rest: any[]) => vscode.commands.executeCommand(command, ...rest); - let omnisharpServerStatusObserver = new OmnisharpServerStatusObserver(showWarningMessage, executeCommand); + let omnisharpServerStatusObserver = new WarningMessageObserver(showWarningMessage, executeCommand); eventStream.subscribe(omnisharpServerStatusObserver.post); let getConfiguration: GetConfiguration = (name: string) => vscode.workspace.getConfiguration(name); diff --git a/src/observers/OmnisharpServerStatusObserver.ts b/src/observers/WarningMessageObserver.ts similarity index 69% rename from src/observers/OmnisharpServerStatusObserver.ts rename to src/observers/WarningMessageObserver.ts index a0b0efc54d..dbe69dec7c 100644 --- a/src/observers/OmnisharpServerStatusObserver.ts +++ b/src/observers/WarningMessageObserver.ts @@ -4,8 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from '../vscodeAdapter'; -import * as ObservableEvent from "../omnisharp/loggingEvents"; import { Scheduler, Subject } from 'rx'; +import { BaseEvent, OmnisharpServerOnError, OmnisharpServerMsBuildProjectDiagnostics } from '../omnisharp/loggingEvents'; export interface MessageItemWithCommand extends vscode.MessageItem { command: string; @@ -19,13 +19,13 @@ export interface ExecuteCommand { (command: string, ...rest: any[]): Thenable; } -export class OmnisharpServerStatusObserver { +export class WarningMessageObserver { private _messageHandle: NodeJS.Timer; - private subject: Subject; + private warningMessageDebouncer: Subject; constructor(private showWarningMessage: ShowWarningMessage, private executeCommand: ExecuteCommand, scheduler?: Scheduler) { - this.subject = new Subject(); - this.subject.debounce(1500, scheduler).subscribe(event => { + this.warningMessageDebouncer = new Subject(); + this.warningMessageDebouncer.debounce(1500, scheduler).subscribe(event => { let message = "Some projects have trouble loading. Please review the output for more details."; this.showWarningMessage(message, { title: "Show Output", command: 'o.showOutput' }).then(value => { if (value) { @@ -35,20 +35,20 @@ export class OmnisharpServerStatusObserver { }); } - public post = (event: ObservableEvent.BaseEvent) => { + public post = (event: BaseEvent) => { switch (event.constructor.name) { - case ObservableEvent.OmnisharpServerOnError.name: - this.subject.onNext(event); + case OmnisharpServerOnError.name: + this.warningMessageDebouncer.onNext(event); break; - case ObservableEvent.OmnisharpServerMsBuildProjectDiagnostics.name: - this.handleOmnisharpServerMsBuildProjectDiagnostics(event); + case OmnisharpServerMsBuildProjectDiagnostics.name: + this.handleOmnisharpServerMsBuildProjectDiagnostics(event); break; } } - private handleOmnisharpServerMsBuildProjectDiagnostics(event: ObservableEvent.OmnisharpServerMsBuildProjectDiagnostics) { + private handleOmnisharpServerMsBuildProjectDiagnostics(event: OmnisharpServerMsBuildProjectDiagnostics) { if (event.diagnostics.Errors.length > 0) { - this.subject.onNext(event); + this.warningMessageDebouncer.onNext(event); } } } \ No newline at end of file diff --git a/test/unitTests/logging/OmnisharpServerStatusObserver.test.ts b/test/unitTests/logging/WarningMessageObserver.test.ts similarity index 93% rename from test/unitTests/logging/OmnisharpServerStatusObserver.test.ts rename to test/unitTests/logging/WarningMessageObserver.test.ts index fad5c9b14f..b4e656edd9 100644 --- a/test/unitTests/logging/OmnisharpServerStatusObserver.test.ts +++ b/test/unitTests/logging/WarningMessageObserver.test.ts @@ -5,7 +5,7 @@ import { should, expect } from 'chai'; import * as rx from 'rx'; -import { OmnisharpServerStatusObserver, ShowWarningMessage, MessageItemWithCommand, ExecuteCommand } from '../../../src/observers/OmnisharpServerStatusObserver'; +import { WarningMessageObserver, ShowWarningMessage, MessageItemWithCommand, ExecuteCommand } from '../../../src/observers/WarningMessageObserver'; import { resolve } from 'path'; import { getOmnisharpMSBuildProjectDiagnosticsEvent, getMSBuildDiagnosticsMessage, getOmnisharpServerOnErrorEvent } from './Fakes'; import * as vscode from '../../../src/vscodeAdapter'; @@ -15,7 +15,7 @@ suite('OmnisharpServerStatusObserver', () => { suiteSetup(() => should()); let output = ''; let scheduler: rx.HistoricalScheduler; - let observer: OmnisharpServerStatusObserver; + let observer: WarningMessageObserver; let commandExecuted: () => void; let warningFunction: ShowWarningMessage = (message, ...items) => { @@ -49,7 +49,7 @@ suite('OmnisharpServerStatusObserver', () => { scheduler = new rx.HistoricalScheduler(0, (x, y) => { return x > y ? 1 : -1; }); - observer = new OmnisharpServerStatusObserver(warningFunction, executeCommand, scheduler); + observer = new WarningMessageObserver(warningFunction, executeCommand, scheduler); }); test('OmnisharpServerMsBuildProjectDiagnostics: No action is taken if the errors array is empty', () => { From f7f5db913e07c025c967b650e9f7e036fbe96071 Mon Sep 17 00:00:00 2001 From: Akshita Agarwal Date: Mon, 26 Mar 2018 11:28:28 -0700 Subject: [PATCH 17/56] changes --- src/main.ts | 2 +- src/observers/OmnisharpStatusBarObserver.ts | 3 +-- src/observers/WarningMessageObserver.ts | 1 - src/omnisharp/loggingEvents.ts | 1 - .../logging/WarningMessageObserver.test.ts | 21 ++++++++----------- 5 files changed, 11 insertions(+), 17 deletions(-) diff --git a/src/main.ts b/src/main.ts index d7488cced3..e9ce04362a 100644 --- a/src/main.ts +++ b/src/main.ts @@ -58,7 +58,7 @@ export async function activate(context: vscode.ExtensionContext): Promise<{ init eventStream.subscribe(omnisharpChannelObserver.post); let showWarningMessage: ShowWarningMessage = (message: string, ...items: T[]) => vscode.window.showWarningMessage(message, ...items); - let executeCommand: ExecuteCommand = (command: string, ...rest: any[]) => vscode.commands.executeCommand(command, ...rest); + let executeCommand: ExecuteCommand = (command: string, ...rest: any[]) => vscode.commands.executeCommand(command, ...rest); let omnisharpServerStatusObserver = new WarningMessageObserver(showWarningMessage, executeCommand); eventStream.subscribe(omnisharpServerStatusObserver.post); diff --git a/src/observers/OmnisharpStatusBarObserver.ts b/src/observers/OmnisharpStatusBarObserver.ts index aeb666bfec..33f1e8e30c 100644 --- a/src/observers/OmnisharpStatusBarObserver.ts +++ b/src/observers/OmnisharpStatusBarObserver.ts @@ -9,8 +9,7 @@ import { Status } from './status'; import * as serverUtils from '../omnisharp/utils'; import { basename } from 'path'; import { OmniSharpServer } from '../omnisharp/server'; -import { CompositeDisposable, Subject } from 'rx'; -import { ProjectInformationResponse } from '../omnisharp/protocol'; +import { CompositeDisposable } from 'rx'; const debounce = require('lodash.debounce'); diff --git a/src/observers/WarningMessageObserver.ts b/src/observers/WarningMessageObserver.ts index dbe69dec7c..ef57e60db4 100644 --- a/src/observers/WarningMessageObserver.ts +++ b/src/observers/WarningMessageObserver.ts @@ -20,7 +20,6 @@ export interface ExecuteCommand { } export class WarningMessageObserver { - private _messageHandle: NodeJS.Timer; private warningMessageDebouncer: Subject; constructor(private showWarningMessage: ShowWarningMessage, private executeCommand: ExecuteCommand, scheduler?: Scheduler) { diff --git a/src/omnisharp/loggingEvents.ts b/src/omnisharp/loggingEvents.ts index adf508494e..3cf7c7c715 100644 --- a/src/omnisharp/loggingEvents.ts +++ b/src/omnisharp/loggingEvents.ts @@ -6,7 +6,6 @@ import { PlatformInformation } from "../platform"; import { Request } from "./requestQueue"; import * as protocol from './protocol'; -import * as vscode from '../vscodeAdapter'; import { OmniSharpServer } from "./server"; import { EventStream } from "../EventStream"; import { LaunchTarget } from "./launcher"; diff --git a/test/unitTests/logging/WarningMessageObserver.test.ts b/test/unitTests/logging/WarningMessageObserver.test.ts index b4e656edd9..313f0acefc 100644 --- a/test/unitTests/logging/WarningMessageObserver.test.ts +++ b/test/unitTests/logging/WarningMessageObserver.test.ts @@ -60,23 +60,20 @@ suite('OmnisharpServerStatusObserver', () => { expect(output).to.be.empty; }); - + [ getOmnisharpMSBuildProjectDiagnosticsEvent("someFile", [getMSBuildDiagnosticsMessage("warningFile", "", "", 1, 2, 3, 4)], [getMSBuildDiagnosticsMessage("errorFile", "", "", 5, 6, 7, 8)]), - getOmnisharpServerOnErrorEvent("someText", "someFile", 1, 2) + getOmnisharpServerOnErrorEvent("someText", "someFile", 1, 2) ].forEach((event: BaseEvent) => { test(`${event.constructor.name}: Debouce function`, () => { - let event = getOmnisharpMSBuildProjectDiagnosticsEvent("someFile", - [getMSBuildDiagnosticsMessage("warningFile", "", "", 1, 2, 3, 4)], - [getMSBuildDiagnosticsMessage("errorFile", "", "", 5, 6, 7, 8)]); observer.post(event); scheduler.advanceBy(1000); //since the debounce time is 1500 no output should be there expect(output).to.be.empty; }); - - test(`${event.constructor.name}: If a event is fired within 1500ms the first event is debounced`, () => { + + test(`${event.constructor.name}: If an event is fired within 1500ms the first event is debounced`, () => { observer.post(event); scheduler.advanceBy(1000); expect(output).to.be.empty; @@ -89,10 +86,6 @@ suite('OmnisharpServerStatusObserver', () => { }); test(`${event.constructor.name}: Show warning message and execute command are called`, (done) => { - let event = getOmnisharpMSBuildProjectDiagnosticsEvent("someFile", - [getMSBuildDiagnosticsMessage("warningFile", "", "", 1, 2, 3, 4)], - [getMSBuildDiagnosticsMessage("errorFile", "", "", 5, 6, 7, 8)]); - commandExecuted = () => { expect(output).to.contain("Show Output"); expect(output).to.contain("o.showOutput"); @@ -100,9 +93,13 @@ suite('OmnisharpServerStatusObserver', () => { expect(output).to.contain("execute command called"); done(); }; - + observer.post(event); scheduler.advanceBy(1500); }); }); + + teardown(() => { + commandExecuted = undefined; + }); }); \ No newline at end of file From 11c0d4853747066a18fb47a113371b4d3e666d76 Mon Sep 17 00:00:00 2001 From: Akshita Agarwal Date: Mon, 26 Mar 2018 12:36:27 -0700 Subject: [PATCH 18/56] some changes --- src/observers/OmnisharpStatusBarObserver.ts | 25 ++++++++++--------- .../logging/WarningMessageObserver.test.ts | 3 +-- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/observers/OmnisharpStatusBarObserver.ts b/src/observers/OmnisharpStatusBarObserver.ts index 33f1e8e30c..198c5f85c0 100644 --- a/src/observers/OmnisharpStatusBarObserver.ts +++ b/src/observers/OmnisharpStatusBarObserver.ts @@ -4,12 +4,12 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from '../vscodeAdapter'; -import * as ObservableEvent from "../omnisharp/loggingEvents"; import { Status } from './status'; import * as serverUtils from '../omnisharp/utils'; import { basename } from 'path'; import { OmniSharpServer } from '../omnisharp/server'; -import { CompositeDisposable } from 'rx'; +import { CompositeDisposable, Subject } from 'rx'; +import { BaseEvent, OmnisharpServerOnServerError, OmnisharpOnMultipleLaunchTargets, OmnisharpOnBeforeServerInstall, OmnisharpOnBeforeServerStart, ActiveTextEditorChanged, OmnisharpServerOnStop, OmnisharpServerOnStart } from '../omnisharp/loggingEvents'; const debounce = require('lodash.debounce'); @@ -39,32 +39,32 @@ export class OmnisharpStatusBarObserver { this.defaultStatus = new Status(defaultSelector); } - public post = (event: ObservableEvent.BaseEvent) => { + public post = (event: BaseEvent) => { switch (event.constructor.name) { - case ObservableEvent.OmnisharpServerOnServerError.name: + case OmnisharpServerOnServerError.name: SetStatus(this.defaultStatus, '$(flame) Error starting OmniSharp', 'o.showOutput', ''); this.render(); break; - case ObservableEvent.OmnisharpOnMultipleLaunchTargets.name: + case OmnisharpOnMultipleLaunchTargets.name: SetStatus(this.defaultStatus, '$(flame) Select project', 'o.pickProjectAndStart', 'rgb(90, 218, 90)'); this.render(); break; - case ObservableEvent.OmnisharpOnBeforeServerInstall.name: + case OmnisharpOnBeforeServerInstall.name: SetStatus(this.defaultStatus, '$(flame) Installing OmniSharp...', 'o.showOutput', ''); this.render(); break; - case ObservableEvent.OmnisharpOnBeforeServerStart.name: + case OmnisharpOnBeforeServerStart.name: SetStatus(this.defaultStatus, '$(flame) Starting...', 'o.showOutput', ''); this.render(); break; - case ObservableEvent.ActiveTextEditorChanged.name: + case ActiveTextEditorChanged.name: this.render(); break; - case ObservableEvent.OmnisharpServerOnStop.name: + case OmnisharpServerOnStop.name: this.handleOmnisharpServerOnStop(); break; - case ObservableEvent.OmnisharpServerOnStart.name: - this.handleOmnisharpServerOnStart(event); + case OmnisharpServerOnStart.name: + this.handleOmnisharpServerOnStart(event); break; } } @@ -169,10 +169,11 @@ export class OmnisharpStatusBarObserver { } } - private handleOmnisharpServerOnStart(event: ObservableEvent.OmnisharpServerOnStart) { + private handleOmnisharpServerOnStart(event: OmnisharpServerOnStart) { this.localDisposables = new CompositeDisposable(); SetStatus(this.defaultStatus, '$(flame) Running', 'o.pickProjectAndStart', ''); this.render(); + let updateProjectDebouncer = new Subject(); let updateProjectInfoFunction = () => this.updateProjectInfo(event.server); let debouncedUpdateProjectInfo = debounce(updateProjectInfoFunction, 1500, { leading: true }); this.localDisposables.add(event.server.onProjectAdded(debouncedUpdateProjectInfo)); diff --git a/test/unitTests/logging/WarningMessageObserver.test.ts b/test/unitTests/logging/WarningMessageObserver.test.ts index 313f0acefc..3769456c6d 100644 --- a/test/unitTests/logging/WarningMessageObserver.test.ts +++ b/test/unitTests/logging/WarningMessageObserver.test.ts @@ -37,7 +37,6 @@ suite('OmnisharpServerStatusObserver', () => { let executeCommand: ExecuteCommand = (command, ...rest) => { output += "execute command called"; output += command; - console.log(output); return new Promise(resolve => { resolve("execute command resolved"); commandExecuted(); @@ -60,7 +59,6 @@ suite('OmnisharpServerStatusObserver', () => { expect(output).to.be.empty; }); - [ getOmnisharpMSBuildProjectDiagnosticsEvent("someFile", [getMSBuildDiagnosticsMessage("warningFile", "", "", 1, 2, 3, 4)], @@ -91,6 +89,7 @@ suite('OmnisharpServerStatusObserver', () => { expect(output).to.contain("o.showOutput"); expect(output).to.contain("show warning message called"); expect(output).to.contain("execute command called"); + expect(output).to.contain("myCommand"); done(); }; From 1432851259d65e27414cc5fbd169b5a277a3eaab Mon Sep 17 00:00:00 2001 From: Piotr Puszkiewicz Date: Fri, 23 Mar 2018 20:18:09 -0700 Subject: [PATCH 19/56] refactor^2 --- src/main.ts | 24 +- src/observers/InformationMessageObserver.ts | 23 +- .../OmnisharpServerStatusObserver.ts | 48 ++ src/observers/OmnisharpStatusBarObserver.ts | 30 +- src/vscodeAdapter.ts | 534 ++++++++++++++++++ test/unitTests/logging/Fakes.ts | 37 +- .../OmnisharpServerStatusObserver.test.ts | 63 +++ .../OmnisharpStatusBarObserver.test.ts | 28 +- 8 files changed, 724 insertions(+), 63 deletions(-) create mode 100644 src/observers/OmnisharpServerStatusObserver.ts create mode 100644 test/unitTests/logging/OmnisharpServerStatusObserver.test.ts diff --git a/src/main.ts b/src/main.ts index d7488cced3..75f1c9d679 100644 --- a/src/main.ts +++ b/src/main.ts @@ -7,25 +7,24 @@ import * as OmniSharp from './omnisharp/extension'; import * as coreclrdebug from './coreclr-debug/activate'; import * as util from './common'; import * as vscode from 'vscode'; + +import { ActivationFailure, ActiveTextEditorChanged } from './omnisharp/loggingEvents'; + import { CSharpExtDownloader } from './CSharpExtDownloader'; -import { PlatformInformation } from './platform'; -import TelemetryReporter from 'vscode-extension-telemetry'; -import { addJSONProviders } from './features/json/jsonContributions'; import { CsharpChannelObserver } from './observers/CsharpChannelObserver'; import { CsharpLoggerObserver } from './observers/CsharpLoggerObserver'; -import { OmnisharpLoggerObserver } from './observers/OmnisharpLoggerObserver'; import { DotNetChannelObserver } from './observers/DotnetChannelObserver'; -import { TelemetryObserver } from './observers/TelemetryObserver'; -import { OmnisharpChannelObserver } from './observers/OmnisharpChannelObserver'; import { DotnetLoggerObserver } from './observers/DotnetLoggerObserver'; -import { OmnisharpDebugModeLoggerObserver } from './observers/OmnisharpDebugModeLoggerObserver'; -import { ActivationFailure, ActiveTextEditorChanged } from './omnisharp/loggingEvents'; import { EventStream } from './EventStream'; import { WarningMessageObserver, ExecuteCommand, ShowWarningMessage, MessageItemWithCommand } from './observers/WarningMessageObserver'; import { InformationMessageObserver, ShowInformationMessage, GetConfiguration, WorkspaceAsRelativePath } from './observers/InformationMessageObserver'; import { GetActiveTextEditor, OmnisharpStatusBarObserver, Match } from './observers/OmnisharpStatusBarObserver'; import { TextEditorAdapter } from './textEditorAdapter'; import { StatusBarItemAdapter } from './statusBarItemAdapter'; +import { TelemetryObserver } from './observers/TelemetryObserver'; +import TelemetryReporter from 'vscode-extension-telemetry'; +import { TextEditorAdapter } from './textEditorAdapter'; +import { addJSONProviders } from './features/json/jsonContributions'; export async function activate(context: vscode.ExtensionContext): Promise<{ initializationFinished: Promise }> { @@ -62,16 +61,11 @@ export async function activate(context: vscode.ExtensionContext): Promise<{ init let omnisharpServerStatusObserver = new WarningMessageObserver(showWarningMessage, executeCommand); eventStream.subscribe(omnisharpServerStatusObserver.post); - let getConfiguration: GetConfiguration = (name: string) => vscode.workspace.getConfiguration(name); - let showInformationMessage: ShowInformationMessage = (message: string, ...items: string[]) => vscode.window.showInformationMessage(message, ...items); - let workspaceAsRelativePath: WorkspaceAsRelativePath = (path: string, includeWorkspaceFolder?: boolean) => vscode.workspace.asRelativePath(path, includeWorkspaceFolder); - let informationMessageObserver = new InformationMessageObserver(getConfiguration, showInformationMessage, workspaceAsRelativePath); + let informationMessageObserver = new InformationMessageObserver(vscode); eventStream.subscribe(informationMessageObserver.post); let omnisharpStatusBar = new StatusBarItemAdapter(vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, Number.MIN_VALUE)); - let getActiveTextEditor: GetActiveTextEditor = () => new TextEditorAdapter(vscode.window.activeTextEditor); - let match: Match = (selector: vscode.DocumentSelector, document: any) => vscode.languages.match(selector, document); - let omnisharpStatusBarObserver = new OmnisharpStatusBarObserver(getActiveTextEditor, match, omnisharpStatusBar); + let omnisharpStatusBarObserver = new OmnisharpStatusBarObserver(vscode, omnisharpStatusBar); eventStream.subscribe(omnisharpStatusBarObserver.post); const debugMode = false; diff --git a/src/observers/InformationMessageObserver.ts b/src/observers/InformationMessageObserver.ts index 9153de005d..317a86f61d 100644 --- a/src/observers/InformationMessageObserver.ts +++ b/src/observers/InformationMessageObserver.ts @@ -3,24 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as vscode from '../vscodeAdapter'; import * as ObservableEvent from "../omnisharp/loggingEvents"; -import { dotnetRestoreForProject } from '../features/commands'; - -export interface GetConfiguration { - (value: string): vscode.WorkspaceConfiguration; -} -export interface ShowInformationMessage { - (message: string, ...items: string[]): Thenable; -} - -export interface WorkspaceAsRelativePath{ - (path: string, includeWorkspaceFolder?: boolean): string; -} +import { dotnetRestoreForProject } from '../features/commands'; +import { vscode } from '../vscodeAdapter'; export class InformationMessageObserver { - constructor(private getConfiguration: GetConfiguration, private showInformationMessage: ShowInformationMessage, private workspaceAsRelativePath : WorkspaceAsRelativePath) { + constructor(private vscode: vscode) { } public post = (event: ObservableEvent.BaseEvent) => { @@ -32,11 +21,11 @@ export class InformationMessageObserver { } private handleOmnisharpServerUnresolvedDependencies(event: ObservableEvent.OmnisharpServerUnresolvedDependencies) { - let csharpConfig = this.getConfiguration('csharp'); + let csharpConfig = this.vscode.workspace.getConfiguration('csharp'); if (!csharpConfig.get('suppressDotnetRestoreNotification')) { - let info = `There are unresolved dependencies from '${this.workspaceAsRelativePath(event.unresolvedDependencies.FileName)}'. Please execute the restore command to continue.`; + let info = `There are unresolved dependencies from '${this.vscode.workspace.asRelativePath(event.unresolvedDependencies.FileName)}'. Please execute the restore command to continue.`; - return this.showInformationMessage(info, 'Restore').then(value => { + return this.vscode.window.showInformationMessage(info, 'Restore').then(value => { if (value) { dotnetRestoreForProject(event.server, event.unresolvedDependencies.FileName, event.eventStream); } diff --git a/src/observers/OmnisharpServerStatusObserver.ts b/src/observers/OmnisharpServerStatusObserver.ts new file mode 100644 index 0000000000..c1df3dd1b7 --- /dev/null +++ b/src/observers/OmnisharpServerStatusObserver.ts @@ -0,0 +1,48 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as ObservableEvent from "../omnisharp/loggingEvents"; + +import { MessageItem, vscode } from '../vscodeAdapter'; +import { Scheduler, Subject } from 'rx'; + +export interface MessageItemWithCommand extends MessageItem{ + command: string; +} + +export class OmnisharpServerStatusObserver { + private _messageHandle: NodeJS.Timer; + private subject: Subject; + + constructor(private vscode: vscode, scheduler?: Scheduler) { + this.subject = new Subject(); + this.subject.debounce(1500).subscribe(async (event) => { + let message = "Some projects have trouble loading. Please review the output for more details."; + let value = await this.vscode.window.showWarningMessage(message, { title: "Show Output", command: 'o.showOutput' }); + if (value) { + console.log(value); + this.vscode.commands.executeCommand(value.command); + } + }); + } + + public post = (event: ObservableEvent.BaseEvent) => { + switch (event.constructor.name) { + case ObservableEvent.OmnisharpServerOnError.name: + this.subject.onNext(event); + break; + case ObservableEvent.OmnisharpServerMsBuildProjectDiagnostics.name: + this.handleOmnisharpServerMsBuildProjectDiagnostics(event); + break; + } + } + + private handleOmnisharpServerMsBuildProjectDiagnostics(event: ObservableEvent.OmnisharpServerMsBuildProjectDiagnostics) { + if (event.diagnostics.Errors.length > 0) { + //this.showMessageSoon(); + this.subject.onNext(event); + } + } +} \ No newline at end of file diff --git a/src/observers/OmnisharpStatusBarObserver.ts b/src/observers/OmnisharpStatusBarObserver.ts index aeb666bfec..d92446eb1c 100644 --- a/src/observers/OmnisharpStatusBarObserver.ts +++ b/src/observers/OmnisharpStatusBarObserver.ts @@ -3,18 +3,20 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as vscode from '../vscodeAdapter'; import * as ObservableEvent from "../omnisharp/loggingEvents"; -import { Status } from './status'; import * as serverUtils from '../omnisharp/utils'; -import { basename } from 'path'; -import { OmniSharpServer } from '../omnisharp/server'; + import { CompositeDisposable, Subject } from 'rx'; +import { DocumentFilter, DocumentSelector, StatusBarItem, vscode } from '../vscodeAdapter'; + +import { OmniSharpServer } from '../omnisharp/server'; import { ProjectInformationResponse } from '../omnisharp/protocol'; +import { Status } from './status'; +import { basename } from 'path'; const debounce = require('lodash.debounce'); -let defaultSelector: vscode.DocumentSelector = [ +let defaultSelector: DocumentSelector = [ 'csharp', // c#-files OR { pattern: '**/project.json' }, // project.json-files OR { pattern: '**/*.sln' }, // any solution file OR @@ -23,20 +25,12 @@ let defaultSelector: vscode.DocumentSelector = [ { pattern: '**/*.cake' } // Cake script ]; -export interface GetActiveTextEditor { - (): vscode.TextEditor; -} - -export interface Match { - (selector: vscode.DocumentSelector, document: any): number; -} - export class OmnisharpStatusBarObserver { private defaultStatus: Status; private projectStatus: Status; private localDisposables: CompositeDisposable; - constructor(private getActiveTextEditor: GetActiveTextEditor, private match: Match, private statusBar: vscode.StatusBarItem) { + constructor(private vscode: vscode, private statusBar: StatusBarItem) { this.defaultStatus = new Status(defaultSelector); } @@ -78,7 +72,7 @@ export class OmnisharpStatusBarObserver { SourceFiles: string[]; } - let fileNames: vscode.DocumentFilter[] = []; + let fileNames: DocumentFilter[] = []; let label: string; function addProjectFileNames(project: Project) { @@ -133,7 +127,7 @@ export class OmnisharpStatusBarObserver { } private render = () => { - let activeTextEditor = this.getActiveTextEditor(); + let activeTextEditor = this.vscode.window.activeTextEditor; if (!activeTextEditor) { this.statusBar.hide(); return; @@ -142,9 +136,9 @@ export class OmnisharpStatusBarObserver { let document = activeTextEditor.document; let status: Status; - if (this.projectStatus && this.match(this.projectStatus.selector, document)) { + if (this.projectStatus && this.vscode.languages.match(this.projectStatus.selector, document)) { status = this.projectStatus; - } else if (this.defaultStatus.text && this.match(this.defaultStatus.selector, document)) { + } else if (this.defaultStatus.text && this.vscode.languages.match(this.defaultStatus.selector, document)) { status = this.defaultStatus; } diff --git a/src/vscodeAdapter.ts b/src/vscodeAdapter.ts index 37f16b0442..a7283c3141 100644 --- a/src/vscodeAdapter.ts +++ b/src/vscodeAdapter.ts @@ -299,6 +299,100 @@ export interface TextEditor { */ document: any; } +/** + * A universal resource identifier representing either a file on disk + * or another resource, like untitled resources. + */ +export interface Uri { + + /** + * Create an URI from a file system path. The [scheme](#Uri.scheme) + * will be `file`. + * + * @param path A file system or UNC path. + * @return A new Uri instance. + */ + file(path: string): Uri; + + /** + * Create an URI from a string. Will throw if the given value is not + * valid. + * + * @param value The string value of an Uri. + * @return A new Uri instance. + */ + parse(value: string): Uri; + + /** + * Scheme is the `http` part of `http://www.msft.com/some/path?query#fragment`. + * The part before the first colon. + */ + readonly scheme: string; + + /** + * Authority is the `www.msft.com` part of `http://www.msft.com/some/path?query#fragment`. + * The part between the first double slashes and the next slash. + */ + readonly authority: string; + + /** + * Path is the `/some/path` part of `http://www.msft.com/some/path?query#fragment`. + */ + readonly path: string; + + /** + * Query is the `query` part of `http://www.msft.com/some/path?query#fragment`. + */ + readonly query: string; + + /** + * Fragment is the `fragment` part of `http://www.msft.com/some/path?query#fragment`. + */ + readonly fragment: string; + + /** + * The string representing the corresponding file system path of this Uri. + * + * Will handle UNC paths and normalize windows drive letters to lower-case. Also + * uses the platform specific path separator. Will *not* validate the path for + * invalid characters and semantics. Will *not* look at the scheme of this Uri. + */ + readonly fsPath: string; + + /** + * Derive a new Uri from this Uri. + * + * ```ts + * let file = Uri.parse('before:some/file/path'); + * let other = file.with({ scheme: 'after' }); + * assert.ok(other.toString() === 'after:some/file/path'); + * ``` + * + * @param change An object that describes a change to this Uri. To unset components use `null` or + * the empty string. + * @return A new Uri that reflects the given change. Will return `this` Uri if the change + * is not changing anything. + */ + with(change: { scheme?: string; authority?: string; path?: string; query?: string; fragment?: string }): Uri; + + /** + * Returns a string representation of this Uri. The representation and normalization + * of a URI depends on the scheme. The resulting string can be safely used with + * [Uri.parse](#Uri.parse). + * + * @param skipEncoding Do not percentage-encode the result, defaults to `false`. Note that + * the `#` and `?` characters occuring in the path will always be encoded. + * @returns A string representation of this Uri. + */ + toString(skipEncoding?: boolean): string; + + /** + * Returns a JSON representation of this Uri. + * + * @return An object. + */ + toJSON(): any; +} export interface MessageItem { @@ -313,3 +407,443 @@ export interface MessageItem { */ isCloseAffordance?: boolean; } + +/** + * Represents a text document, such as a source file. Text documents have + * [lines](#TextLine) and knowledge about an underlying resource like a file. + */ +export interface TextDocument { + + /** + * The associated URI for this document. Most documents have the __file__-scheme, indicating that they + * represent files on disk. However, some documents may have other schemes indicating that they are not + * available on disk. + */ + readonly uri: Uri; + + /** + * The file system path of the associated resource. Shorthand + * notation for [TextDocument.uri.fsPath](#TextDocument.uri). Independent of the uri scheme. + */ + readonly fileName: string; + + /** + * Is this document representing an untitled file. + */ + readonly isUntitled: boolean; + + /** + * The identifier of the language associated with this document. + */ + readonly languageId: string; + + /** + * The version number of this document (it will strictly increase after each + * change, including undo/redo). + */ + readonly version: number; + + /** + * `true` if there are unpersisted changes. + */ + readonly isDirty: boolean; + + /** + * `true` if the document have been closed. A closed document isn't synchronized anymore + * and won't be re-used when the same resource is opened again. + */ + readonly isClosed: boolean; + + /** + * Save the underlying file. + * + * @return A promise that will resolve to true when the file + * has been saved. If the file was not dirty or the save failed, + * will return false. + */ + save(): Thenable; + + /** + * The [end of line](#EndOfLine) sequence that is predominately + * used in this document. + */ + readonly eol: EndOfLine; + + /** + * The number of lines in this document. + */ + readonly lineCount: number; + + /** + * Returns a text line denoted by the line number. Note + * that the returned object is *not* live and changes to the + * document are not reflected. + * + * @param line A line number in [0, lineCount). + * @return A [line](#TextLine). + */ + lineAt(line: number): TextLine; + + /** + * Returns a text line denoted by the position. Note + * that the returned object is *not* live and changes to the + * document are not reflected. + * + * The position will be [adjusted](#TextDocument.validatePosition). + * + * @see [TextDocument.lineAt](#TextDocument.lineAt) + * @param position A position. + * @return A [line](#TextLine). + */ + lineAt(position: Position): TextLine; + + /** + * Converts the position to a zero-based offset. + * + * The position will be [adjusted](#TextDocument.validatePosition). + * + * @param position A position. + * @return A valid zero-based offset. + */ + offsetAt(position: Position): number; + + /** + * Converts a zero-based offset to a position. + * + * @param offset A zero-based offset. + * @return A valid [position](#Position). + */ + positionAt(offset: number): Position; + + /** + * Get the text of this document. A substring can be retrieved by providing + * a range. The range will be [adjusted](#TextDocument.validateRange). + * + * @param range Include only the text included by the range. + * @return The text inside the provided range or the entire text. + */ + getText(range?: Range): string; + + /** + * Get a word-range at the given position. By default words are defined by + * common separators, like space, -, _, etc. In addition, per languge custom + * [word definitions](#LanguageConfiguration.wordPattern) can be defined. It + * is also possible to provide a custom regular expression. + * + * * *Note 1:* A custom regular expression must not match the empty string and + * if it does, it will be ignored. + * * *Note 2:* A custom regular expression will fail to match multiline strings + * and in the name of speed regular expressions should not match words with + * spaces. Use [`TextLine.text`](#TextLine.text) for more complex, non-wordy, scenarios. + * + * The position will be [adjusted](#TextDocument.validatePosition). + * + * @param position A position. + * @param regex Optional regular expression that describes what a word is. + * @return A range spanning a word, or `undefined`. + */ + getWordRangeAtPosition(position: Position, regex?: RegExp): Range | undefined; + + /** + * Ensure a range is completely contained in this document. + * + * @param range A range. + * @return The given range or a new, adjusted range. + */ + validateRange(range: Range): Range; + + /** + * Ensure a position is contained in the range of this document. + * + * @param position A position. + * @return The given position or a new, adjusted position. + */ + validatePosition(position: Position): Position; +} + +/** + * Represents an end of line character sequence in a [document](#TextDocument). + */ +export enum EndOfLine { + /** + * The line feed `\n` character. + */ + LF = 1, + /** + * The carriage return line feed `\r\n` sequence. + */ + CRLF = 2 +} + +/** + * Represents a line and character position, such as + * the position of the cursor. + * + * Position objects are __immutable__. Use the [with](#Position.with) or + * [translate](#Position.translate) methods to derive new positions + * from an existing position. + */ +export interface Position { + + /** + * The zero-based line value. + */ + readonly line: number; + + /** + * The zero-based character value. + */ + readonly character: number; + + /** + * @param line A zero-based line value. + * @param character A zero-based character value. + */ + constructor(line: number, character: number); + + /** + * Check if `other` is before this position. + * + * @param other A position. + * @return `true` if position is on a smaller line + * or on the same line on a smaller character. + */ + isBefore(other: Position): boolean; + + /** + * Check if `other` is before or equal to this position. + * + * @param other A position. + * @return `true` if position is on a smaller line + * or on the same line on a smaller or equal character. + */ + isBeforeOrEqual(other: Position): boolean; + + /** + * Check if `other` is after this position. + * + * @param other A position. + * @return `true` if position is on a greater line + * or on the same line on a greater character. + */ + isAfter(other: Position): boolean; + + /** + * Check if `other` is after or equal to this position. + * + * @param other A position. + * @return `true` if position is on a greater line + * or on the same line on a greater or equal character. + */ + isAfterOrEqual(other: Position): boolean; + + /** + * Check if `other` equals this position. + * + * @param other A position. + * @return `true` if the line and character of the given position are equal to + * the line and character of this position. + */ + isEqual(other: Position): boolean; + + /** + * Compare this to `other`. + * + * @param other A position. + * @return A number smaller than zero if this position is before the given position, + * a number greater than zero if this position is after the given position, or zero when + * this and the given position are equal. + */ + compareTo(other: Position): number; + + /** + * Create a new position relative to this position. + * + * @param lineDelta Delta value for the line value, default is `0`. + * @param characterDelta Delta value for the character value, default is `0`. + * @return A position which line and character is the sum of the current line and + * character and the corresponding deltas. + */ + translate(lineDelta?: number, characterDelta?: number): Position; + + /** + * Derived a new position relative to this position. + * + * @param change An object that describes a delta to this position. + * @return A position that reflects the given delta. Will return `this` position if the change + * is not changing anything. + */ + translate(change: { lineDelta?: number; characterDelta?: number; }): Position; + + /** + * Create a new position derived from this position. + * + * @param line Value that should be used as line value, default is the [existing value](#Position.line) + * @param character Value that should be used as character value, default is the [existing value](#Position.character) + * @return A position where line and character are replaced by the given values. + */ + with(line?: number, character?: number): Position; + + /** + * Derived a new position from this position. + * + * @param change An object that describes a change to this position. + * @return A position that reflects the given change. Will return `this` position if the change + * is not changing anything. + */ + with(change: { line?: number; character?: number; }): Position; +} + +export interface Range { + + /** + * The start position. It is before or equal to [end](#Range.end). + */ + readonly start: Position; + + /** + * The end position. It is after or equal to [start](#Range.start). + */ + readonly end: Position; + + /** + * `true` if `start` and `end` are equal. + */ + isEmpty: boolean; + + /** + * `true` if `start.line` and `end.line` are equal. + */ + isSingleLine: boolean; + + /** + * Check if a position or a range is contained in this range. + * + * @param positionOrRange A position or a range. + * @return `true` if the position or range is inside or equal + * to this range. + */ + contains(positionOrRange: Position | Range): boolean; + + /** + * Check if `other` equals this range. + * + * @param other A range. + * @return `true` when start and end are [equal](#Position.isEqual) to + * start and end of this range. + */ + isEqual(other: Range): boolean; + + /** + * Intersect `range` with this range and returns a new range or `undefined` + * if the ranges have no overlap. + * + * @param range A range. + * @return A range of the greater start and smaller end positions. Will + * return undefined when there is no overlap. + */ + intersection(range: Range): Range | undefined; + + /** + * Compute the union of `other` with this range. + * + * @param other A range. + * @return A range of smaller start position and the greater end position. + */ + union(other: Range): Range; + + /** + * Derived a new range from this range. + * + * @param start A position that should be used as start. The default value is the [current start](#Range.start). + * @param end A position that should be used as end. The default value is the [current end](#Range.end). + * @return A range derived from this range with the given start and end position. + * If start and end are not different `this` range will be returned. + */ + with(start?: Position, end?: Position): Range; + + /** + * Derived a new range from this range. + * + * @param change An object that describes a change to this range. + * @return A range that reflects the given change. Will return `this` range if the change + * is not changing anything. + */ + with(change: { start?: Position, end?: Position }): Range; +} + +/** + * Represents a line of text, such as a line of source code. + * + * TextLine objects are __immutable__. When a [document](#TextDocument) changes, + * previously retrieved lines will not represent the latest state. + */ +export interface TextLine { + + /** + * The zero-based line number. + */ + readonly lineNumber: number; + + /** + * The text of this line without the line separator characters. + */ + readonly text: string; + + /** + * The range this line covers without the line separator characters. + */ + readonly range: Range; + + /** + * The range this line covers with the line separator characters. + */ + readonly rangeIncludingLineBreak: Range; + + /** + * The offset of the first character which is not a whitespace character as defined + * by `/\s/`. **Note** that if a line is all whitespaces the length of the line is returned. + */ + readonly firstNonWhitespaceCharacterIndex: number; + + /** + * Whether this line is whitespace only, shorthand + * for [TextLine.firstNonWhitespaceCharacterIndex](#TextLine.firstNonWhitespaceCharacterIndex) === [TextLine.text.length](#TextLine.text). + */ + readonly isEmptyOrWhitespace: boolean; +} + +/** + * Thenable is a common denominator between ES6 promises, Q, jquery.Deferred, WinJS.Promise, + * and others. This API makes no assumption about what promise libary is being used which + * enables reusing existing code without migrating to a specific promise implementation. Still, + * we recommend the use of native promises which are available in this editor. + */ +interface Thenable { + /** + * Attaches callbacks for the resolution and/or rejection of the Promise. + * @param onfulfilled The callback to execute when the Promise is resolved. + * @param onrejected The callback to execute when the Promise is rejected. + * @returns A Promise for the completion of which ever callback is executed. + */ + then(onfulfilled?: (value: T) => TResult | Thenable, onrejected?: (reason: any) => TResult | Thenable): Thenable; + then(onfulfilled?: (value: T) => TResult | Thenable, onrejected?: (reason: any) => void): Thenable; +} + +export interface vscode { + commands: { + executeCommand: (command: string, ...rest: any[]) => Thenable; + }; + languages: { + match: (selector: DocumentSelector, document: TextDocument) => number; + }; + window: { + activeTextEditor: TextEditor | undefined; + showInformationMessage: (message: string, ...items: string[]) => Thenable; + showWarningMessage: (message: string, ...items: T[]) => Thenable; + }; + workspace: { + getConfiguration: (section?: string, resource?: Uri) => WorkspaceConfiguration; + asRelativePath: (pathOrUri: string | Uri, includeWorkspaceFolder?: boolean) => string; + }; +} \ No newline at end of file diff --git a/test/unitTests/logging/Fakes.ts b/test/unitTests/logging/Fakes.ts index 898c7d2919..c02d41ba0a 100644 --- a/test/unitTests/logging/Fakes.ts +++ b/test/unitTests/logging/Fakes.ts @@ -4,6 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from '../../../src/vscodeAdapter'; + +import { DocumentSelector, MessageItem, TextDocument, Uri } from '../../../src/vscodeAdapter'; + import { ITelemetryReporter } from '../../../src/observers/TelemetryObserver'; import { MSBuildDiagnosticsMessage } from '../../../src/omnisharp/protocol'; import { OmnisharpServerMsBuildProjectDiagnostics, OmnisharpServerOnError } from '../../../src/omnisharp/loggingEvents'; @@ -62,4 +65,36 @@ export function getOmnisharpServerOnErrorEvent(text: string, fileName: string, l Line: line, Column: column }); -} \ No newline at end of file +} + +export function getFakeVsCode() : vscode.vscode { + return { + commands: { + executeCommand: (command: string, ...rest: any[]) => { + throw new Error("Not Implemented"); + } + }, + languages: { + match: (selector: DocumentSelector, document: TextDocument) => { + throw new Error("Not Implemented"); + } + }, + window: { + activeTextEditor: undefined, + showInformationMessage: (message: string, ...items: string[]) => { + throw new Error("Not Implemented"); + }, + showWarningMessage: (message: string, ...items: T[]) => { + throw new Error("Not Implemented"); + } + }, + workspace: { + getConfiguration: (section?: string, resource?: Uri) => { + throw new Error("Not Implemented"); + }, + asRelativePath: (pathOrUri: string | Uri, includeWorkspaceFolder?: boolean) => { + throw new Error("Not Implemented"); + } + } + }; +} \ No newline at end of file diff --git a/test/unitTests/logging/OmnisharpServerStatusObserver.test.ts b/test/unitTests/logging/OmnisharpServerStatusObserver.test.ts new file mode 100644 index 0000000000..0d43e01e46 --- /dev/null +++ b/test/unitTests/logging/OmnisharpServerStatusObserver.test.ts @@ -0,0 +1,63 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as rx from 'rx'; + +import { MessageItemWithCommand, OmnisharpServerStatusObserver } from '../../../src/observers/OmnisharpServerStatusObserver'; +import { expect, should } from 'chai'; +import { getMSBuildDiagnosticsMessage, getOmnisharpMSBuildProjectDiagnostics } from './Fakes'; + +import { getFakeVsCode } from './Fakes'; +import { resolve } from 'path'; +import { vscode } from '../../../src/vscodeAdapter'; + +suite('OmnisharpServerStatusObserver', () => { + let warningMessage; + let invokedCommand; + let scheduler: rx.HistoricalScheduler; + let observer: OmnisharpServerStatusObserver; + let vscode: vscode = getFakeVsCode(); + + vscode.window.showWarningMessage = (message, ...items) => { + warningMessage = message; + + return undefined; + }; + + vscode.commands.executeCommand = (command, ...rest) => { + invokedCommand = command; + return undefined; + }; + + suiteSetup(() => should()); + + setup(() => { + scheduler = new rx.HistoricalScheduler(0, (x, y) => { + return x > y ? 1 : -1; + }); + observer = new OmnisharpServerStatusObserver(vscode); + }); + + test('OmnisharpServerMsBuildProjectDiagnostics: No action is taken if the errors array is empty', () => { + let event = getOmnisharpMSBuildProjectDiagnostics("someFile", + [getMSBuildDiagnosticsMessage("warningFile", "", "", 0, 0, 0, 0)], + []); + observer.post(event); + expect(output).to.be.empty; + }); + + test('OmnisharpServerMsBuildProjectDiagnostics: No action is taken if the errors array is empty', () => { + let event = getOmnisharpMSBuildProjectDiagnostics("someFile", + [getMSBuildDiagnosticsMessage("warningFile", "", "", 1, 2, 3, 4)], + [getMSBuildDiagnosticsMessage("errorFile", "", "", 5, 6, 7, 8)]); + observer.post(event); + scheduler.advanceBy(1500); + console.log(output); + expect(output).to.contain("Show Output"); + expect(output).to.contain("o.showOutput"); + expect(output).to.contain("show warning message called"); + expect(output).to.contain("execute command called"); + }); +}); \ No newline at end of file diff --git a/test/unitTests/logging/OmnisharpStatusBarObserver.test.ts b/test/unitTests/logging/OmnisharpStatusBarObserver.test.ts index 92e942644b..01d41e1036 100644 --- a/test/unitTests/logging/OmnisharpStatusBarObserver.test.ts +++ b/test/unitTests/logging/OmnisharpStatusBarObserver.test.ts @@ -3,27 +3,31 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { should, expect } from 'chai'; -import { OmnisharpStatusBarObserver, GetActiveTextEditor, Match } from '../../../src/observers/OmnisharpStatusBarObserver'; -import * as vscode from '../../../src/vscodeAdapter'; -import { OmnisharpServerOnServerError, OmnisharpOnBeforeServerInstall, OmnisharpOnBeforeServerStart, OmnisharpOnMultipleLaunchTargets } from '../../../src/omnisharp/loggingEvents'; +import { DocumentSelector, StatusBarItem, vscode } from '../../../src/vscodeAdapter'; +import { OmnisharpOnBeforeServerInstall, OmnisharpOnBeforeServerStart, OmnisharpOnMultipleLaunchTargets, OmnisharpServerOnServerError } from '../../../src/omnisharp/loggingEvents'; +import { expect, should } from 'chai'; + +import { OmnisharpStatusBarObserver } from '../../../src/observers/OmnisharpStatusBarObserver'; +import { getFakeVsCode } from './Fakes'; suite('OmnisharpServerStatusObserver', () => { suiteSetup(() => should()); let output = ''; let showCalled: boolean; setup(() => { - output = ''; + output = ''; showCalled = false; }); - let getActiveTextEditor: GetActiveTextEditor = () => { return { document: "hello" }; }; - let matchFunction: Match = (selector: vscode.DocumentSelector, document: any) => { return 2; }; - let statusBar = { - show: () => { showCalled = true;} + let vscode: vscode = getFakeVsCode(); + vscode.window.activeTextEditor = { document: "hello" }; + vscode.languages.match = (selector: DocumentSelector, document: any) => { return 2; }; + + let statusBar = { + show: () => { showCalled = true; } }; - let observer = new OmnisharpStatusBarObserver(getActiveTextEditor, matchFunction, statusBar); + let observer = new OmnisharpStatusBarObserver(vscode, statusBar); test('OnServerError: If there is no project status, default status should be shown which includes error and flame', () => { let event = new OmnisharpServerOnServerError("someError"); @@ -58,9 +62,9 @@ suite('OmnisharpServerStatusObserver', () => { }); - - + + }); \ No newline at end of file From bbcc1e4c874ecbe3c60422fb8ed3139e044847ed Mon Sep 17 00:00:00 2001 From: Piotr Puszkiewicz Date: Mon, 26 Mar 2018 13:25:53 -0700 Subject: [PATCH 20/56] merge conflicts --- src/main.ts | 15 ++-- .../OmnisharpServerStatusObserver.ts | 48 ------------ src/observers/WarningMessageObserver.ts | 52 ++++++------- .../OmnisharpServerStatusObserver.test.ts | 63 --------------- .../logging/WarningMessageObserver.test.ts | 77 ++++++++----------- 5 files changed, 63 insertions(+), 192 deletions(-) delete mode 100644 src/observers/OmnisharpServerStatusObserver.ts delete mode 100644 test/unitTests/logging/OmnisharpServerStatusObserver.test.ts diff --git a/src/main.ts b/src/main.ts index 75f1c9d679..862cf7b45a 100644 --- a/src/main.ts +++ b/src/main.ts @@ -9,6 +9,7 @@ import * as util from './common'; import * as vscode from 'vscode'; import { ActivationFailure, ActiveTextEditorChanged } from './omnisharp/loggingEvents'; +import { MessageItemWithCommand, WarningMessageObserver } from './observers/WarningMessageObserver'; import { CSharpExtDownloader } from './CSharpExtDownloader'; import { CsharpChannelObserver } from './observers/CsharpChannelObserver'; @@ -16,10 +17,12 @@ import { CsharpLoggerObserver } from './observers/CsharpLoggerObserver'; import { DotNetChannelObserver } from './observers/DotnetChannelObserver'; import { DotnetLoggerObserver } from './observers/DotnetLoggerObserver'; import { EventStream } from './EventStream'; -import { WarningMessageObserver, ExecuteCommand, ShowWarningMessage, MessageItemWithCommand } from './observers/WarningMessageObserver'; -import { InformationMessageObserver, ShowInformationMessage, GetConfiguration, WorkspaceAsRelativePath } from './observers/InformationMessageObserver'; -import { GetActiveTextEditor, OmnisharpStatusBarObserver, Match } from './observers/OmnisharpStatusBarObserver'; -import { TextEditorAdapter } from './textEditorAdapter'; +import { InformationMessageObserver } from './observers/InformationMessageObserver'; +import { OmnisharpChannelObserver } from './observers/OmnisharpChannelObserver'; +import { OmnisharpDebugModeLoggerObserver } from './observers/OmnisharpDebugModeLoggerObserver'; +import { OmnisharpLoggerObserver } from './observers/OmnisharpLoggerObserver'; +import { OmnisharpStatusBarObserver } from './observers/OmnisharpStatusBarObserver'; +import { PlatformInformation } from './platform'; import { StatusBarItemAdapter } from './statusBarItemAdapter'; import { TelemetryObserver } from './observers/TelemetryObserver'; import TelemetryReporter from 'vscode-extension-telemetry'; @@ -56,9 +59,7 @@ export async function activate(context: vscode.ExtensionContext): Promise<{ init eventStream.subscribe(omnisharpLogObserver.post); eventStream.subscribe(omnisharpChannelObserver.post); - let showWarningMessage: ShowWarningMessage = (message: string, ...items: T[]) => vscode.window.showWarningMessage(message, ...items); - let executeCommand: ExecuteCommand = (command: string, ...rest: any[]) => vscode.commands.executeCommand(command, ...rest); - let omnisharpServerStatusObserver = new WarningMessageObserver(showWarningMessage, executeCommand); + let omnisharpServerStatusObserver = new WarningMessageObserver(vscode); eventStream.subscribe(omnisharpServerStatusObserver.post); let informationMessageObserver = new InformationMessageObserver(vscode); diff --git a/src/observers/OmnisharpServerStatusObserver.ts b/src/observers/OmnisharpServerStatusObserver.ts deleted file mode 100644 index c1df3dd1b7..0000000000 --- a/src/observers/OmnisharpServerStatusObserver.ts +++ /dev/null @@ -1,48 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as ObservableEvent from "../omnisharp/loggingEvents"; - -import { MessageItem, vscode } from '../vscodeAdapter'; -import { Scheduler, Subject } from 'rx'; - -export interface MessageItemWithCommand extends MessageItem{ - command: string; -} - -export class OmnisharpServerStatusObserver { - private _messageHandle: NodeJS.Timer; - private subject: Subject; - - constructor(private vscode: vscode, scheduler?: Scheduler) { - this.subject = new Subject(); - this.subject.debounce(1500).subscribe(async (event) => { - let message = "Some projects have trouble loading. Please review the output for more details."; - let value = await this.vscode.window.showWarningMessage(message, { title: "Show Output", command: 'o.showOutput' }); - if (value) { - console.log(value); - this.vscode.commands.executeCommand(value.command); - } - }); - } - - public post = (event: ObservableEvent.BaseEvent) => { - switch (event.constructor.name) { - case ObservableEvent.OmnisharpServerOnError.name: - this.subject.onNext(event); - break; - case ObservableEvent.OmnisharpServerMsBuildProjectDiagnostics.name: - this.handleOmnisharpServerMsBuildProjectDiagnostics(event); - break; - } - } - - private handleOmnisharpServerMsBuildProjectDiagnostics(event: ObservableEvent.OmnisharpServerMsBuildProjectDiagnostics) { - if (event.diagnostics.Errors.length > 0) { - //this.showMessageSoon(); - this.subject.onNext(event); - } - } -} \ No newline at end of file diff --git a/src/observers/WarningMessageObserver.ts b/src/observers/WarningMessageObserver.ts index dbe69dec7c..f343b55a43 100644 --- a/src/observers/WarningMessageObserver.ts +++ b/src/observers/WarningMessageObserver.ts @@ -3,52 +3,46 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as vscode from '../vscodeAdapter'; -import { Scheduler, Subject } from 'rx'; -import { BaseEvent, OmnisharpServerOnError, OmnisharpServerMsBuildProjectDiagnostics } from '../omnisharp/loggingEvents'; - -export interface MessageItemWithCommand extends vscode.MessageItem { - command: string; -} +import * as ObservableEvent from "../omnisharp/loggingEvents"; -export interface ShowWarningMessage { - (message: string, ...items: T[]): Thenable; -} +import { MessageItem, vscode } from '../vscodeAdapter'; +import { Scheduler, Subject } from 'rx'; -export interface ExecuteCommand { - (command: string, ...rest: any[]): Thenable; -} +export interface MessageItemWithCommand extends MessageItem{ + command: string; +} export class WarningMessageObserver { private _messageHandle: NodeJS.Timer; - private warningMessageDebouncer: Subject; + private subject: Subject; - constructor(private showWarningMessage: ShowWarningMessage, private executeCommand: ExecuteCommand, scheduler?: Scheduler) { - this.warningMessageDebouncer = new Subject(); - this.warningMessageDebouncer.debounce(1500, scheduler).subscribe(event => { + constructor(private vscode: vscode, scheduler?: Scheduler) { + this.subject = new Subject(); + this.subject.debounce(1500).subscribe(async (event) => { let message = "Some projects have trouble loading. Please review the output for more details."; - this.showWarningMessage(message, { title: "Show Output", command: 'o.showOutput' }).then(value => { - if (value) { - this.executeCommand(value.command); - } - }); + let value = await this.vscode.window.showWarningMessage(message, { title: "Show Output", command: 'o.showOutput' }); + if (value) { + console.log(value); + this.vscode.commands.executeCommand(value.command); + } }); } - public post = (event: BaseEvent) => { + public post = (event: ObservableEvent.BaseEvent) => { switch (event.constructor.name) { - case OmnisharpServerOnError.name: - this.warningMessageDebouncer.onNext(event); + case ObservableEvent.OmnisharpServerOnError.name: + this.subject.onNext(event); break; - case OmnisharpServerMsBuildProjectDiagnostics.name: - this.handleOmnisharpServerMsBuildProjectDiagnostics(event); + case ObservableEvent.OmnisharpServerMsBuildProjectDiagnostics.name: + this.handleOmnisharpServerMsBuildProjectDiagnostics(event); break; } } - private handleOmnisharpServerMsBuildProjectDiagnostics(event: OmnisharpServerMsBuildProjectDiagnostics) { + private handleOmnisharpServerMsBuildProjectDiagnostics(event: ObservableEvent.OmnisharpServerMsBuildProjectDiagnostics) { if (event.diagnostics.Errors.length > 0) { - this.warningMessageDebouncer.onNext(event); + //this.showMessageSoon(); + this.subject.onNext(event); } } } \ No newline at end of file diff --git a/test/unitTests/logging/OmnisharpServerStatusObserver.test.ts b/test/unitTests/logging/OmnisharpServerStatusObserver.test.ts deleted file mode 100644 index 0d43e01e46..0000000000 --- a/test/unitTests/logging/OmnisharpServerStatusObserver.test.ts +++ /dev/null @@ -1,63 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as rx from 'rx'; - -import { MessageItemWithCommand, OmnisharpServerStatusObserver } from '../../../src/observers/OmnisharpServerStatusObserver'; -import { expect, should } from 'chai'; -import { getMSBuildDiagnosticsMessage, getOmnisharpMSBuildProjectDiagnostics } from './Fakes'; - -import { getFakeVsCode } from './Fakes'; -import { resolve } from 'path'; -import { vscode } from '../../../src/vscodeAdapter'; - -suite('OmnisharpServerStatusObserver', () => { - let warningMessage; - let invokedCommand; - let scheduler: rx.HistoricalScheduler; - let observer: OmnisharpServerStatusObserver; - let vscode: vscode = getFakeVsCode(); - - vscode.window.showWarningMessage = (message, ...items) => { - warningMessage = message; - - return undefined; - }; - - vscode.commands.executeCommand = (command, ...rest) => { - invokedCommand = command; - return undefined; - }; - - suiteSetup(() => should()); - - setup(() => { - scheduler = new rx.HistoricalScheduler(0, (x, y) => { - return x > y ? 1 : -1; - }); - observer = new OmnisharpServerStatusObserver(vscode); - }); - - test('OmnisharpServerMsBuildProjectDiagnostics: No action is taken if the errors array is empty', () => { - let event = getOmnisharpMSBuildProjectDiagnostics("someFile", - [getMSBuildDiagnosticsMessage("warningFile", "", "", 0, 0, 0, 0)], - []); - observer.post(event); - expect(output).to.be.empty; - }); - - test('OmnisharpServerMsBuildProjectDiagnostics: No action is taken if the errors array is empty', () => { - let event = getOmnisharpMSBuildProjectDiagnostics("someFile", - [getMSBuildDiagnosticsMessage("warningFile", "", "", 1, 2, 3, 4)], - [getMSBuildDiagnosticsMessage("errorFile", "", "", 5, 6, 7, 8)]); - observer.post(event); - scheduler.advanceBy(1500); - console.log(output); - expect(output).to.contain("Show Output"); - expect(output).to.contain("o.showOutput"); - expect(output).to.contain("show warning message called"); - expect(output).to.contain("execute command called"); - }); -}); \ No newline at end of file diff --git a/test/unitTests/logging/WarningMessageObserver.test.ts b/test/unitTests/logging/WarningMessageObserver.test.ts index b4e656edd9..e418270008 100644 --- a/test/unitTests/logging/WarningMessageObserver.test.ts +++ b/test/unitTests/logging/WarningMessageObserver.test.ts @@ -3,53 +3,48 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { should, expect } from 'chai'; import * as rx from 'rx'; -import { WarningMessageObserver, ShowWarningMessage, MessageItemWithCommand, ExecuteCommand } from '../../../src/observers/WarningMessageObserver'; -import { resolve } from 'path'; -import { getOmnisharpMSBuildProjectDiagnosticsEvent, getMSBuildDiagnosticsMessage, getOmnisharpServerOnErrorEvent } from './Fakes'; -import * as vscode from '../../../src/vscodeAdapter'; + +import { MessageItemWithCommand, WarningMessageObserver } from '../../../src/observers/WarningMessageObserver'; +import { use as chaiUse, expect, should } from 'chai'; +import { getFakeVsCode, getMSBuildDiagnosticsMessage, getOmnisharpMSBuildProjectDiagnosticsEvent, getOmnisharpServerOnErrorEvent } from './Fakes'; + import { BaseEvent } from '../../../src/omnisharp/loggingEvents'; +import { resolve } from 'path'; +import { vscode } from '../../../src/vscodeAdapter'; + +chaiUse(require('chai-string')); suite('OmnisharpServerStatusObserver', () => { suiteSetup(() => should()); - let output = ''; + + let warningMessage; + let invokedCommand; let scheduler: rx.HistoricalScheduler; let observer: WarningMessageObserver; - let commandExecuted: () => void; + let vscode: vscode = getFakeVsCode(); - let warningFunction: ShowWarningMessage = (message, ...items) => { - output += "show warning message called"; - output += message; - items.forEach(element => { - output += element.title; - output += element.command; - }); - - let testMessage: MessageItemWithCommand = { - title: "myTitle", - command: "myCommand" - }; - - return Promise.resolve(testMessage); + vscode.window.showWarningMessage = (message, ...items) => { + warningMessage = message; + + return undefined; }; - let executeCommand: ExecuteCommand = (command, ...rest) => { - output += "execute command called"; - output += command; - console.log(output); - return new Promise(resolve => { - resolve("execute command resolved"); - commandExecuted(); - }); + vscode.commands.executeCommand = (command, ...rest) => { + invokedCommand = command; + return undefined; }; setup(() => { - output = ''; scheduler = new rx.HistoricalScheduler(0, (x, y) => { return x > y ? 1 : -1; }); - observer = new WarningMessageObserver(warningFunction, executeCommand, scheduler); + observer = new WarningMessageObserver(vscode); + }); + + beforeEach(() => { + warningMessage = undefined; + invokedCommand = undefined; }); test('OmnisharpServerMsBuildProjectDiagnostics: No action is taken if the errors array is empty', () => { @@ -57,7 +52,7 @@ suite('OmnisharpServerStatusObserver', () => { [getMSBuildDiagnosticsMessage("warningFile", "", "", 0, 0, 0, 0)], []); observer.post(event); - expect(output).to.be.empty; + expect(invokedCommand).to.be.undefined; }); @@ -73,34 +68,26 @@ suite('OmnisharpServerStatusObserver', () => { [getMSBuildDiagnosticsMessage("errorFile", "", "", 5, 6, 7, 8)]); observer.post(event); scheduler.advanceBy(1000); //since the debounce time is 1500 no output should be there - expect(output).to.be.empty; + expect(invokedCommand).to.be.undefined; }); test(`${event.constructor.name}: If a event is fired within 1500ms the first event is debounced`, () => { observer.post(event); scheduler.advanceBy(1000); - expect(output).to.be.empty; + expect(invokedCommand).to.be.undefined; observer.post(event); scheduler.advanceBy(500); - expect(output).to.be.empty; + expect(invokedCommand).to.be.undefined; scheduler.advanceBy(1000); - expect(output).to.not.be.empty; + expect(invokedCommand).to.be.undefined; //once there is a silence for 1500 ms the function will be invoked }); - test(`${event.constructor.name}: Show warning message and execute command are called`, (done) => { + test(`${event.constructor.name}: Show warning message and execute command are called`, () => { let event = getOmnisharpMSBuildProjectDiagnosticsEvent("someFile", [getMSBuildDiagnosticsMessage("warningFile", "", "", 1, 2, 3, 4)], [getMSBuildDiagnosticsMessage("errorFile", "", "", 5, 6, 7, 8)]); - commandExecuted = () => { - expect(output).to.contain("Show Output"); - expect(output).to.contain("o.showOutput"); - expect(output).to.contain("show warning message called"); - expect(output).to.contain("execute command called"); - done(); - }; - observer.post(event); scheduler.advanceBy(1500); }); From 439b1d1fe8b571cfff6e2a65f41b0da5fbe24dd6 Mon Sep 17 00:00:00 2001 From: Akshita Agarwal Date: Mon, 26 Mar 2018 13:52:58 -0700 Subject: [PATCH 21/56] using rx debounce --- src/observers/OmnisharpStatusBarObserver.ts | 30 ++++++++++++++------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/src/observers/OmnisharpStatusBarObserver.ts b/src/observers/OmnisharpStatusBarObserver.ts index 198c5f85c0..460cb53643 100644 --- a/src/observers/OmnisharpStatusBarObserver.ts +++ b/src/observers/OmnisharpStatusBarObserver.ts @@ -8,7 +8,7 @@ import { Status } from './status'; import * as serverUtils from '../omnisharp/utils'; import { basename } from 'path'; import { OmniSharpServer } from '../omnisharp/server'; -import { CompositeDisposable, Subject } from 'rx'; +import { CompositeDisposable, Subject, Scheduler } from 'rx'; import { BaseEvent, OmnisharpServerOnServerError, OmnisharpOnMultipleLaunchTargets, OmnisharpOnBeforeServerInstall, OmnisharpOnBeforeServerStart, ActiveTextEditorChanged, OmnisharpServerOnStop, OmnisharpServerOnStart } from '../omnisharp/loggingEvents'; const debounce = require('lodash.debounce'); @@ -34,9 +34,14 @@ export class OmnisharpStatusBarObserver { private defaultStatus: Status; private projectStatus: Status; private localDisposables: CompositeDisposable; + private updateProjectDebouncer: Subject; + private firstUpdateProject: boolean; - constructor(private getActiveTextEditor: GetActiveTextEditor, private match: Match, private statusBar: vscode.StatusBarItem) { + constructor(private getActiveTextEditor: GetActiveTextEditor, private match: Match, private statusBar: vscode.StatusBarItem, scheduler: Scheduler) { this.defaultStatus = new Status(defaultSelector); + this.updateProjectDebouncer = new Subject(); + this.updateProjectDebouncer.debounce(1500, scheduler).subscribe((server: OmniSharpServer) => { this.updateProjectInfo(server); }); + this.firstUpdateProject = true; } public post = (event: BaseEvent) => { @@ -70,6 +75,7 @@ export class OmnisharpStatusBarObserver { } private updateProjectInfo = (server: OmniSharpServer) => { + this.firstUpdateProject = false; serverUtils.requestWorkspaceInformation(server).then(info => { interface Project { @@ -161,7 +167,7 @@ export class OmnisharpStatusBarObserver { private handleOmnisharpServerOnStop() { this.projectStatus = undefined; this.defaultStatus.text = undefined; - let disposables = this.localDisposables; + let disposables = this.localDisposables; this.localDisposables = undefined; if (disposables) { @@ -173,12 +179,18 @@ export class OmnisharpStatusBarObserver { this.localDisposables = new CompositeDisposable(); SetStatus(this.defaultStatus, '$(flame) Running', 'o.pickProjectAndStart', ''); this.render(); - let updateProjectDebouncer = new Subject(); - let updateProjectInfoFunction = () => this.updateProjectInfo(event.server); - let debouncedUpdateProjectInfo = debounce(updateProjectInfoFunction, 1500, { leading: true }); - this.localDisposables.add(event.server.onProjectAdded(debouncedUpdateProjectInfo)); - this.localDisposables.add(event.server.onProjectChange(debouncedUpdateProjectInfo)); - this.localDisposables.add(event.server.onProjectRemoved(debouncedUpdateProjectInfo)); + let updateTracker = () => { + if (this.firstUpdateProject) { + this.updateProjectInfo(event.server); + } + else { + this.updateProjectDebouncer.onNext(event.server); + } + }; + + this.localDisposables.add(event.server.onProjectAdded(updateTracker)); + this.localDisposables.add(event.server.onProjectChange(updateTracker)); + this.localDisposables.add(event.server.onProjectRemoved(updateTracker)); } } From 0b0b2ab009d75131aa685b04eee2e38f17333e6b Mon Sep 17 00:00:00 2001 From: Unknown Date: Tue, 27 Mar 2018 10:12:52 -0700 Subject: [PATCH 22/56] Warning Message Observer tests --- package-lock.json | 9 ++ package.json | 1 + src/main.ts | 4 +- src/observers/OmnisharpStatusBarObserver.ts | 10 +- src/observers/WarningMessageObserver.ts | 34 +++---- test/unitTests/logging/Fakes.ts | 2 - .../InformationMessageObserver.test.ts | 18 ++++ .../OmnisharpStatusBarObserver.test.ts | 22 +---- .../logging/WarningMessageObserver.test.ts | 96 ++++++++++++------- 9 files changed, 112 insertions(+), 84 deletions(-) create mode 100644 test/unitTests/logging/InformationMessageObserver.test.ts diff --git a/package-lock.json b/package-lock.json index c26963641c..aa6708d0b5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,15 @@ "@types/chai": "4.0.8" } }, + "@types/chai-as-promised": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@types/chai-as-promised/-/chai-as-promised-7.1.0.tgz", + "integrity": "sha512-MFiW54UOSt+f2bRw8J7LgQeIvE/9b4oGvwU7XW30S9QGAiHGnU/fmiOprsyMkdmH2rl8xSPc0/yrQw8juXU6bQ==", + "dev": true, + "requires": { + "@types/chai": "4.0.8" + } + }, "@types/chai-string": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/@types/chai-string/-/chai-string-1.4.0.tgz", diff --git a/package.json b/package.json index b7545c2ef3..919bc90d50 100644 --- a/package.json +++ b/package.json @@ -81,6 +81,7 @@ "devDependencies": { "@types/chai": "4.0.8", "@types/chai-arrays": "1.0.2", + "@types/chai-as-promised": "^7.1.0", "@types/chai-string": "^1.4.0", "@types/fs-extra": "4.0.5", "@types/mkdirp": "0.5.1", diff --git a/src/main.ts b/src/main.ts index 862cf7b45a..49a2a201f1 100644 --- a/src/main.ts +++ b/src/main.ts @@ -9,8 +9,7 @@ import * as util from './common'; import * as vscode from 'vscode'; import { ActivationFailure, ActiveTextEditorChanged } from './omnisharp/loggingEvents'; -import { MessageItemWithCommand, WarningMessageObserver } from './observers/WarningMessageObserver'; - +import { WarningMessageObserver } from './observers/WarningMessageObserver'; import { CSharpExtDownloader } from './CSharpExtDownloader'; import { CsharpChannelObserver } from './observers/CsharpChannelObserver'; import { CsharpLoggerObserver } from './observers/CsharpLoggerObserver'; @@ -26,7 +25,6 @@ import { PlatformInformation } from './platform'; import { StatusBarItemAdapter } from './statusBarItemAdapter'; import { TelemetryObserver } from './observers/TelemetryObserver'; import TelemetryReporter from 'vscode-extension-telemetry'; -import { TextEditorAdapter } from './textEditorAdapter'; import { addJSONProviders } from './features/json/jsonContributions'; export async function activate(context: vscode.ExtensionContext): Promise<{ initializationFinished: Promise }> { diff --git a/src/observers/OmnisharpStatusBarObserver.ts b/src/observers/OmnisharpStatusBarObserver.ts index 079aa67c4d..8047f88985 100644 --- a/src/observers/OmnisharpStatusBarObserver.ts +++ b/src/observers/OmnisharpStatusBarObserver.ts @@ -3,17 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - -import * as ObservableEvent from "../omnisharp/loggingEvents"; import * as serverUtils from '../omnisharp/utils'; - -import { CompositeDisposable, Subject } from 'rx'; +import { CompositeDisposable, Subject, Scheduler } from 'rx'; import { DocumentFilter, DocumentSelector, StatusBarItem, vscode } from '../vscodeAdapter'; - import { OmniSharpServer } from '../omnisharp/server'; -import { ProjectInformationResponse } from '../omnisharp/protocol'; import { Status } from './status'; import { basename } from 'path'; +import { OmnisharpServerOnServerError, BaseEvent, OmnisharpOnMultipleLaunchTargets, OmnisharpOnBeforeServerInstall, OmnisharpOnBeforeServerStart, ActiveTextEditorChanged, OmnisharpServerOnStop, OmnisharpServerOnStart } from "../omnisharp/loggingEvents"; let defaultSelector: DocumentSelector = [ 'csharp', // c#-files OR @@ -31,7 +27,7 @@ export class OmnisharpStatusBarObserver { private updateProjectDebouncer: Subject; private firstUpdateProject: boolean; - constructor(private vscode: vscode, private statusBar: StatusBarItem) { + constructor(private vscode: vscode, private statusBar: StatusBarItem, scheduler?: Scheduler) { this.defaultStatus = new Status(defaultSelector); this.updateProjectDebouncer = new Subject(); this.updateProjectDebouncer.debounce(1500, scheduler).subscribe((server: OmniSharpServer) => { this.updateProjectInfo(server); }); diff --git a/src/observers/WarningMessageObserver.ts b/src/observers/WarningMessageObserver.ts index d82007cd5c..e6f867c8b6 100644 --- a/src/observers/WarningMessageObserver.ts +++ b/src/observers/WarningMessageObserver.ts @@ -3,46 +3,42 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as ObservableEvent from "../omnisharp/loggingEvents"; - import { MessageItem, vscode } from '../vscodeAdapter'; import { Scheduler, Subject } from 'rx'; +import { BaseEvent, OmnisharpServerOnError, OmnisharpServerMsBuildProjectDiagnostics } from "../omnisharp/loggingEvents"; -export interface MessageItemWithCommand extends MessageItem{ - command: string; -} +export interface MessageItemWithCommand extends MessageItem { + command: string; +} export class WarningMessageObserver { - private warningMessageDebouncer: Subject; constructor(private vscode: vscode, scheduler?: Scheduler) { - this.subject = new Subject(); - this.subject.debounce(1500).subscribe(async (event) => { + this.warningMessageDebouncer = new Subject(); + this.warningMessageDebouncer.debounce(1500, scheduler).subscribe(async event => { let message = "Some projects have trouble loading. Please review the output for more details."; - let value = await this.vscode.window.showWarningMessage(message, { title: "Show Output", command: 'o.showOutput' }); + let value = await this.vscode.window.showWarningMessage(message, { title: "Show Output", command: 'o.showOutput' }); if (value) { - console.log(value); - this.vscode.commands.executeCommand(value.command); + await this.vscode.commands.executeCommand(value.command); } }); } - public post = (event: ObservableEvent.BaseEvent) => { + public post = (event: BaseEvent) => { switch (event.constructor.name) { - case ObservableEvent.OmnisharpServerOnError.name: - this.subject.onNext(event); + case OmnisharpServerOnError.name: + this.warningMessageDebouncer.onNext(event); break; - case ObservableEvent.OmnisharpServerMsBuildProjectDiagnostics.name: - this.handleOmnisharpServerMsBuildProjectDiagnostics(event); + case OmnisharpServerMsBuildProjectDiagnostics.name: + this.handleOmnisharpServerMsBuildProjectDiagnostics(event); break; } } - private handleOmnisharpServerMsBuildProjectDiagnostics(event: ObservableEvent.OmnisharpServerMsBuildProjectDiagnostics) { + private handleOmnisharpServerMsBuildProjectDiagnostics(event: OmnisharpServerMsBuildProjectDiagnostics) { if (event.diagnostics.Errors.length > 0) { - //this.showMessageSoon(); - this.subject.onNext(event); + this.warningMessageDebouncer.onNext(event); } } } \ No newline at end of file diff --git a/test/unitTests/logging/Fakes.ts b/test/unitTests/logging/Fakes.ts index c02d41ba0a..53a049c707 100644 --- a/test/unitTests/logging/Fakes.ts +++ b/test/unitTests/logging/Fakes.ts @@ -4,9 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from '../../../src/vscodeAdapter'; - import { DocumentSelector, MessageItem, TextDocument, Uri } from '../../../src/vscodeAdapter'; - import { ITelemetryReporter } from '../../../src/observers/TelemetryObserver'; import { MSBuildDiagnosticsMessage } from '../../../src/omnisharp/protocol'; import { OmnisharpServerMsBuildProjectDiagnostics, OmnisharpServerOnError } from '../../../src/omnisharp/loggingEvents'; diff --git a/test/unitTests/logging/InformationMessageObserver.test.ts b/test/unitTests/logging/InformationMessageObserver.test.ts new file mode 100644 index 0000000000..43ef875a23 --- /dev/null +++ b/test/unitTests/logging/InformationMessageObserver.test.ts @@ -0,0 +1,18 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as rx from 'rx'; +import { InformationMessageObserver } from '../../../src/observers/InformationMessageObserver'; +import { expect, should } from 'chai'; +import { vscode } from '../../../src/vscodeAdapter'; +import { getFakeVsCode } from './Fakes'; + +suite("InformationMessageObserver", () => { + suiteSetup(() => should()); + + let vscode: vscode = getFakeVsCode(); + let observer = new InformationMessageObserver(vscode); + +}); \ No newline at end of file diff --git a/test/unitTests/logging/OmnisharpStatusBarObserver.test.ts b/test/unitTests/logging/OmnisharpStatusBarObserver.test.ts index 01d41e1036..f891c09c29 100644 --- a/test/unitTests/logging/OmnisharpStatusBarObserver.test.ts +++ b/test/unitTests/logging/OmnisharpStatusBarObserver.test.ts @@ -6,7 +6,6 @@ import { DocumentSelector, StatusBarItem, vscode } from '../../../src/vscodeAdapter'; import { OmnisharpOnBeforeServerInstall, OmnisharpOnBeforeServerStart, OmnisharpOnMultipleLaunchTargets, OmnisharpServerOnServerError } from '../../../src/omnisharp/loggingEvents'; import { expect, should } from 'chai'; - import { OmnisharpStatusBarObserver } from '../../../src/observers/OmnisharpStatusBarObserver'; import { getFakeVsCode } from './Fakes'; @@ -33,38 +32,27 @@ suite('OmnisharpServerStatusObserver', () => { let event = new OmnisharpServerOnServerError("someError"); observer.post(event); expect(showCalled).to.be.true; - expect(statusBar.text.toLowerCase()).to.contain("error"); - expect(statusBar.text).to.contain("$(flame)"); //omnisharp flame + expect(statusBar.text).to.equal(`$(flame) Error starting OmniSharp`); }); test('OnBeforeServerInstall: If there is no project status, default status should be shown which includes install and flame', () => { let event = new OmnisharpOnBeforeServerInstall(); observer.post(event); expect(showCalled).to.be.true; - expect(statusBar.text.toLowerCase()).to.contain("install"); - expect(statusBar.text).to.contain("$(flame)"); + expect(statusBar.text).to.be.equal('$(flame) Installing OmniSharp...'); }); test('OnBeforeServerStart: If there is no project status, default status should be shown which includes start and flame', () => { let event = new OmnisharpOnBeforeServerStart(); observer.post(event); expect(showCalled).to.be.true; - expect(statusBar.text.toLowerCase()).to.contain("start"); - expect(statusBar.text).to.contain("$(flame)"); + expect(statusBar.text).to.be.equal('$(flame) Starting...'); }); - test('OnMultipleLaunchTargets: If there is no project status, default status should be shown which includes error and flame', () => { + test('OnMultipleLaunchTargets: If there is no project status, default status should be shown which includes select project and flame', () => { let event = new OmnisharpOnMultipleLaunchTargets([]); observer.post(event); expect(showCalled).to.be.true; - expect(statusBar.text.toLowerCase()).to.contain("select project"); - expect(statusBar.text).to.contain("$(flame)"); + expect(statusBar.text).to.be.equal('$(flame) Select project'); }); - - - - - - - }); \ No newline at end of file diff --git a/test/unitTests/logging/WarningMessageObserver.test.ts b/test/unitTests/logging/WarningMessageObserver.test.ts index 3f5f56e728..d336fc18f2 100644 --- a/test/unitTests/logging/WarningMessageObserver.test.ts +++ b/test/unitTests/logging/WarningMessageObserver.test.ts @@ -4,47 +4,62 @@ *--------------------------------------------------------------------------------------------*/ import * as rx from 'rx'; - import { MessageItemWithCommand, WarningMessageObserver } from '../../../src/observers/WarningMessageObserver'; import { use as chaiUse, expect, should } from 'chai'; import { getFakeVsCode, getMSBuildDiagnosticsMessage, getOmnisharpMSBuildProjectDiagnosticsEvent, getOmnisharpServerOnErrorEvent } from './Fakes'; - import { BaseEvent } from '../../../src/omnisharp/loggingEvents'; -import { resolve } from 'path'; import { vscode } from '../../../src/vscodeAdapter'; +import { Observable } from 'rx'; +chaiUse(require('chai-as-promised')); chaiUse(require('chai-string')); suite('OmnisharpServerStatusObserver', () => { suiteSetup(() => should()); - + + let doClickOk: () => void; + let doClickCancel: () => void; + let signalCommandDone: () => void; + let commandDone = new Promise(resolve => { + signalCommandDone = () => { resolve(); }; + }); + let warningMessage; let invokedCommand; let scheduler: rx.HistoricalScheduler; let observer: WarningMessageObserver; let vscode: vscode = getFakeVsCode(); - vscode.window.showWarningMessage = (message, ...items) => { + vscode.window.showWarningMessage = (message, ...items) => { warningMessage = message; - - return undefined; - }; + return new Promise(resolve => { + doClickCancel = () => { + resolve(undefined); + }; + + doClickOk = () => { + resolve(items[0]); + }; + }); + }; vscode.commands.executeCommand = (command, ...rest) => { invokedCommand = command; + signalCommandDone(); return undefined; + }; setup(() => { scheduler = new rx.HistoricalScheduler(0, (x, y) => { return x > y ? 1 : -1; }); - observer = new WarningMessageObserver(vscode); - }); - - beforeEach(() => { + observer = new WarningMessageObserver(vscode, scheduler); warningMessage = undefined; invokedCommand = undefined; + commandDone = new Promise(resolve => { + signalCommandDone = () => { resolve(); }; + }); }); test('OmnisharpServerMsBuildProjectDiagnostics: No action is taken if the errors array is empty', () => { @@ -59,33 +74,42 @@ suite('OmnisharpServerStatusObserver', () => { getOmnisharpMSBuildProjectDiagnosticsEvent("someFile", [getMSBuildDiagnosticsMessage("warningFile", "", "", 1, 2, 3, 4)], [getMSBuildDiagnosticsMessage("errorFile", "", "", 5, 6, 7, 8)]), - getOmnisharpServerOnErrorEvent("someText", "someFile", 1, 2) + getOmnisharpServerOnErrorEvent("someText", "someFile", 1, 2) ].forEach((event: BaseEvent) => { - test(`${event.constructor.name}: Debouce function`, () => { - observer.post(event); - scheduler.advanceBy(1000); //since the debounce time is 1500 no output should be there - expect(invokedCommand).to.be.undefined; - }); + suite(`${event.constructor.name}`, () => { + test(`When the event is fired then a warning message is displayed`, () => { + observer.post(event); + scheduler.advanceBy(1500); //since the debounce time is 1500 no output should be there + expect(warningMessage).to.be.equal("Some projects have trouble loading. Please review the output for more details."); + }); - test(`${event.constructor.name}: If an event is fired within 1500ms the first event is debounced`, () => { - observer.post(event); - scheduler.advanceBy(1000); - expect(invokedCommand).to.be.undefined; - observer.post(event); - scheduler.advanceBy(500); - expect(invokedCommand).to.be.undefined; - scheduler.advanceBy(1000); - expect(invokedCommand).to.be.undefined; - //once there is a silence for 1500 ms the function will be invoked - }); + test(`When events are fired rapidly, then they are debounced by 1500 ms`, () => { + observer.post(event); + scheduler.advanceBy(1000); + expect(warningMessage).to.be.undefined; + observer.post(event); + scheduler.advanceBy(500); + expect(warningMessage).to.be.undefined; + scheduler.advanceBy(1000); + expect(warningMessage).to.not.be.empty; + //once there is a silence for 1500 ms the function will be invoked + }); - test(`${event.constructor.name}: Show warning message and execute command are called`, () => { - observer.post(event); - scheduler.advanceBy(1500); - }); - }); + test(`Given a warning message, when the user clicks ok the command is executed`, async () => { + observer.post(event); + scheduler.advanceBy(1500); + doClickOk(); + await commandDone; + expect(invokedCommand).to.be.equal("o.showOutput"); + }); - teardown(() => { - commandExecuted = undefined; + test(`Given a warning message, when the user clicks cancel the command is not executed`, async () => { + observer.post(event); + scheduler.advanceBy(1500); + doClickCancel(); + await expect(Observable.fromPromise(commandDone).timeout(1).toPromise()).to.be.rejected; + expect(invokedCommand).to.be.undefined; + }); + }); }); }); \ No newline at end of file From 4ee140712a5f6805a4eccdd2752fcb4271a6dcaa Mon Sep 17 00:00:00 2001 From: Unknown Date: Tue, 27 Mar 2018 10:50:58 -0700 Subject: [PATCH 23/56] Remove loadsh.debounce and renamed observer --- package.json | 1 - src/main.ts | 4 ++-- src/observers/OmnisharpStatusBarObserver.ts | 14 +++++++------- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index 919bc90d50..cdb010e3f5 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,6 @@ "https-proxy-agent": "^2.1.1", "istanbul": "^0.4.5", "jsonc-parser": "^1.0.0", - "lodash.debounce": "^4.0.8", "mkdirp": "^0.5.1", "open": "*", "request-light": "^0.2.0", diff --git a/src/main.ts b/src/main.ts index 49a2a201f1..c29e4d4cc1 100644 --- a/src/main.ts +++ b/src/main.ts @@ -57,8 +57,8 @@ export async function activate(context: vscode.ExtensionContext): Promise<{ init eventStream.subscribe(omnisharpLogObserver.post); eventStream.subscribe(omnisharpChannelObserver.post); - let omnisharpServerStatusObserver = new WarningMessageObserver(vscode); - eventStream.subscribe(omnisharpServerStatusObserver.post); + let warningMessageObserver = new WarningMessageObserver(vscode); + eventStream.subscribe(warningMessageObserver.post); let informationMessageObserver = new InformationMessageObserver(vscode); eventStream.subscribe(informationMessageObserver.post); diff --git a/src/observers/OmnisharpStatusBarObserver.ts b/src/observers/OmnisharpStatusBarObserver.ts index 8047f88985..386df81946 100644 --- a/src/observers/OmnisharpStatusBarObserver.ts +++ b/src/observers/OmnisharpStatusBarObserver.ts @@ -27,7 +27,7 @@ export class OmnisharpStatusBarObserver { private updateProjectDebouncer: Subject; private firstUpdateProject: boolean; - constructor(private vscode: vscode, private statusBar: StatusBarItem, scheduler?: Scheduler) { + constructor(private vscode: vscode, private statusBarItem: StatusBarItem, scheduler?: Scheduler) { this.defaultStatus = new Status(defaultSelector); this.updateProjectDebouncer = new Subject(); this.updateProjectDebouncer.debounce(1500, scheduler).subscribe((server: OmniSharpServer) => { this.updateProjectInfo(server); }); @@ -130,7 +130,7 @@ export class OmnisharpStatusBarObserver { private render = () => { let activeTextEditor = this.vscode.window.activeTextEditor; if (!activeTextEditor) { - this.statusBar.hide(); + this.statusBarItem.hide(); return; } @@ -144,14 +144,14 @@ export class OmnisharpStatusBarObserver { } if (status) { - this.statusBar.text = status.text; - this.statusBar.command = status.command; - this.statusBar.color = status.color; - this.statusBar.show(); + this.statusBarItem.text = status.text; + this.statusBarItem.command = status.command; + this.statusBarItem.color = status.color; + this.statusBarItem.show(); return; } - this.statusBar.hide(); + this.statusBarItem.hide(); } private handleOmnisharpServerOnStop() { From 9eb6e5543ce41ce9a4b0ebe7b148442bf96633e5 Mon Sep 17 00:00:00 2001 From: Unknown Date: Tue, 27 Mar 2018 12:58:36 -0700 Subject: [PATCH 24/56] Move the workspace info invocation to server --- src/observers/OmnisharpStatusBarObserver.ts | 98 +++++++++------------ src/omnisharp/loggingEvents.ts | 16 ++-- src/omnisharp/server.ts | 54 +++++++++--- 3 files changed, 93 insertions(+), 75 deletions(-) diff --git a/src/observers/OmnisharpStatusBarObserver.ts b/src/observers/OmnisharpStatusBarObserver.ts index 386df81946..4e3f0d332e 100644 --- a/src/observers/OmnisharpStatusBarObserver.ts +++ b/src/observers/OmnisharpStatusBarObserver.ts @@ -3,13 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as serverUtils from '../omnisharp/utils'; +//import * as serverUtils from '../omnisharp/utils'; import { CompositeDisposable, Subject, Scheduler } from 'rx'; import { DocumentFilter, DocumentSelector, StatusBarItem, vscode } from '../vscodeAdapter'; import { OmniSharpServer } from '../omnisharp/server'; import { Status } from './status'; import { basename } from 'path'; -import { OmnisharpServerOnServerError, BaseEvent, OmnisharpOnMultipleLaunchTargets, OmnisharpOnBeforeServerInstall, OmnisharpOnBeforeServerStart, ActiveTextEditorChanged, OmnisharpServerOnStop, OmnisharpServerOnStart } from "../omnisharp/loggingEvents"; +import { OmnisharpServerOnServerError, BaseEvent, OmnisharpOnMultipleLaunchTargets, OmnisharpOnBeforeServerInstall, OmnisharpOnBeforeServerStart, ActiveTextEditorChanged, OmnisharpServerOnStop, OmnisharpServerOnStart, ProjectModified, WorkspaceInformationUpdated } from "../omnisharp/loggingEvents"; let defaultSelector: DocumentSelector = [ 'csharp', // c#-files OR @@ -29,9 +29,9 @@ export class OmnisharpStatusBarObserver { constructor(private vscode: vscode, private statusBarItem: StatusBarItem, scheduler?: Scheduler) { this.defaultStatus = new Status(defaultSelector); - this.updateProjectDebouncer = new Subject(); + /*this.updateProjectDebouncer = new Subject(); this.updateProjectDebouncer.debounce(1500, scheduler).subscribe((server: OmniSharpServer) => { this.updateProjectInfo(server); }); - this.firstUpdateProject = true; + this.firstUpdateProject = true;*/ } public post = (event: BaseEvent) => { @@ -61,71 +61,55 @@ export class OmnisharpStatusBarObserver { case OmnisharpServerOnStart.name: this.handleOmnisharpServerOnStart(event); break; + case WorkspaceInformationUpdated.name: + this.handleWorkspaceInformationUpdated(event); } } - private updateProjectInfo = (server: OmniSharpServer) => { - this.firstUpdateProject = false; - serverUtils.requestWorkspaceInformation(server).then(info => { - - interface Project { - Path: string; - SourceFiles: string[]; - } + private handleWorkspaceInformationUpdated(event: WorkspaceInformationUpdated) { + interface Project { + Path: string; + SourceFiles: string[]; + } - let fileNames: DocumentFilter[] = []; - let label: string; + let fileNames: DocumentFilter[] = []; + let label: string; - function addProjectFileNames(project: Project) { - fileNames.push({ pattern: project.Path }); + function addProjectFileNames(project: Project) { + fileNames.push({ pattern: project.Path }); - if (project.SourceFiles) { - for (let sourceFile of project.SourceFiles) { - fileNames.push({ pattern: sourceFile }); - } + if (project.SourceFiles) { + for (let sourceFile of project.SourceFiles) { + fileNames.push({ pattern: sourceFile }); } } + } - function addDnxOrDotNetProjects(projects: Project[]) { - let count = 0; - - for (let project of projects) { - count += 1; - addProjectFileNames(project); - } + let info = event.info; + // show sln-file if applicable + if (info.MsBuild && info.MsBuild.SolutionPath) { + label = basename(info.MsBuild.SolutionPath); //workspace.getRelativePath(info.MsBuild.SolutionPath); + fileNames.push({ pattern: info.MsBuild.SolutionPath }); - if (!label) { - if (count === 1) { - label = basename(projects[0].Path); //workspace.getRelativePath(info.Dnx.Projects[0].Path); - } - else { - label = `${count} projects`; - } - } + for (let project of info.MsBuild.Projects) { + addProjectFileNames(project); } + } - // show sln-file if applicable - if (info.MsBuild && info.MsBuild.SolutionPath) { - label = basename(info.MsBuild.SolutionPath); //workspace.getRelativePath(info.MsBuild.SolutionPath); - fileNames.push({ pattern: info.MsBuild.SolutionPath }); - - for (let project of info.MsBuild.Projects) { - addProjectFileNames(project); - } - } + // set project info + this.projectStatus = new Status(fileNames); + SetStatus(this.projectStatus, '$(flame) ' + label, 'o.pickProjectAndStart'); + SetStatus(this.defaultStatus, '$(flame) Switch projects', 'o.pickProjectAndStart'); + this.render(); + } - // show .NET Core projects if applicable - if (info.DotNet) { - addDnxOrDotNetProjects(info.DotNet.Projects); - } + /*private updateProjectInfo = (server: OmniSharpServer) => { + this.firstUpdateProject = false; + serverUtils.requestWorkspaceInformation(server).then(info => { - // set project info - this.projectStatus = new Status(fileNames); - SetStatus(this.projectStatus, '$(flame) ' + label, 'o.pickProjectAndStart'); - SetStatus(this.defaultStatus, '$(flame) Switch projects', 'o.pickProjectAndStart'); - this.render(); + }); - } + }*/ private render = () => { let activeTextEditor = this.vscode.window.activeTextEditor; @@ -166,10 +150,10 @@ export class OmnisharpStatusBarObserver { } private handleOmnisharpServerOnStart(event: OmnisharpServerOnStart) { - this.localDisposables = new CompositeDisposable(); + //this.localDisposables = new CompositeDisposable(); SetStatus(this.defaultStatus, '$(flame) Running', 'o.pickProjectAndStart', ''); this.render(); - let updateTracker = () => { + /*let updateTracker = () => { if (this.firstUpdateProject) { this.updateProjectInfo(event.server); } @@ -180,7 +164,7 @@ export class OmnisharpStatusBarObserver { this.localDisposables.add(event.server.onProjectAdded(updateTracker)); this.localDisposables.add(event.server.onProjectChange(updateTracker)); - this.localDisposables.add(event.server.onProjectRemoved(updateTracker)); + this.localDisposables.add(event.server.onProjectRemoved(updateTracker));*/ } } diff --git a/src/omnisharp/loggingEvents.ts b/src/omnisharp/loggingEvents.ts index 3cf7c7c715..095cd9ad01 100644 --- a/src/omnisharp/loggingEvents.ts +++ b/src/omnisharp/loggingEvents.ts @@ -100,12 +100,16 @@ export class OmnisharpOnMultipleLaunchTargets implements BaseEvent { constructor(public targets: LaunchTarget[]) { } } -export class EventWithMessage implements BaseEvent { - constructor(public message: string) { } +/*export class OmnisharpServerOnStart implements BaseEvent { + constructor(public server: OmniSharpServer) { } +}*/ + +export class WorkspaceInformationUpdated implements BaseEvent { + constructor(public info: protocol.WorkspaceInformationResponse) { } } -export class OmnisharpServerOnStart implements BaseEvent { - constructor(public server: OmniSharpServer) { } +export class EventWithMessage implements BaseEvent { + constructor(public message: string) { } } export class DebuggerPrerequisiteFailure extends EventWithMessage { } @@ -120,6 +124,7 @@ export class OmnisharpServerOnStdErr extends EventWithMessage { } export class OmnisharpServerMessage extends EventWithMessage { } export class OmnisharpServerVerboseMessage extends EventWithMessage { } +export class ProjectModified implements BaseEvent { } export class ActivationFailure implements BaseEvent { } export class CommandShowOutput implements BaseEvent { } export class DebuggerNotInstalledFailure implements BaseEvent { } @@ -130,4 +135,5 @@ export class ProjectJsonDeprecatedWarning implements BaseEvent { } export class OmnisharpOnBeforeServerStart implements BaseEvent { } export class OmnisharpOnBeforeServerInstall implements BaseEvent { } export class ActiveTextEditorChanged implements BaseEvent { } -export class OmnisharpServerOnStop implements BaseEvent { } \ No newline at end of file +export class OmnisharpServerOnStop implements BaseEvent { } +export class OmnisharpServerOnStart implements BaseEvent { } \ No newline at end of file diff --git a/src/omnisharp/server.ts b/src/omnisharp/server.ts index 6dd8f2c62f..11bf8a16de 100644 --- a/src/omnisharp/server.ts +++ b/src/omnisharp/server.ts @@ -7,6 +7,7 @@ import * as path from 'path'; import * as protocol from './protocol'; import * as utils from '../common'; import * as vscode from 'vscode'; +import * as serverUtils from '../omnisharp/utils'; import { ChildProcess, exec } from 'child_process'; import { LaunchTarget, findLaunchTargets } from './launcher'; @@ -22,7 +23,7 @@ import { setTimeout } from 'timers'; import { OmnisharpDownloader } from './OmnisharpDownloader'; import * as ObservableEvents from './loggingEvents'; import { EventStream } from '../EventStream'; -import { Disposable, CompositeDisposable } from 'rx'; +import { Disposable, CompositeDisposable, Subject } from 'rx'; enum ServerState { Starting, @@ -84,12 +85,17 @@ export class OmniSharpServer { private _omnisharpManager: OmnisharpManager; private eventStream: EventStream; + private updateProjectDebouncer: Subject; + private firstUpdateProject: boolean; constructor(eventStream: EventStream, packageJSON: any, platformInfo: PlatformInformation) { this.eventStream = eventStream; this._requestQueue = new RequestQueueCollection(this.eventStream, 8, request => this._makeRequest(request)); let downloader = new OmnisharpDownloader(this.eventStream, packageJSON, platformInfo); this._omnisharpManager = new OmnisharpManager(downloader, platformInfo); + this.updateProjectDebouncer = new Subject(); + this.updateProjectDebouncer.debounce(1500).subscribe((event) => { this.updateProjectInfo(); }); + this.firstUpdateProject = true; } public isRunning(): boolean { @@ -231,29 +237,29 @@ export class OmniSharpServer { // --- start, stop, and connect private async _start(launchTarget: LaunchTarget): Promise { - + let disposables = new CompositeDisposable(); - disposables.add(this.onServerError(err => + disposables.add(this.onServerError(err => this.eventStream.post(new ObservableEvents.OmnisharpServerOnServerError(err)) )); - disposables.add(this.onError((message: protocol.ErrorMessage) => + disposables.add(this.onError((message: protocol.ErrorMessage) => this.eventStream.post(new ObservableEvents.OmnisharpServerOnError(message)) - )); - - disposables.add(this.onMsBuildProjectDiagnostics((message:protocol.MSBuildProjectDiagnostics) => + )); + + disposables.add(this.onMsBuildProjectDiagnostics((message: protocol.MSBuildProjectDiagnostics) => this.eventStream.post(new ObservableEvents.OmnisharpServerMsBuildProjectDiagnostics(message)) - )); + )); - disposables.add(this.onUnresolvedDependencies((message: protocol.UnresolvedDependenciesMessage) => + disposables.add(this.onUnresolvedDependencies((message: protocol.UnresolvedDependenciesMessage) => this.eventStream.post(new ObservableEvents.OmnisharpServerUnresolvedDependencies(message, this, this.eventStream)) )); - disposables.add(this.onStderr((message:string) => + disposables.add(this.onStderr((message: string) => this.eventStream.post(new ObservableEvents.OmnisharpServerOnStdErr(message)) )); - disposables.add(this.onMultipleLaunchTargets((targets: LaunchTarget[]) => + disposables.add(this.onMultipleLaunchTargets((targets: LaunchTarget[]) => this.eventStream.post(new ObservableEvents.OmnisharpOnMultipleLaunchTargets(targets)) )); @@ -270,13 +276,18 @@ export class OmniSharpServer { )); disposables.add(this.onServerStart(() => { - this.eventStream.post(new ObservableEvents.OmnisharpServerOnStart(this)); + this.eventStream.post(new ObservableEvents.OmnisharpServerOnStart()); })); + //should we do this on start + disposables.add(this.onProjectAdded(this.updateTracker)); + disposables.add(this.onProjectChange(this.updateTracker)); + disposables.add(this.onProjectRemoved(this.updateTracker)); + this._disposables = new CompositeDisposable(Disposable.create(() => { disposables.dispose(); })); - + this._setState(ServerState.Starting); this._launchTarget = launchTarget; @@ -332,6 +343,23 @@ export class OmniSharpServer { }); } + // we could move this to a project updated observer and pass the server there. But we will need the event stream also there :( + private updateTracker = () => { + if (this.firstUpdateProject) { + this.updateProjectInfo(); + } + else { + this.updateProjectDebouncer.onNext(new ObservableEvents.ProjectModified()); + } + } + + private updateProjectInfo = async () => { + this.firstUpdateProject = false; + let info = await serverUtils.requestWorkspaceInformation(this); + //once we get the info, push the event into the event stream + this.eventStream.post(new ObservableEvents.WorkspaceInformationUpdated(info)); + } + public stop(): Promise { this._disposables.dispose(); From afd3f0a89135b1df91366968bd15c13cd8d3613e Mon Sep 17 00:00:00 2001 From: Unknown Date: Tue, 27 Mar 2018 14:08:49 -0700 Subject: [PATCH 25/56] Clean up --- src/observers/OmnisharpStatusBarObserver.ts | 58 +++------------------ src/omnisharp/loggingEvents.ts | 4 -- src/omnisharp/server.ts | 5 +- 3 files changed, 9 insertions(+), 58 deletions(-) diff --git a/src/observers/OmnisharpStatusBarObserver.ts b/src/observers/OmnisharpStatusBarObserver.ts index 4e3f0d332e..124e8a1b76 100644 --- a/src/observers/OmnisharpStatusBarObserver.ts +++ b/src/observers/OmnisharpStatusBarObserver.ts @@ -3,13 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -//import * as serverUtils from '../omnisharp/utils'; -import { CompositeDisposable, Subject, Scheduler } from 'rx'; import { DocumentFilter, DocumentSelector, StatusBarItem, vscode } from '../vscodeAdapter'; -import { OmniSharpServer } from '../omnisharp/server'; import { Status } from './status'; import { basename } from 'path'; -import { OmnisharpServerOnServerError, BaseEvent, OmnisharpOnMultipleLaunchTargets, OmnisharpOnBeforeServerInstall, OmnisharpOnBeforeServerStart, ActiveTextEditorChanged, OmnisharpServerOnStop, OmnisharpServerOnStart, ProjectModified, WorkspaceInformationUpdated } from "../omnisharp/loggingEvents"; +import { OmnisharpServerOnServerError, BaseEvent, OmnisharpOnMultipleLaunchTargets, OmnisharpOnBeforeServerInstall, OmnisharpOnBeforeServerStart, ActiveTextEditorChanged, OmnisharpServerOnStop, OmnisharpServerOnStart, WorkspaceInformationUpdated } from "../omnisharp/loggingEvents"; let defaultSelector: DocumentSelector = [ 'csharp', // c#-files OR @@ -23,15 +20,9 @@ let defaultSelector: DocumentSelector = [ export class OmnisharpStatusBarObserver { private defaultStatus: Status; private projectStatus: Status; - private localDisposables: CompositeDisposable; - private updateProjectDebouncer: Subject; - private firstUpdateProject: boolean; - constructor(private vscode: vscode, private statusBarItem: StatusBarItem, scheduler?: Scheduler) { + constructor(private vscode: vscode, private statusBarItem: StatusBarItem) { this.defaultStatus = new Status(defaultSelector); - /*this.updateProjectDebouncer = new Subject(); - this.updateProjectDebouncer.debounce(1500, scheduler).subscribe((server: OmniSharpServer) => { this.updateProjectInfo(server); }); - this.firstUpdateProject = true;*/ } public post = (event: BaseEvent) => { @@ -56,13 +47,15 @@ export class OmnisharpStatusBarObserver { this.render(); break; case OmnisharpServerOnStop.name: - this.handleOmnisharpServerOnStop(); + this.projectStatus = undefined; + this.defaultStatus.text = undefined; break; case OmnisharpServerOnStart.name: - this.handleOmnisharpServerOnStart(event); + SetStatus(this.defaultStatus, '$(flame) Running', 'o.pickProjectAndStart', ''); + this.render(); break; case WorkspaceInformationUpdated.name: - this.handleWorkspaceInformationUpdated(event); + this.handleWorkspaceInformationUpdated(event); } } @@ -103,14 +96,6 @@ export class OmnisharpStatusBarObserver { this.render(); } - /*private updateProjectInfo = (server: OmniSharpServer) => { - this.firstUpdateProject = false; - serverUtils.requestWorkspaceInformation(server).then(info => { - - - }); - }*/ - private render = () => { let activeTextEditor = this.vscode.window.activeTextEditor; if (!activeTextEditor) { @@ -137,35 +122,6 @@ export class OmnisharpStatusBarObserver { this.statusBarItem.hide(); } - - private handleOmnisharpServerOnStop() { - this.projectStatus = undefined; - this.defaultStatus.text = undefined; - let disposables = this.localDisposables; - this.localDisposables = undefined; - - if (disposables) { - disposables.dispose(); - } - } - - private handleOmnisharpServerOnStart(event: OmnisharpServerOnStart) { - //this.localDisposables = new CompositeDisposable(); - SetStatus(this.defaultStatus, '$(flame) Running', 'o.pickProjectAndStart', ''); - this.render(); - /*let updateTracker = () => { - if (this.firstUpdateProject) { - this.updateProjectInfo(event.server); - } - else { - this.updateProjectDebouncer.onNext(event.server); - } - }; - - this.localDisposables.add(event.server.onProjectAdded(updateTracker)); - this.localDisposables.add(event.server.onProjectChange(updateTracker)); - this.localDisposables.add(event.server.onProjectRemoved(updateTracker));*/ - } } function SetStatus(status: Status, text: string, command: string, color?: string) { diff --git a/src/omnisharp/loggingEvents.ts b/src/omnisharp/loggingEvents.ts index 095cd9ad01..6e5c1d88e4 100644 --- a/src/omnisharp/loggingEvents.ts +++ b/src/omnisharp/loggingEvents.ts @@ -100,10 +100,6 @@ export class OmnisharpOnMultipleLaunchTargets implements BaseEvent { constructor(public targets: LaunchTarget[]) { } } -/*export class OmnisharpServerOnStart implements BaseEvent { - constructor(public server: OmniSharpServer) { } -}*/ - export class WorkspaceInformationUpdated implements BaseEvent { constructor(public info: protocol.WorkspaceInformationResponse) { } } diff --git a/src/omnisharp/server.ts b/src/omnisharp/server.ts index 11bf8a16de..f3b658df39 100644 --- a/src/omnisharp/server.ts +++ b/src/omnisharp/server.ts @@ -279,11 +279,10 @@ export class OmniSharpServer { this.eventStream.post(new ObservableEvents.OmnisharpServerOnStart()); })); - //should we do this on start disposables.add(this.onProjectAdded(this.updateTracker)); disposables.add(this.onProjectChange(this.updateTracker)); - disposables.add(this.onProjectRemoved(this.updateTracker)); - + disposables.add(this.onProjectRemoved(this.updateTracker)); + this._disposables = new CompositeDisposable(Disposable.create(() => { disposables.dispose(); })); From 288548083a83af0e770769943b4247376beb3045 Mon Sep 17 00:00:00 2001 From: Unknown Date: Tue, 27 Mar 2018 14:24:43 -0700 Subject: [PATCH 26/56] More test to statusBarObserver --- .../OmnisharpStatusBarObserver.test.ts | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/test/unitTests/logging/OmnisharpStatusBarObserver.test.ts b/test/unitTests/logging/OmnisharpStatusBarObserver.test.ts index f891c09c29..b8bcdeb432 100644 --- a/test/unitTests/logging/OmnisharpStatusBarObserver.test.ts +++ b/test/unitTests/logging/OmnisharpStatusBarObserver.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { DocumentSelector, StatusBarItem, vscode } from '../../../src/vscodeAdapter'; -import { OmnisharpOnBeforeServerInstall, OmnisharpOnBeforeServerStart, OmnisharpOnMultipleLaunchTargets, OmnisharpServerOnServerError } from '../../../src/omnisharp/loggingEvents'; +import { OmnisharpOnBeforeServerInstall, OmnisharpOnBeforeServerStart, OmnisharpOnMultipleLaunchTargets, OmnisharpServerOnServerError, OmnisharpServerOnStart } from '../../../src/omnisharp/loggingEvents'; import { expect, should } from 'chai'; import { OmnisharpStatusBarObserver } from '../../../src/observers/OmnisharpStatusBarObserver'; import { getFakeVsCode } from './Fakes'; @@ -28,31 +28,43 @@ suite('OmnisharpServerStatusObserver', () => { let observer = new OmnisharpStatusBarObserver(vscode, statusBar); - test('OnServerError: If there is no project status, default status should be shown which includes error and flame', () => { + test('OnServerError: If there is no project status, default status should be shown with the error text', () => { let event = new OmnisharpServerOnServerError("someError"); observer.post(event); expect(showCalled).to.be.true; expect(statusBar.text).to.equal(`$(flame) Error starting OmniSharp`); + expect(statusBar.command).to.equal('o.showOutput'); }); - test('OnBeforeServerInstall: If there is no project status, default status should be shown which includes install and flame', () => { + test('OnBeforeServerInstall: If there is no project status, default status should be shown with the install text', () => { let event = new OmnisharpOnBeforeServerInstall(); observer.post(event); expect(showCalled).to.be.true; expect(statusBar.text).to.be.equal('$(flame) Installing OmniSharp...'); + expect(statusBar.command).to.equal('o.showOutput'); }); - test('OnBeforeServerStart: If there is no project status, default status should be shown which includes start and flame', () => { + test('OnBeforeServerStart: If there is no project status, default status should be shown as Starting..', () => { let event = new OmnisharpOnBeforeServerStart(); observer.post(event); expect(showCalled).to.be.true; expect(statusBar.text).to.be.equal('$(flame) Starting...'); + expect(statusBar.command).to.equal('o.showOutput'); }); - test('OnMultipleLaunchTargets: If there is no project status, default status should be shown which includes select project and flame', () => { + test('OnMultipleLaunchTargets: If there is no project status, default status should be shown with select project', () => { let event = new OmnisharpOnMultipleLaunchTargets([]); observer.post(event); expect(showCalled).to.be.true; expect(statusBar.text).to.be.equal('$(flame) Select project'); + expect(statusBar.command).to.equal('o.pickProjectAndStart'); + }); + + test('OnServerStart: If there is no project status, default status should be shown as Running', () => { + let event = new OmnisharpServerOnStart(); + observer.post(event); + expect(showCalled).to.be.true; + expect(statusBar.text).to.be.equal('$(flame) Running'); + expect(statusBar.command).to.equal('o.pickProjectAndStart'); }); }); \ No newline at end of file From a24a549f5da6e1cba0f44dc6056b8e7fb9aacae7 Mon Sep 17 00:00:00 2001 From: Unknown Date: Tue, 27 Mar 2018 14:57:44 -0700 Subject: [PATCH 27/56] Clean up tests --- src/observers/InformationMessageObserver.ts | 1 - .../logging/CsharpLoggerObserver.test.ts | 15 +- .../logging/OmnisharpLoggerObserver.test.ts | 148 +++++++++--------- .../logging/TelemetryObserver.test.ts | 92 +++++------ 4 files changed, 133 insertions(+), 123 deletions(-) diff --git a/src/observers/InformationMessageObserver.ts b/src/observers/InformationMessageObserver.ts index 317a86f61d..d12ae92217 100644 --- a/src/observers/InformationMessageObserver.ts +++ b/src/observers/InformationMessageObserver.ts @@ -24,7 +24,6 @@ export class InformationMessageObserver { let csharpConfig = this.vscode.workspace.getConfiguration('csharp'); if (!csharpConfig.get('suppressDotnetRestoreNotification')) { let info = `There are unresolved dependencies from '${this.vscode.workspace.asRelativePath(event.unresolvedDependencies.FileName)}'. Please execute the restore command to continue.`; - return this.vscode.window.showInformationMessage(info, 'Restore').then(value => { if (value) { dotnetRestoreForProject(event.server, event.unresolvedDependencies.FileName, event.eventStream); diff --git a/test/unitTests/logging/CsharpLoggerObserver.test.ts b/test/unitTests/logging/CsharpLoggerObserver.test.ts index f0611272a8..3efbc2d7ba 100644 --- a/test/unitTests/logging/CsharpLoggerObserver.test.ts +++ b/test/unitTests/logging/CsharpLoggerObserver.test.ts @@ -85,21 +85,21 @@ suite('CsharpLoggerObsever', () => { expect(logOutput).to.contain("MyArchitecture"); }); - test('Event.InstallationFailure: Stage and Error is logged if not a PackageError', () => { + test('InstallationFailure: Stage and Error is logged if not a PackageError', () => { let event = new Event.InstallationFailure("someStage", new Error("someError")); observer.post(event); expect(logOutput).to.contain(event.stage); expect(logOutput).to.contain(event.error.toString()); }); - test('Event.InstallationFailure: Stage and Error is logged if a PackageError without inner error', () => { + test('InstallationFailure: Stage and Error is logged if a PackageError without inner error', () => { let event = new Event.InstallationFailure("someStage", new PackageError("someError", null, null)); observer.post(event); expect(logOutput).to.contain(event.stage); expect(logOutput).to.contain(event.error.message); }); - test('Event.InstallationFailure: Stage and Inner error is logged if a PackageError without inner error', () => { + test('InstallationFailure: Stage and Inner error is logged if a PackageError without inner error', () => { let event = new Event.InstallationFailure("someStage", new PackageError("someError", null, "innerError")); observer.post(event); expect(logOutput).to.contain(event.stage); @@ -122,25 +122,25 @@ suite('CsharpLoggerObsever', () => { expect(logOutput).to.contain(element.expected); })); - test(`Event.ActivaltionFailure: Some message is logged`, () => { + test(`ActivaltionFailure: Some message is logged`, () => { let event = new Event.ActivationFailure(); observer.post(event); expect(logOutput).to.not.be.empty; }); - test(`Event.ProjectJsonDeprecatedWarning: Some message is logged`, () => { + test(`ProjectJsonDeprecatedWarning: Some message is logged`, () => { let event = new Event.ProjectJsonDeprecatedWarning(); observer.post(event); expect(logOutput).to.not.be.empty; }); - test(`Event.ProjectJsonDeprecatedWarning: Some message is logged`, () => { + test(`ProjectJsonDeprecatedWarning: Some message is logged`, () => { let event = new Event.InstallationSuccess(); observer.post(event); expect(logOutput).to.not.be.empty; }); - test(`Event.InstallationProgress: Progress message is logged`, () => { + test(`InstallationProgress: Progress message is logged`, () => { let event = new Event.InstallationProgress("someStage", "someMessage"); observer.post(event); expect(logOutput).to.contain(event.message); @@ -151,5 +151,4 @@ suite('CsharpLoggerObsever', () => { observer.post(event); expect(logOutput).to.contain(event.packageInfo); }); - }); diff --git a/test/unitTests/logging/OmnisharpLoggerObserver.test.ts b/test/unitTests/logging/OmnisharpLoggerObserver.test.ts index fa295254ae..4562ef432b 100644 --- a/test/unitTests/logging/OmnisharpLoggerObserver.test.ts +++ b/test/unitTests/logging/OmnisharpLoggerObserver.test.ts @@ -21,42 +21,44 @@ suite("OmnisharpLoggerObserver", () => { logOutput = ""; }); - test(`OmnisharpServerMsBuildProjectDiagnostics: Logged message contains the Filename if there is atleast one error or warning`, () => { - let event = getOmnisharpMSBuildProjectDiagnosticsEvent("someFile", - [getMSBuildDiagnosticsMessage("warningFile", "", "", 0, 0, 0, 0)], - []); - observer.post(event); - expect(logOutput).to.contain(event.diagnostics.FileName); - }); - - test("OmnisharpServerMsBuildProjectDiagnostics: Logged message is empty if there are no warnings and erros", () => { - let event = getOmnisharpMSBuildProjectDiagnosticsEvent("someFile", [], []); - observer.post(event); - expect(logOutput).to.be.empty; - }); - - [ - getOmnisharpMSBuildProjectDiagnosticsEvent("someFile", - [getMSBuildDiagnosticsMessage("warningFile", "", "someWarningText", 1, 2, 3, 4)], - [getMSBuildDiagnosticsMessage("errorFile", "", "someErrorText", 5, 6, 7, 8)]) - ].forEach((event: OmnisharpServerMsBuildProjectDiagnostics) => { - test(`${event.constructor.name}: Logged message contains the Filename, StartColumn, StartLine and Text for the diagnostic warnings`, () => { + suite('OmnisharpServerMsBuildProjectDiagnostics', () => { + test(`Logged message contains the Filename if there is atleast one error or warning`, () => { + let event = getOmnisharpMSBuildProjectDiagnosticsEvent("someFile", + [getMSBuildDiagnosticsMessage("warningFile", "", "", 0, 0, 0, 0)], + []); observer.post(event); - event.diagnostics.Warnings.forEach(element => { - expect(logOutput).to.contain(element.FileName); - expect(logOutput).to.contain(element.StartLine); - expect(logOutput).to.contain(element.StartColumn); - expect(logOutput).to.contain(element.Text); - }); + expect(logOutput).to.contain(event.diagnostics.FileName); }); - test(`${event.constructor.name}: Logged message contains the Filename, StartColumn, StartLine and Text for the diagnostics errors`, () => { + test("Logged message is empty if there are no warnings and erros", () => { + let event = getOmnisharpMSBuildProjectDiagnosticsEvent("someFile", [], []); observer.post(event); - event.diagnostics.Errors.forEach(element => { - expect(logOutput).to.contain(element.FileName); - expect(logOutput).to.contain(element.StartLine); - expect(logOutput).to.contain(element.StartColumn); - expect(logOutput).to.contain(element.Text); + expect(logOutput).to.be.empty; + }); + + [ + getOmnisharpMSBuildProjectDiagnosticsEvent("someFile", + [getMSBuildDiagnosticsMessage("warningFile", "", "someWarningText", 1, 2, 3, 4)], + [getMSBuildDiagnosticsMessage("errorFile", "", "someErrorText", 5, 6, 7, 8)]) + ].forEach((event: OmnisharpServerMsBuildProjectDiagnostics) => { + test(`Logged message contains the Filename, StartColumn, StartLine and Text for the diagnostic warnings`, () => { + observer.post(event); + event.diagnostics.Warnings.forEach(element => { + expect(logOutput).to.contain(element.FileName); + expect(logOutput).to.contain(element.StartLine); + expect(logOutput).to.contain(element.StartColumn); + expect(logOutput).to.contain(element.Text); + }); + }); + + test(`${event.constructor.name}: Logged message contains the Filename, StartColumn, StartLine and Text for the diagnostics errors`, () => { + observer.post(event); + event.diagnostics.Errors.forEach(element => { + expect(logOutput).to.contain(element.FileName); + expect(logOutput).to.contain(element.StartLine); + expect(logOutput).to.contain(element.StartColumn); + expect(logOutput).to.contain(element.Text); + }); }); }); }); @@ -107,60 +109,66 @@ suite("OmnisharpLoggerObserver", () => { }); }); - [ - new OmnisharpServerOnError({ Text: "someText", FileName: "someFile", Line: 1, Column: 2 }), - ].forEach((event: OmnisharpServerOnError) => { - test(`${event.constructor.name}: Contains the error message text`, () => { - observer.post(event); - expect(logOutput).to.contain(event.errorMessage.Text); - }); + suite('OmnisharpServerOnError', () => { + [ + new OmnisharpServerOnError({ Text: "someText", FileName: "someFile", Line: 1, Column: 2 }), + ].forEach((event: OmnisharpServerOnError) => { + test('Contains the error message text', () => { + observer.post(event); + expect(logOutput).to.contain(event.errorMessage.Text); + }); - test(`${event.constructor.name}: Contains the error message FileName, Line and column if FileName is not null`, () => { - observer.post(event); - if (event.errorMessage.FileName) { + test(`Contains the error message FileName, Line and column if FileName is not null`, () => { + observer.post(event); expect(logOutput).to.contain(event.errorMessage.FileName); expect(logOutput).to.contain(event.errorMessage.Line); expect(logOutput).to.contain(event.errorMessage.Column); - } + }); }); - }); - test(`OmnisharpServerOnError: Doesnot throw error if FileName is null`, () => { - let event = new OmnisharpServerOnError({ Text: "someText", FileName: null, Line: 1, Column: 2 }); - let fn = function () { observer.post(event); }; - expect(fn).to.not.throw(Error); + test(`Doesnot throw error if FileName is null`, () => { + let event = new OmnisharpServerOnError({ Text: "someText", FileName: null, Line: 1, Column: 2 }); + let fn = function () { observer.post(event); }; + expect(fn).to.not.throw(Error); + }); }); + + test('OmnisharpFailure: Failure message is logged', () => { let event = new OmnisharpFailure("failureMessage", new Error("errorMessage")); observer.post(event); expect(logOutput).to.contain(event.message); }); - [ - new OmnisharpEventPacketReceived("TRACE", "foo", "someMessage"), - new OmnisharpEventPacketReceived("DEBUG", "foo", "someMessage"), - new OmnisharpEventPacketReceived("INFORMATION", "foo", "someMessage"), - new OmnisharpEventPacketReceived("WARNING", "foo", "someMessage"), - new OmnisharpEventPacketReceived("ERROR", "foo", "someMessage"), - new OmnisharpEventPacketReceived("CRITICAL", "foo", "someMessage"), - ].forEach((event: OmnisharpEventPacketReceived) => { - test(`OmnisharpEventPacketReceived: ${event.logLevel} messages are logged with name and the message`, () => { + suite('OmnisharpEventPacketReceived', () => { + [ + new OmnisharpEventPacketReceived("TRACE", "foo", "someMessage"), + new OmnisharpEventPacketReceived("DEBUG", "foo", "someMessage"), + new OmnisharpEventPacketReceived("INFORMATION", "foo", "someMessage"), + new OmnisharpEventPacketReceived("WARNING", "foo", "someMessage"), + new OmnisharpEventPacketReceived("ERROR", "foo", "someMessage"), + new OmnisharpEventPacketReceived("CRITICAL", "foo", "someMessage"), + ].forEach((event: OmnisharpEventPacketReceived) => { + test(`${event.logLevel} messages are logged with name and the message`, () => { + observer.post(event); + expect(logOutput).to.contain(event.name); + expect(logOutput).to.contain(event.message); + }); + }); + + test('Throws error on unknown log level', () => { + let event = new OmnisharpEventPacketReceived("random log level", "foo", "someMessage"); + let fn = function () { observer.post(event); }; + expect(fn).to.throw(Error); + }); + + test(`Information messages with name OmniSharp.Middleware.LoggingMiddleware and follow pattern /^\/[\/\w]+: 200 \d+ms/ are not logged`, () => { + let event = new OmnisharpEventPacketReceived("INFORMATION", "OmniSharp.Middleware.LoggingMiddleware", "/codecheck: 200 339ms"); observer.post(event); - expect(logOutput).to.contain(event.name); - expect(logOutput).to.contain(event.message); + expect(logOutput).to.be.empty; }); }); - test('OmnisharpEventPacketReceived: Throws error on unknown log level', () => { - let event = new OmnisharpEventPacketReceived("random log level", "foo", "someMessage"); - let fn = function () { observer.post(event); }; - expect(fn).to.throw(Error); - }); - - test(`OmnisharpEventPacketReceived: Information messages with name OmniSharp.Middleware.LoggingMiddleware and follow pattern /^\/[\/\w]+: 200 \d+ms/ are not logged`, () => { - let event = new OmnisharpEventPacketReceived("INFORMATION", "OmniSharp.Middleware.LoggingMiddleware", "/codecheck: 200 339ms"); - observer.post(event); - expect(logOutput).to.be.empty; - }); + }); diff --git a/test/unitTests/logging/TelemetryObserver.test.ts b/test/unitTests/logging/TelemetryObserver.test.ts index 868b2eb8c7..97513269e0 100644 --- a/test/unitTests/logging/TelemetryObserver.test.ts +++ b/test/unitTests/logging/TelemetryObserver.test.ts @@ -41,21 +41,23 @@ suite('TelemetryReporterObserver', () => { expect(name).to.be.not.empty; }); - test("InstallationFailure: Telemetry Props contains platform information, install stage and an event name", () => { - let event = new InstallationFailure("someStage", "someError"); - observer.post(event); - expect(property).to.have.property("platform.architecture", platformInfo.architecture); - expect(property).to.have.property("platform.platform", platformInfo.platform); - expect(property).to.have.property("installStage"); - expect(name).to.not.be.empty; - }); - - test(`InstallationFailure: Telemetry Props contains message and packageUrl if error is package error`, () => { - let error = new PackageError("someError", { "description": "foo", "url": "someurl" }); - let event = new InstallationFailure("someStage", error); - observer.post(event); - expect(property).to.have.property("error.message", error.message); - expect(property).to.have.property("error.packageUrl", error.pkg.url); + suite('InstallationFailure', () => { + test("Telemetry Props contains platform information, install stage and an event name", () => { + let event = new InstallationFailure("someStage", "someError"); + observer.post(event); + expect(property).to.have.property("platform.architecture", platformInfo.architecture); + expect(property).to.have.property("platform.platform", platformInfo.platform); + expect(property).to.have.property("installStage"); + expect(name).to.not.be.empty; + }); + + test(`Telemetry Props contains message and packageUrl if error is package error`, () => { + let error = new PackageError("someError", { "description": "foo", "url": "someurl" }); + let event = new InstallationFailure("someStage", error); + observer.post(event); + expect(property).to.have.property("error.message", error.message); + expect(property).to.have.property("error.packageUrl", error.pkg.url); + }); }); test('InstallationSuccess: Telemetry props contain installation stage', () => { @@ -65,37 +67,39 @@ suite('TelemetryReporterObserver', () => { expect(property).to.have.property("installStage", "completeSuccess"); }); - test('TestExecutionCountReport: SendTelemetryEvent is called for "RunTest" and "DebugTest"', () => { - let event = new TestExecutionCountReport({ "framework1": 20 }, { "framework2": 30 }); - observer.post(event); - expect(name).to.contain("RunTest"); - expect(name).to.contain("DebugTest"); - expect(measure).to.be.containingAllOf([event.debugCounts, event.runCounts]); - }); - - test('TestExecutionCountReport: SendTelemetryEvent is not called for empty run count', () => { - let event = new TestExecutionCountReport({ "framework1": 20 }, null); - observer.post(event); - expect(name).to.not.contain("RunTest"); - expect(name).to.contain("DebugTest"); - expect(measure).to.be.containingAllOf([event.debugCounts]); - }); - - test('TestExecutionCountReport: SendTelemetryEvent is not called for empty debug count', () => { - let event = new TestExecutionCountReport(null, { "framework1": 20 }); - observer.post(event); - expect(name).to.contain("RunTest"); - expect(name).to.not.contain("DebugTest"); - expect(measure).to.be.containingAllOf([event.runCounts]); + suite('TestCountExecutionReport', () => { + test('SendTelemetryEvent is called for "RunTest" and "DebugTest"', () => { + let event = new TestExecutionCountReport({ "framework1": 20 }, { "framework2": 30 }); + observer.post(event); + expect(name).to.contain("RunTest"); + expect(name).to.contain("DebugTest"); + expect(measure).to.be.containingAllOf([event.debugCounts, event.runCounts]); + }); + + test('SendTelemetryEvent is not called for empty run count', () => { + let event = new TestExecutionCountReport({ "framework1": 20 }, null); + observer.post(event); + expect(name).to.not.contain("RunTest"); + expect(name).to.contain("DebugTest"); + expect(measure).to.be.containingAllOf([event.debugCounts]); + }); + + test('SendTelemetryEvent is not called for empty debug count', () => { + let event = new TestExecutionCountReport(null, { "framework1": 20 }); + observer.post(event); + expect(name).to.contain("RunTest"); + expect(name).to.not.contain("DebugTest"); + expect(measure).to.be.containingAllOf([event.runCounts]); + }); + + test('SendTelemetryEvent is not called for empty debug and run counts', () => { + let event = new TestExecutionCountReport(null, null); + observer.post(event); + expect(name).to.be.empty; + expect(measure).to.be.empty; + }); }); - test('TestExecutionCountReport: SendTelemetryEvent is not called for empty debug and run counts', () => { - let event = new TestExecutionCountReport(null, null); - observer.post(event); - expect(name).to.be.empty; - expect(measure).to.be.empty; - }); - [ new OmnisharpDelayTrackerEventMeasures("someEvent", { someKey: 1 }), new OmnisharpStart("startEvent", { someOtherKey: 2 }) From dbd8c8759cbe03a0741f9f6912e189e15a446ea1 Mon Sep 17 00:00:00 2001 From: Unknown Date: Tue, 27 Mar 2018 15:09:47 -0700 Subject: [PATCH 28/56] Fixed ttd --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9ac56fb895..1ee06137db 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "vscode:prepublish": "tsc -p ./", "compile": "tsc -p ./ && gulp tslint", "watch": "tsc -watch -p ./", - "tdd": "npm run test:unit -- --watch --watch-extensions ts", + "tdd": "nyc -r lcovonly --report-dir coverage/unit mocha --ui tdd --watch --watch-extensions ts -- test/unitTests/**/*.test.ts", "test": "npm-run-all test:feature test:unit test:integration", "test:unit": "nyc -r lcovonly --report-dir coverage/unit mocha --ui tdd -- test/unitTests/**/*.test.ts", "test:feature": "cross-env OSVC_SUITE=featureTests CODE_EXTENSIONS_PATH=./ CODE_TESTS_PATH=./out/test/featureTests npm run test:runInVsCode", From 97d85cdb088d07eb76cd9776d9b37f317f9805b7 Mon Sep 17 00:00:00 2001 From: Unknown Date: Tue, 27 Mar 2018 16:32:23 -0700 Subject: [PATCH 29/56] Use vscode commands instead of calling into commands.ts --- src/features/commands.ts | 9 ++++++++- src/observers/InformationMessageObserver.ts | 4 +--- src/omnisharp/loggingEvents.ts | 4 +--- src/omnisharp/server.ts | 2 +- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/features/commands.ts b/src/features/commands.ts index e976599ea1..faf4cad87c 100644 --- a/src/features/commands.ts +++ b/src/features/commands.ts @@ -24,7 +24,14 @@ export default function registerCommands(server: OmniSharpServer, eventStream: E let d1 = vscode.commands.registerCommand('o.restart', () => restartOmniSharp(server)); let d2 = vscode.commands.registerCommand('o.pickProjectAndStart', () => pickProjectAndStart(server)); let d3 = vscode.commands.registerCommand('o.showOutput', () => eventStream.post(new CommandShowOutput())); - let d4 = vscode.commands.registerCommand('dotnet.restore', () => dotnetRestoreAllProjects(server, eventStream)); + let d4 = vscode.commands.registerCommand('dotnet.restore', fileName => { + if (fileName) { + dotnetRestoreForProject(server, fileName, eventStream); + } + else { + dotnetRestoreAllProjects(server, eventStream); + } + }); // register empty handler for csharp.installDebugger // running the command activates the extension, which is all we need for installation to kickoff diff --git a/src/observers/InformationMessageObserver.ts b/src/observers/InformationMessageObserver.ts index d12ae92217..458ec65f3a 100644 --- a/src/observers/InformationMessageObserver.ts +++ b/src/observers/InformationMessageObserver.ts @@ -4,8 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import * as ObservableEvent from "../omnisharp/loggingEvents"; - -import { dotnetRestoreForProject } from '../features/commands'; import { vscode } from '../vscodeAdapter'; export class InformationMessageObserver { @@ -26,7 +24,7 @@ export class InformationMessageObserver { let info = `There are unresolved dependencies from '${this.vscode.workspace.asRelativePath(event.unresolvedDependencies.FileName)}'. Please execute the restore command to continue.`; return this.vscode.window.showInformationMessage(info, 'Restore').then(value => { if (value) { - dotnetRestoreForProject(event.server, event.unresolvedDependencies.FileName, event.eventStream); + this.vscode.commands.executeCommand('dotnet.restore', event.unresolvedDependencies.FileName); } }); } diff --git a/src/omnisharp/loggingEvents.ts b/src/omnisharp/loggingEvents.ts index 6e5c1d88e4..df1fc2c732 100644 --- a/src/omnisharp/loggingEvents.ts +++ b/src/omnisharp/loggingEvents.ts @@ -6,8 +6,6 @@ import { PlatformInformation } from "../platform"; import { Request } from "./requestQueue"; import * as protocol from './protocol'; -import { OmniSharpServer } from "./server"; -import { EventStream } from "../EventStream"; import { LaunchTarget } from "./launcher"; export interface BaseEvent { @@ -73,7 +71,7 @@ export class OmnisharpServerMsBuildProjectDiagnostics implements BaseEvent { } export class OmnisharpServerUnresolvedDependencies implements BaseEvent { - constructor(public unresolvedDependencies: protocol.UnresolvedDependenciesMessage, public server: OmniSharpServer, public eventStream: EventStream) { } + constructor(public unresolvedDependencies: protocol.UnresolvedDependenciesMessage) { } } export class OmnisharpServerEnqueueRequest implements BaseEvent { diff --git a/src/omnisharp/server.ts b/src/omnisharp/server.ts index f3b658df39..208abb2c9a 100644 --- a/src/omnisharp/server.ts +++ b/src/omnisharp/server.ts @@ -252,7 +252,7 @@ export class OmniSharpServer { )); disposables.add(this.onUnresolvedDependencies((message: protocol.UnresolvedDependenciesMessage) => - this.eventStream.post(new ObservableEvents.OmnisharpServerUnresolvedDependencies(message, this, this.eventStream)) + this.eventStream.post(new ObservableEvents.OmnisharpServerUnresolvedDependencies(message)) )); disposables.add(this.onStderr((message: string) => From 3dd568b97259fea1c53aacc2744f213e59f4154a Mon Sep 17 00:00:00 2001 From: Unknown Date: Tue, 27 Mar 2018 18:11:10 -0700 Subject: [PATCH 30/56] Added test for information message observer --- src/observers/InformationMessageObserver.ts | 15 ++- test/unitTests/logging/Fakes.ts | 32 ++++- .../InformationMessageObserver.test.ts | 112 +++++++++++++++++- .../logging/OmnisharpLoggerObserver.test.ts | 2 +- 4 files changed, 145 insertions(+), 16 deletions(-) diff --git a/src/observers/InformationMessageObserver.ts b/src/observers/InformationMessageObserver.ts index 458ec65f3a..6fbf4093c7 100644 --- a/src/observers/InformationMessageObserver.ts +++ b/src/observers/InformationMessageObserver.ts @@ -13,20 +13,19 @@ export class InformationMessageObserver { public post = (event: ObservableEvent.BaseEvent) => { switch (event.constructor.name) { case ObservableEvent.OmnisharpServerUnresolvedDependencies.name: - this.handleOmnisharpServerUnresolvedDependencies(event); - break; + this.handleOmnisharpServerUnresolvedDependencies(event); + break; } } - private handleOmnisharpServerUnresolvedDependencies(event: ObservableEvent.OmnisharpServerUnresolvedDependencies) { + private async handleOmnisharpServerUnresolvedDependencies(event: ObservableEvent.OmnisharpServerUnresolvedDependencies) { let csharpConfig = this.vscode.workspace.getConfiguration('csharp'); if (!csharpConfig.get('suppressDotnetRestoreNotification')) { let info = `There are unresolved dependencies from '${this.vscode.workspace.asRelativePath(event.unresolvedDependencies.FileName)}'. Please execute the restore command to continue.`; - return this.vscode.window.showInformationMessage(info, 'Restore').then(value => { - if (value) { - this.vscode.commands.executeCommand('dotnet.restore', event.unresolvedDependencies.FileName); - } - }); + let value = await this.vscode.window.showInformationMessage(info, 'Restore'); + if (value) { + this.vscode.commands.executeCommand('dotnet.restore', event.unresolvedDependencies.FileName); + } } } } diff --git a/test/unitTests/logging/Fakes.ts b/test/unitTests/logging/Fakes.ts index 53a049c707..02126fe342 100644 --- a/test/unitTests/logging/Fakes.ts +++ b/test/unitTests/logging/Fakes.ts @@ -4,10 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from '../../../src/vscodeAdapter'; +import * as protocol from '../../../src/omnisharp/protocol'; import { DocumentSelector, MessageItem, TextDocument, Uri } from '../../../src/vscodeAdapter'; import { ITelemetryReporter } from '../../../src/observers/TelemetryObserver'; import { MSBuildDiagnosticsMessage } from '../../../src/omnisharp/protocol'; -import { OmnisharpServerMsBuildProjectDiagnostics, OmnisharpServerOnError } from '../../../src/omnisharp/loggingEvents'; +import { OmnisharpServerMsBuildProjectDiagnostics, OmnisharpServerOnError, OmnisharpServerUnresolvedDependencies } from '../../../src/omnisharp/loggingEvents'; export const getNullChannel = (): vscode.OutputChannel => { let returnChannel: vscode.OutputChannel = { @@ -30,6 +31,22 @@ export const getNullTelemetryReporter = (): ITelemetryReporter => { return reporter; }; +export const getNullWorkspaceConfiguration = (): vscode.WorkspaceConfiguration => { + let workspace: vscode.WorkspaceConfiguration = { + get: (section: string) => { + return true; + }, + has: (section: string) => { return true; }, + inspect: () => { + return { + key: "somekey" + }; + }, + update: () => { return Promise.resolve(); }, + }; + return workspace; +}; + export function getOmnisharpMSBuildProjectDiagnosticsEvent(fileName: string, warnings: MSBuildDiagnosticsMessage[], errors: MSBuildDiagnosticsMessage[]): OmnisharpServerMsBuildProjectDiagnostics { return new OmnisharpServerMsBuildProjectDiagnostics({ FileName: fileName, @@ -63,9 +80,16 @@ export function getOmnisharpServerOnErrorEvent(text: string, fileName: string, l Line: line, Column: column }); -} +} + +export function getUnresolvedDependenices(fileName: string): OmnisharpServerUnresolvedDependencies { + return new OmnisharpServerUnresolvedDependencies({ + UnresolvedDependencies: [], + FileName: fileName + }); +} -export function getFakeVsCode() : vscode.vscode { +export function getFakeVsCode(): vscode.vscode { return { commands: { executeCommand: (command: string, ...rest: any[]) => { @@ -86,7 +110,7 @@ export function getFakeVsCode() : vscode.vscode { throw new Error("Not Implemented"); } }, - workspace: { + workspace: { getConfiguration: (section?: string, resource?: Uri) => { throw new Error("Not Implemented"); }, diff --git a/test/unitTests/logging/InformationMessageObserver.test.ts b/test/unitTests/logging/InformationMessageObserver.test.ts index 43ef875a23..59e816cae7 100644 --- a/test/unitTests/logging/InformationMessageObserver.test.ts +++ b/test/unitTests/logging/InformationMessageObserver.test.ts @@ -6,13 +6,119 @@ import * as rx from 'rx'; import { InformationMessageObserver } from '../../../src/observers/InformationMessageObserver'; import { expect, should } from 'chai'; -import { vscode } from '../../../src/vscodeAdapter'; -import { getFakeVsCode } from './Fakes'; +import { vscode, Uri } from '../../../src/vscodeAdapter'; +import { getFakeVsCode, getNullWorkspaceConfiguration, getUnresolvedDependenices } from './Fakes'; suite("InformationMessageObserver", () => { suiteSetup(() => should()); + let doClickOk: () => void; + let doClickCancel: () => void; + let signalCommandDone: () => void; + let commandDone = new Promise(resolve => { + signalCommandDone = () => { resolve(); }; + }); let vscode: vscode = getFakeVsCode(); - let observer = new InformationMessageObserver(vscode); + let infoMessage; + let relativePath; + let invokedCommand; + let observer: InformationMessageObserver = new InformationMessageObserver(vscode); + vscode.window.showInformationMessage = (message: string, ...items: string[]) => { + infoMessage = message; + return new Promise(resolve => { + doClickCancel = () => { + resolve(undefined); + }; + + doClickOk = () => { + resolve(message); + }; + }); + }; + + vscode.commands.executeCommand = (command: string, ...rest: any[]) => { + invokedCommand = command; + signalCommandDone(); + return undefined; + }; + + vscode.workspace.asRelativePath = (pathOrUri?: string | Uri, includeWorspaceFolder? : boolean) => { + relativePath = pathOrUri; + return relativePath; + }; + + + setup(() => { + infoMessage = undefined; + relativePath = undefined; + invokedCommand = undefined; + commandDone = new Promise(resolve => { + signalCommandDone = () => { resolve(); }; + }); + }); + + test('If suppress dotnet configuration is set to true, the information message is not shown', () => { + let event = getUnresolvedDependenices("someFile"); + vscode.workspace.getConfiguration = (section?: string, resource?: Uri) => { + return { + ...getNullWorkspaceConfiguration(), + get: (section: string) => { + return true;// suppress the restore information + }}; + }; + observer.post(event); + expect(infoMessage).to.be.undefined; + }); + + test('If suppress dotnet configuration is set to false, the information message is shown', async () => { + let event = getUnresolvedDependenices("someFile"); + vscode.workspace.getConfiguration = (section?: string, resource?: Uri) => { + return { + ...getNullWorkspaceConfiguration(), + get: (section: string) => { + return false; // do not suppress the restore info + }}; + }; + + observer.post(event); + expect(relativePath).to.not.be.empty; + expect(infoMessage).to.not.be.empty; + doClickOk(); + await commandDone; + expect(invokedCommand).to.be.equal('dotnet.restore'); + }); + + test('Given an information message if the user clicks Restore, the command is executed', async () => { + let event = getUnresolvedDependenices("someFile"); + vscode.workspace.getConfiguration = (section?: string, resource?: Uri) => { + return { + ...getNullWorkspaceConfiguration(), + get: (section: string) => { + return false; // do not suppress the restore info + }}; + }; + + observer.post(event); + doClickOk(); + await commandDone; + expect(invokedCommand).to.be.equal('dotnet.restore'); + }); + + + test('Given an information message if the user clicks cancel, the command is not executed', async () => { + let event = getUnresolvedDependenices("someFile"); + vscode.workspace.getConfiguration = (section?: string, resource?: Uri) => { + return { + ...getNullWorkspaceConfiguration(), + get: (section: string) => { + return false; // do not suppress the restore info + }}; + }; + + observer.post(event); + doClickCancel(); + await expect(rx.Observable.fromPromise(commandDone).timeout(1).toPromise()).to.be.rejected; + expect(invokedCommand).to.be.undefined; + }); }); \ No newline at end of file diff --git a/test/unitTests/logging/OmnisharpLoggerObserver.test.ts b/test/unitTests/logging/OmnisharpLoggerObserver.test.ts index 4562ef432b..3602717b44 100644 --- a/test/unitTests/logging/OmnisharpLoggerObserver.test.ts +++ b/test/unitTests/logging/OmnisharpLoggerObserver.test.ts @@ -51,7 +51,7 @@ suite("OmnisharpLoggerObserver", () => { }); }); - test(`${event.constructor.name}: Logged message contains the Filename, StartColumn, StartLine and Text for the diagnostics errors`, () => { + test(`Logged message contains the Filename, StartColumn, StartLine and Text for the diagnostics errors`, () => { observer.post(event); event.diagnostics.Errors.forEach(element => { expect(logOutput).to.contain(element.FileName); From d78e7fef87705c7ebdb60b4f6fe6baf651603a3a Mon Sep 17 00:00:00 2001 From: Unknown Date: Wed, 28 Mar 2018 12:25:35 -0700 Subject: [PATCH 31/56] Tests for status bar obsever --- src/vscodeAdapter.ts | 2 +- test/unitTests/logging/Fakes.ts | 21 ++++++-- .../InformationMessageObserver.test.ts | 5 +- .../OmnisharpStatusBarObserver.test.ts | 54 ++++++++++++------- 4 files changed, 59 insertions(+), 23 deletions(-) diff --git a/src/vscodeAdapter.ts b/src/vscodeAdapter.ts index a7283c3141..bc2581d83e 100644 --- a/src/vscodeAdapter.ts +++ b/src/vscodeAdapter.ts @@ -297,7 +297,7 @@ export interface TextEditor { /** * The document associated with this text editor. The document will be the same for the entire lifetime of this text editor. */ - document: any; + document: TextDocument; } /** * A universal resource identifier representing either a file on disk diff --git a/test/unitTests/logging/Fakes.ts b/test/unitTests/logging/Fakes.ts index 02126fe342..cba10ed42a 100644 --- a/test/unitTests/logging/Fakes.ts +++ b/test/unitTests/logging/Fakes.ts @@ -8,7 +8,7 @@ import * as protocol from '../../../src/omnisharp/protocol'; import { DocumentSelector, MessageItem, TextDocument, Uri } from '../../../src/vscodeAdapter'; import { ITelemetryReporter } from '../../../src/observers/TelemetryObserver'; import { MSBuildDiagnosticsMessage } from '../../../src/omnisharp/protocol'; -import { OmnisharpServerMsBuildProjectDiagnostics, OmnisharpServerOnError, OmnisharpServerUnresolvedDependencies } from '../../../src/omnisharp/loggingEvents'; +import { OmnisharpServerMsBuildProjectDiagnostics, OmnisharpServerOnError, OmnisharpServerUnresolvedDependencies, WorkspaceInformationUpdated } from '../../../src/omnisharp/loggingEvents'; export const getNullChannel = (): vscode.OutputChannel => { let returnChannel: vscode.OutputChannel = { @@ -33,7 +33,7 @@ export const getNullTelemetryReporter = (): ITelemetryReporter => { export const getNullWorkspaceConfiguration = (): vscode.WorkspaceConfiguration => { let workspace: vscode.WorkspaceConfiguration = { - get: (section: string) => { + get: (section: string) => { return true; }, has: (section: string) => { return true; }, @@ -119,4 +119,19 @@ export function getFakeVsCode(): vscode.vscode { } } }; -} \ No newline at end of file +} + +export function getMSBuildWorkspaceInformation(msBuildSolutionPath: string, msBuildProjects: protocol.MSBuildProject[]): protocol.MsBuildWorkspaceInformation { + return { + SolutionPath: msBuildSolutionPath, + Projects: msBuildProjects + }; +} + +export function getWorkspaceInformationUpdated(msbuild: protocol.MsBuildWorkspaceInformation): WorkspaceInformationUpdated { + let a: protocol.WorkspaceInformationResponse = { + MsBuild: msbuild + }; + + return new WorkspaceInformationUpdated(a); +} \ No newline at end of file diff --git a/test/unitTests/logging/InformationMessageObserver.test.ts b/test/unitTests/logging/InformationMessageObserver.test.ts index 59e816cae7..3315e05e32 100644 --- a/test/unitTests/logging/InformationMessageObserver.test.ts +++ b/test/unitTests/logging/InformationMessageObserver.test.ts @@ -5,10 +5,13 @@ import * as rx from 'rx'; import { InformationMessageObserver } from '../../../src/observers/InformationMessageObserver'; -import { expect, should } from 'chai'; +import { use as chaiUse, expect, should } from 'chai'; import { vscode, Uri } from '../../../src/vscodeAdapter'; import { getFakeVsCode, getNullWorkspaceConfiguration, getUnresolvedDependenices } from './Fakes'; +chaiUse(require('chai-as-promised')); +chaiUse(require('chai-string')); + suite("InformationMessageObserver", () => { suiteSetup(() => should()); diff --git a/test/unitTests/logging/OmnisharpStatusBarObserver.test.ts b/test/unitTests/logging/OmnisharpStatusBarObserver.test.ts index b8bcdeb432..c078c9bc50 100644 --- a/test/unitTests/logging/OmnisharpStatusBarObserver.test.ts +++ b/test/unitTests/logging/OmnisharpStatusBarObserver.test.ts @@ -7,7 +7,7 @@ import { DocumentSelector, StatusBarItem, vscode } from '../../../src/vscodeAdap import { OmnisharpOnBeforeServerInstall, OmnisharpOnBeforeServerStart, OmnisharpOnMultipleLaunchTargets, OmnisharpServerOnServerError, OmnisharpServerOnStart } from '../../../src/omnisharp/loggingEvents'; import { expect, should } from 'chai'; import { OmnisharpStatusBarObserver } from '../../../src/observers/OmnisharpStatusBarObserver'; -import { getFakeVsCode } from './Fakes'; +import { getFakeVsCode, getWorkspaceInformationUpdated, getMSBuildWorkspaceInformation } from './Fakes'; suite('OmnisharpServerStatusObserver', () => { suiteSetup(() => should()); @@ -19,52 +19,70 @@ suite('OmnisharpServerStatusObserver', () => { }); let vscode: vscode = getFakeVsCode(); - vscode.window.activeTextEditor = { document: "hello" }; + vscode.window.activeTextEditor = { document: undefined }; vscode.languages.match = (selector: DocumentSelector, document: any) => { return 2; }; - let statusBar = { + let statusBarItem = { show: () => { showCalled = true; } }; - let observer = new OmnisharpStatusBarObserver(vscode, statusBar); + let observer = new OmnisharpStatusBarObserver(vscode, statusBarItem); - test('OnServerError: If there is no project status, default status should be shown with the error text', () => { + test('OnServerError: If there is no project status yet, status bar is shown with the error text', () => { let event = new OmnisharpServerOnServerError("someError"); observer.post(event); expect(showCalled).to.be.true; - expect(statusBar.text).to.equal(`$(flame) Error starting OmniSharp`); - expect(statusBar.command).to.equal('o.showOutput'); + expect(statusBarItem.text).to.equal(`$(flame) Error starting OmniSharp`); + expect(statusBarItem.command).to.equal('o.showOutput'); }); - test('OnBeforeServerInstall: If there is no project status, default status should be shown with the install text', () => { + test('OnBeforeServerInstall: If there is no project status yet, status bar is shown with the installation text', () => { let event = new OmnisharpOnBeforeServerInstall(); observer.post(event); expect(showCalled).to.be.true; - expect(statusBar.text).to.be.equal('$(flame) Installing OmniSharp...'); - expect(statusBar.command).to.equal('o.showOutput'); + expect(statusBarItem.text).to.be.equal('$(flame) Installing OmniSharp...'); + expect(statusBarItem.command).to.equal('o.showOutput'); }); - test('OnBeforeServerStart: If there is no project status, default status should be shown as Starting..', () => { + test('OnBeforeServerStart: If there is no project status yet, status bar is shown with the starting text', () => { let event = new OmnisharpOnBeforeServerStart(); observer.post(event); expect(showCalled).to.be.true; - expect(statusBar.text).to.be.equal('$(flame) Starting...'); - expect(statusBar.command).to.equal('o.showOutput'); + expect(statusBarItem.text).to.be.equal('$(flame) Starting...'); + expect(statusBarItem.command).to.equal('o.showOutput'); }); - test('OnMultipleLaunchTargets: If there is no project status, default status should be shown with select project', () => { + test('OnMultipleLaunchTargets: If there is no project status yet, status bar is shown with the select project option and the comand to pick a project', () => { let event = new OmnisharpOnMultipleLaunchTargets([]); observer.post(event); expect(showCalled).to.be.true; - expect(statusBar.text).to.be.equal('$(flame) Select project'); - expect(statusBar.command).to.equal('o.pickProjectAndStart'); + expect(statusBarItem.text).to.be.equal('$(flame) Select project'); + expect(statusBarItem.command).to.equal('o.pickProjectAndStart'); }); test('OnServerStart: If there is no project status, default status should be shown as Running', () => { let event = new OmnisharpServerOnStart(); observer.post(event); expect(showCalled).to.be.true; - expect(statusBar.text).to.be.equal('$(flame) Running'); - expect(statusBar.command).to.equal('o.pickProjectAndStart'); + expect(statusBarItem.text).to.be.equal('$(flame) Running'); + expect(statusBarItem.command).to.equal('o.pickProjectAndStart'); + }); + + suite('WorkspaceInformationUpdated', () => { + /*test('Project status is shown', () => { + let event = getWorkspaceInformationUpdated(null); + observer.post(event); + expect(showCalled).to.be.true; + expect(statusBarItem.text).to.be.equal('$(flame) '); + expect(statusBarItem.command).to.equal('o.pickProjectAndStart'); + }); + + test('Project status is shown', () => { + let event = getWorkspaceInformationUpdated(getMSBuildWorkspaceInformation("somePath", [])); + observer.post(event); + expect(showCalled).to.be.true; + expect(statusBarItem.text).to.be.equal('$(flame) '+`${event.info.MsBuild.SolutionPath}`); + expect(statusBarItem.command).to.equal('o.pickProjectAndStart'); + });*/ }); }); \ No newline at end of file From 53ce637fdd1382492fda7e1e020debb0ce1d867d Mon Sep 17 00:00:00 2001 From: Akshita Agarwal Date: Fri, 30 Mar 2018 13:04:21 -0700 Subject: [PATCH 32/56] project status observer --- src/observers/OmnisharpStatusBarObserver.ts | 41 +++---- src/observers/ProjectStatusObserver.ts | 107 ++++++++++++++++++ .../logging/ProjectStatusBarObserver.test.ts | 18 +-- 3 files changed, 131 insertions(+), 35 deletions(-) create mode 100644 src/observers/ProjectStatusObserver.ts rename src/observers/status.ts => test/unitTests/logging/ProjectStatusBarObserver.test.ts (57%) diff --git a/src/observers/OmnisharpStatusBarObserver.ts b/src/observers/OmnisharpStatusBarObserver.ts index 124e8a1b76..1a71175457 100644 --- a/src/observers/OmnisharpStatusBarObserver.ts +++ b/src/observers/OmnisharpStatusBarObserver.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import { DocumentFilter, DocumentSelector, StatusBarItem, vscode } from '../vscodeAdapter'; -import { Status } from './status'; import { basename } from 'path'; import { OmnisharpServerOnServerError, BaseEvent, OmnisharpOnMultipleLaunchTargets, OmnisharpOnBeforeServerInstall, OmnisharpOnBeforeServerStart, ActiveTextEditorChanged, OmnisharpServerOnStop, OmnisharpServerOnStart, WorkspaceInformationUpdated } from "../omnisharp/loggingEvents"; @@ -18,41 +17,35 @@ let defaultSelector: DocumentSelector = [ ]; export class OmnisharpStatusBarObserver { - private defaultStatus: Status; - private projectStatus: Status; constructor(private vscode: vscode, private statusBarItem: StatusBarItem) { - this.defaultStatus = new Status(defaultSelector); } - +//This should not care about the public post = (event: BaseEvent) => { switch (event.constructor.name) { case OmnisharpServerOnServerError.name: - SetStatus(this.defaultStatus, '$(flame) Error starting OmniSharp', 'o.showOutput', ''); - this.render(); + this.SetAndRenderStatusBar('$(flame) Error starting OmniSharp', 'o.showOutput', ''); break; case OmnisharpOnMultipleLaunchTargets.name: - SetStatus(this.defaultStatus, '$(flame) Select project', 'o.pickProjectAndStart', 'rgb(90, 218, 90)'); - this.render(); + this.SetAndRenderStatusBar('$(flame) Select project', 'o.pickProjectAndStart', 'rgb(90, 218, 90)'); break; case OmnisharpOnBeforeServerInstall.name: - SetStatus(this.defaultStatus, '$(flame) Installing OmniSharp...', 'o.showOutput', ''); - this.render(); + this.SetAndRenderStatusBar('$(flame) Installing OmniSharp...', 'o.showOutput', ''); break; case OmnisharpOnBeforeServerStart.name: - SetStatus(this.defaultStatus, '$(flame) Starting...', 'o.showOutput', ''); - this.render(); + this.SetAndRenderStatusBar('$(flame) Starting...', 'o.showOutput', ''); break; case ActiveTextEditorChanged.name: - this.render(); break; case OmnisharpServerOnStop.name: - this.projectStatus = undefined; - this.defaultStatus.text = undefined; + this.statusBarItem.text = undefined; + this.statusBarItem.command = command; + this.statusBarItem.color = color; + this.statusBarItem.show(); + break; case OmnisharpServerOnStart.name: - SetStatus(this.defaultStatus, '$(flame) Running', 'o.pickProjectAndStart', ''); - this.render(); + this.SetAndRenderStatusBar('$(flame) Running', 'o.pickProjectAndStart', ''); break; case WorkspaceInformationUpdated.name: this.handleWorkspaceInformationUpdated(event); @@ -122,10 +115,12 @@ export class OmnisharpStatusBarObserver { this.statusBarItem.hide(); } + + private SetAndRenderStatusBar(text: string, command: string, color?: string) { + this.statusBarItem.text = text; + this.statusBarItem.command = command; + this.statusBarItem.color = color; + this.statusBarItem.show(); + } } -function SetStatus(status: Status, text: string, command: string, color?: string) { - status.text = text; - status.command = command; - status.color = color; -} \ No newline at end of file diff --git a/src/observers/ProjectStatusObserver.ts b/src/observers/ProjectStatusObserver.ts new file mode 100644 index 0000000000..215255b37f --- /dev/null +++ b/src/observers/ProjectStatusObserver.ts @@ -0,0 +1,107 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { DocumentFilter, DocumentSelector, StatusBarItem, vscode } from '../vscodeAdapter'; +import { basename } from 'path'; +import { OmnisharpServerOnServerError, BaseEvent, OmnisharpOnMultipleLaunchTargets, OmnisharpOnBeforeServerInstall, OmnisharpOnBeforeServerStart, ActiveTextEditorChanged, OmnisharpServerOnStop, OmnisharpServerOnStart, WorkspaceInformationUpdated } from "../omnisharp/loggingEvents"; +export class OmnisharpStatusBarObserver { + constructor(private vscode: vscode, private statusBarItem: StatusBarItem) { + } +/* Notes: Since we have removed the listeners and the disposables from the server start and stop event, +we will not show up the status bar item :) +*/ + public post = (event: BaseEvent) => { + switch (event.constructor.name) { + case OmnisharpOnMultipleLaunchTargets.name: + SetStatus(this.defaultStatus, '$(flame) Select project', 'o.pickProjectAndStart', 'rgb(90, 218, 90)'); + this.render(); + break; + case ActiveTextEditorChanged.name: + this.render(); + break; + case OmnisharpServerOnStop.name: + this.projectStatus = undefined; + this.defaultStatus.text = undefined; + break; + case OmnisharpServerOnStart.name: + SetStatus(this.defaultStatus, '$(flame) Running', 'o.pickProjectAndStart', ''); + this.render(); + break; + case WorkspaceInformationUpdated.name: + this.handleWorkspaceInformationUpdated(event); + } + } + + private handleWorkspaceInformationUpdated(event: WorkspaceInformationUpdated) { + interface Project { + Path: string; + SourceFiles: string[]; + } + + let fileNames: DocumentFilter[] = []; + let label: string; + + function addProjectFileNames(project: Project) { + fileNames.push({ pattern: project.Path }); + + if (project.SourceFiles) { + for (let sourceFile of project.SourceFiles) { + fileNames.push({ pattern: sourceFile }); + } + } + } + + let info = event.info; + // show sln-file if applicable + if (info.MsBuild && info.MsBuild.SolutionPath) { + label = basename(info.MsBuild.SolutionPath); //workspace.getRelativePath(info.MsBuild.SolutionPath); + fileNames.push({ pattern: info.MsBuild.SolutionPath }); + + for (let project of info.MsBuild.Projects) { + addProjectFileNames(project); + } + } + + // set project info + this.projectStatus = new Status(fileNames); + SetStatus(this.projectStatus, '$(flame) ' + label, 'o.pickProjectAndStart'); + SetStatus(this.defaultStatus, '$(flame) Switch projects', 'o.pickProjectAndStart'); + this.render(); + } + + private render = () => { + let activeTextEditor = this.vscode.window.activeTextEditor; + if (!activeTextEditor) { + this.statusBarItem.hide(); + return; + } + + let document = activeTextEditor.document; + let status: Status; + + if (this.projectStatus && this.vscode.languages.match(this.projectStatus.selector, document)) { + status = this.projectStatus; + } else if (this.defaultStatus.text && this.vscode.languages.match(this.defaultStatus.selector, document)) { + status = this.defaultStatus; + } + + if (status) { + this.statusBarItem.text = status.text; + this.statusBarItem.command = status.command; + this.statusBarItem.color = status.color; + this.statusBarItem.show(); + return; + } + + this.statusBarItem.hide(); + } + + private SetAndRenderStatusBar(text: string, command: string, color?: string) { + this.statusBarItem.text = text; + this.statusBarItem.command = command; + this.statusBarItem.color = color; + this.statusBarItem.show(); + } +} \ No newline at end of file diff --git a/src/observers/status.ts b/test/unitTests/logging/ProjectStatusBarObserver.test.ts similarity index 57% rename from src/observers/status.ts rename to test/unitTests/logging/ProjectStatusBarObserver.test.ts index 6074edca30..2eaafbeb17 100644 --- a/src/observers/status.ts +++ b/test/unitTests/logging/ProjectStatusBarObserver.test.ts @@ -3,16 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as vscode from '../vscodeAdapter'; +import { should } from 'chai'; -export class Status { - - selector: vscode.DocumentSelector; - text: string; - command: string; - color: string; - - constructor(selector: vscode.DocumentSelector) { - this.selector = selector; - } -} \ No newline at end of file +suite('ProjectStatusBarObserver', () => { + suiteSetup(() => should()); + + +}); \ No newline at end of file From 274c700cfb4f757faf0bc9ee317fcc0a7023adb9 Mon Sep 17 00:00:00 2001 From: Akshita Agarwal Date: Mon, 2 Apr 2018 14:15:49 -0700 Subject: [PATCH 33/56] Changes to show two observers --- package-lock.json | 130 +++++++++++++++----- package.json | 1 + src/main.ts | 7 +- src/observers/OmnisharpStatusBarObserver.ts | 90 ++------------ src/observers/ProjectStatusBarObserver.ts | 78 ++++++++++++ src/vscodeAdapter.ts | 2 +- 6 files changed, 191 insertions(+), 117 deletions(-) create mode 100644 src/observers/ProjectStatusBarObserver.ts diff --git a/package-lock.json b/package-lock.json index c4b6181d2d..1660baedb1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "csharp", - "version": "1.15.0-beta3", + "version": "1.15.0-beta4", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -25,7 +25,7 @@ "integrity": "sha512-MFiW54UOSt+f2bRw8J7LgQeIvE/9b4oGvwU7XW30S9QGAiHGnU/fmiOprsyMkdmH2rl8xSPc0/yrQw8juXU6bQ==", "dev": true, "requires": { - "@types/chai": "4.0.8" + "@types/chai": "4.1.2" } }, "@types/chai-string": { @@ -247,7 +247,8 @@ "abbrev": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", - "integrity": "sha1-kbR5JYinc4wl813W9jdSovh3YTU=" + "integrity": "sha1-kbR5JYinc4wl813W9jdSovh3YTU=", + "dev": true }, "agent-base": { "version": "4.1.2", @@ -273,6 +274,7 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", + "dev": true, "requires": { "kind-of": "3.2.2", "longest": "1.0.1", @@ -282,7 +284,8 @@ "amdefine": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", - "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=" + "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", + "dev": true }, "ansi-colors": { "version": "1.1.0", @@ -358,6 +361,7 @@ "version": "1.0.9", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.9.tgz", "integrity": "sha1-c9g7wmP4bpf4zE9rrhsOkKfSLIY=", + "dev": true, "requires": { "sprintf-js": "1.0.3" } @@ -489,7 +493,8 @@ "async": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=" + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", + "dev": true }, "async-arrays": { "version": "1.0.1", @@ -570,7 +575,8 @@ "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true }, "base64-js": { "version": "1.2.3", @@ -631,6 +637,7 @@ "version": "1.1.8", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=", + "dev": true, "requires": { "balanced-match": "1.0.0", "concat-map": "0.0.1" @@ -686,6 +693,7 @@ "version": "0.1.3", "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", + "dev": true, "optional": true, "requires": { "align-text": "0.1.4", @@ -1003,7 +1011,8 @@ "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true }, "convert-source-map": { "version": "1.5.1", @@ -1230,7 +1239,8 @@ "decamelize": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true }, "deep-assign": { "version": "1.0.0", @@ -1253,7 +1263,8 @@ "deep-is": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=" + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true }, "defaults": { "version": "1.0.3", @@ -1543,6 +1554,7 @@ "version": "1.8.1", "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.8.1.tgz", "integrity": "sha1-WltTr0aTEQvrsIZ6o0MN07cKEBg=", + "dev": true, "requires": { "esprima": "2.7.3", "estraverse": "1.9.3", @@ -1555,6 +1567,7 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz", "integrity": "sha1-2rc/vPwrqBm03gO9b26qSBZLP50=", + "dev": true, "optional": true, "requires": { "amdefine": "1.0.1" @@ -1565,17 +1578,20 @@ "esprima": { "version": "2.7.3", "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", - "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=" + "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", + "dev": true }, "estraverse": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.9.3.tgz", - "integrity": "sha1-r2fy3JIlgkFZUJJgkaQAXSnJu0Q=" + "integrity": "sha1-r2fy3JIlgkFZUJJgkaQAXSnJu0Q=", + "dev": true }, "esutils": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", - "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=" + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", + "dev": true }, "event-stream": { "version": "3.3.4", @@ -1714,7 +1730,8 @@ "fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true }, "fd-slicer": { "version": "1.0.1", @@ -2017,6 +2034,15 @@ "is-glob": "2.0.1" } }, + "glob-promise": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/glob-promise/-/glob-promise-3.4.0.tgz", + "integrity": "sha512-q08RJ6O+eJn+dVanerAndJwIcumgbDdYiUT7zFQl3Wm1xD6fBKtah7H8ZJChj4wP+8C+QfeVy8xautR7rdmKEw==", + "dev": true, + "requires": { + "@types/glob": "5.0.35" + } + }, "glob-stream": { "version": "3.1.18", "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-3.1.18.tgz", @@ -3167,6 +3193,7 @@ "version": "4.0.11", "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.0.11.tgz", "integrity": "sha1-Ywo13+ApS8KB7a5v/F0yn8eYLcw=", + "dev": true, "requires": { "async": "1.5.2", "optimist": "0.6.1", @@ -3178,6 +3205,7 @@ "version": "0.4.4", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", + "dev": true, "requires": { "amdefine": "1.0.1" } @@ -3223,7 +3251,8 @@ "has-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=" + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true }, "has-gulplog": { "version": "0.1.0", @@ -3368,6 +3397,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, "requires": { "once": "1.4.0", "wrappy": "1.0.2" @@ -3376,7 +3406,8 @@ "inherits": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true }, "ini": { "version": "1.3.5", @@ -3429,7 +3460,8 @@ "is-buffer": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true }, "is-builtin-module": { "version": "1.0.0", @@ -3662,7 +3694,8 @@ "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true }, "isobject": { "version": "2.1.0", @@ -3691,6 +3724,7 @@ "version": "0.4.5", "resolved": "https://registry.npmjs.org/istanbul/-/istanbul-0.4.5.tgz", "integrity": "sha1-ZcfXPUxNqE1POsMQuRj7C4Azczs=", + "dev": true, "requires": { "abbrev": "1.0.9", "async": "1.5.2", @@ -3712,6 +3746,7 @@ "version": "5.0.15", "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", + "dev": true, "requires": { "inflight": "1.0.6", "inherits": "2.0.3", @@ -3723,12 +3758,14 @@ "resolve": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", - "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=" + "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", + "dev": true }, "supports-color": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true, "requires": { "has-flag": "1.0.0" } @@ -3890,6 +3927,7 @@ "version": "3.11.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.11.0.tgz", "integrity": "sha512-saJstZWv7oNeOyBh3+Dx1qWzhW0+e6/8eDzo7p5rDFqxntSztloLtuKu+Ejhtq82jsilwOIZYsCz+lIjthg1Hw==", + "dev": true, "requires": { "argparse": "1.0.9", "esprima": "4.0.0" @@ -3898,7 +3936,8 @@ "esprima": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz", - "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==" + "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==", + "dev": true } } }, @@ -3991,6 +4030,7 @@ "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, "requires": { "is-buffer": "1.1.6" } @@ -3999,6 +4039,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=", + "dev": true, "optional": true }, "lazystream": { @@ -4061,6 +4102,7 @@ "version": "0.3.0", "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dev": true, "requires": { "prelude-ls": "1.1.2", "type-check": "0.3.2" @@ -4200,11 +4242,6 @@ "integrity": "sha1-+6HEUkwZ7ppfgTa0YJ8BfPTe1pI=", "dev": true }, - "lodash.debounce": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=" - }, "lodash.escape": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-3.2.0.tgz", @@ -4297,7 +4334,8 @@ "longest": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", - "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=" + "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=", + "dev": true }, "lru-cache": { "version": "4.1.1", @@ -4466,6 +4504,7 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, "requires": { "brace-expansion": "1.1.8" } @@ -4625,6 +4664,7 @@ "version": "3.0.6", "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", + "dev": true, "requires": { "abbrev": "1.0.9" } @@ -7577,6 +7617,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, "requires": { "wrappy": "1.0.2" } @@ -7590,6 +7631,7 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", + "dev": true, "requires": { "minimist": "0.0.8", "wordwrap": "0.0.3" @@ -7598,7 +7640,8 @@ "wordwrap": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", - "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=" + "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", + "dev": true } } }, @@ -7606,6 +7649,7 @@ "version": "0.8.2", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", + "dev": true, "requires": { "deep-is": "0.1.3", "fast-levenshtein": "2.0.6", @@ -7772,7 +7816,8 @@ "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true }, "path-is-inside": { "version": "1.0.2", @@ -7908,7 +7953,8 @@ "prelude-ls": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=" + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true }, "preserve": { "version": "0.2.0", @@ -8207,7 +8253,8 @@ "repeat-string": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=" + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", + "dev": true }, "replace-ext": { "version": "0.0.1", @@ -8457,6 +8504,7 @@ "version": "0.1.3", "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=", + "dev": true, "optional": true, "requires": { "align-text": "0.1.4" @@ -8471,6 +8519,11 @@ "glob": "7.1.2" } }, + "rx": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/rx/-/rx-4.1.0.tgz", + "integrity": "sha1-pfE/957zt0D+MKqAP7CfmIBdR4I=" + }, "rxjs": { "version": "5.5.6", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.6.tgz", @@ -8621,7 +8674,8 @@ "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true }, "sshpk": { "version": "1.13.1", @@ -9126,6 +9180,7 @@ "version": "0.3.2", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, "requires": { "prelude-ls": "1.1.2" } @@ -9162,6 +9217,7 @@ "version": "2.8.29", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", + "dev": true, "optional": true, "requires": { "source-map": "0.5.7", @@ -9173,12 +9229,14 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=", + "dev": true, "optional": true }, "cliui": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", + "dev": true, "optional": true, "requires": { "center-align": "0.1.3", @@ -9190,18 +9248,21 @@ "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true, "optional": true }, "wordwrap": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=", + "dev": true, "optional": true }, "yargs": { "version": "3.10.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", + "dev": true, "optional": true, "requires": { "camelcase": "1.2.1", @@ -9216,6 +9277,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", + "dev": true, "optional": true }, "unc-path-regex": { @@ -9577,6 +9639,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/which/-/which-1.3.0.tgz", "integrity": "sha512-xcJpopdamTuY5duC/KnTTNBraPK54YwpenP4lzxU8H91GudWpFv38u0CKjclE1Wi2EH2EDz5LRcHcKbCIzqGyg==", + "dev": true, "requires": { "isexe": "2.0.0" } @@ -9591,6 +9654,7 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=", + "dev": true, "optional": true }, "wolfy87-eventemitter": { @@ -9602,7 +9666,8 @@ "wordwrap": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=" + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", + "dev": true }, "wrap-ansi": { "version": "2.1.0", @@ -9617,7 +9682,8 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true }, "xmlbuilder": { "version": "9.0.7", diff --git a/package.json b/package.json index 3de7e4635b..89003a386f 100644 --- a/package.json +++ b/package.json @@ -71,6 +71,7 @@ "mkdirp": "^0.5.1", "open": "*", "request-light": "^0.2.0", + "rx": "^4.1.0", "rxjs": "^5.5.6", "semver": "*", "tmp": "0.0.33", diff --git a/src/main.ts b/src/main.ts index c29e4d4cc1..0a9e06ea8e 100644 --- a/src/main.ts +++ b/src/main.ts @@ -26,6 +26,7 @@ import { StatusBarItemAdapter } from './statusBarItemAdapter'; import { TelemetryObserver } from './observers/TelemetryObserver'; import TelemetryReporter from 'vscode-extension-telemetry'; import { addJSONProviders } from './features/json/jsonContributions'; +import { ProjectStatusBarObserver } from './observers/ProjectStatusBarObserver'; export async function activate(context: vscode.ExtensionContext): Promise<{ initializationFinished: Promise }> { @@ -63,10 +64,14 @@ export async function activate(context: vscode.ExtensionContext): Promise<{ init let informationMessageObserver = new InformationMessageObserver(vscode); eventStream.subscribe(informationMessageObserver.post); - let omnisharpStatusBar = new StatusBarItemAdapter(vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, Number.MIN_VALUE)); + let omnisharpStatusBar = new StatusBarItemAdapter(vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, Number.MIN_VALUE)); let omnisharpStatusBarObserver = new OmnisharpStatusBarObserver(vscode, omnisharpStatusBar); eventStream.subscribe(omnisharpStatusBarObserver.post); + let projectStatusBar = new StatusBarItemAdapter(vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left)); + let projectStatusBarObserver = new ProjectStatusBarObserver(vscode, projectStatusBar); + eventStream.subscribe(projectStatusBarObserver.post); + const debugMode = false; if (debugMode) { let omnisharpDebugModeLoggerObserver = new OmnisharpDebugModeLoggerObserver(omnisharpChannel); diff --git a/src/observers/OmnisharpStatusBarObserver.ts b/src/observers/OmnisharpStatusBarObserver.ts index 1a71175457..56649e6256 100644 --- a/src/observers/OmnisharpStatusBarObserver.ts +++ b/src/observers/OmnisharpStatusBarObserver.ts @@ -7,27 +7,18 @@ import { DocumentFilter, DocumentSelector, StatusBarItem, vscode } from '../vsco import { basename } from 'path'; import { OmnisharpServerOnServerError, BaseEvent, OmnisharpOnMultipleLaunchTargets, OmnisharpOnBeforeServerInstall, OmnisharpOnBeforeServerStart, ActiveTextEditorChanged, OmnisharpServerOnStop, OmnisharpServerOnStart, WorkspaceInformationUpdated } from "../omnisharp/loggingEvents"; -let defaultSelector: DocumentSelector = [ - 'csharp', // c#-files OR - { pattern: '**/project.json' }, // project.json-files OR - { pattern: '**/*.sln' }, // any solution file OR - { pattern: '**/*.csproj' }, // an csproj file - { pattern: '**/*.csx' }, // C# script - { pattern: '**/*.cake' } // Cake script -]; - export class OmnisharpStatusBarObserver { constructor(private vscode: vscode, private statusBarItem: StatusBarItem) { } -//This should not care about the + //This should not care about the public post = (event: BaseEvent) => { switch (event.constructor.name) { case OmnisharpServerOnServerError.name: this.SetAndRenderStatusBar('$(flame) Error starting OmniSharp', 'o.showOutput', ''); break; case OmnisharpOnMultipleLaunchTargets.name: - this.SetAndRenderStatusBar('$(flame) Select project', 'o.pickProjectAndStart', 'rgb(90, 218, 90)'); + this.SetAndRenderStatusBar('$(flame) Select project', 'o.showOutput', 'rgb(90, 218, 90)'); break; case OmnisharpOnBeforeServerInstall.name: this.SetAndRenderStatusBar('$(flame) Installing OmniSharp...', 'o.showOutput', ''); @@ -38,82 +29,15 @@ export class OmnisharpStatusBarObserver { case ActiveTextEditorChanged.name: break; case OmnisharpServerOnStop.name: - this.statusBarItem.text = undefined; - this.statusBarItem.command = command; - this.statusBarItem.color = color; - this.statusBarItem.show(); - + this.statusBarItem.text = undefined; + this.statusBarItem.command = undefined; + this.statusBarItem.color = undefined; + this.statusBarItem.hide(); break; case OmnisharpServerOnStart.name: - this.SetAndRenderStatusBar('$(flame) Running', 'o.pickProjectAndStart', ''); + this.SetAndRenderStatusBar('$(flame) Running', 'o.showOutput', ''); break; - case WorkspaceInformationUpdated.name: - this.handleWorkspaceInformationUpdated(event); - } - } - - private handleWorkspaceInformationUpdated(event: WorkspaceInformationUpdated) { - interface Project { - Path: string; - SourceFiles: string[]; - } - - let fileNames: DocumentFilter[] = []; - let label: string; - - function addProjectFileNames(project: Project) { - fileNames.push({ pattern: project.Path }); - - if (project.SourceFiles) { - for (let sourceFile of project.SourceFiles) { - fileNames.push({ pattern: sourceFile }); - } - } - } - - let info = event.info; - // show sln-file if applicable - if (info.MsBuild && info.MsBuild.SolutionPath) { - label = basename(info.MsBuild.SolutionPath); //workspace.getRelativePath(info.MsBuild.SolutionPath); - fileNames.push({ pattern: info.MsBuild.SolutionPath }); - - for (let project of info.MsBuild.Projects) { - addProjectFileNames(project); - } - } - - // set project info - this.projectStatus = new Status(fileNames); - SetStatus(this.projectStatus, '$(flame) ' + label, 'o.pickProjectAndStart'); - SetStatus(this.defaultStatus, '$(flame) Switch projects', 'o.pickProjectAndStart'); - this.render(); - } - - private render = () => { - let activeTextEditor = this.vscode.window.activeTextEditor; - if (!activeTextEditor) { - this.statusBarItem.hide(); - return; } - - let document = activeTextEditor.document; - let status: Status; - - if (this.projectStatus && this.vscode.languages.match(this.projectStatus.selector, document)) { - status = this.projectStatus; - } else if (this.defaultStatus.text && this.vscode.languages.match(this.defaultStatus.selector, document)) { - status = this.defaultStatus; - } - - if (status) { - this.statusBarItem.text = status.text; - this.statusBarItem.command = status.command; - this.statusBarItem.color = status.color; - this.statusBarItem.show(); - return; - } - - this.statusBarItem.hide(); } private SetAndRenderStatusBar(text: string, command: string, color?: string) { diff --git a/src/observers/ProjectStatusBarObserver.ts b/src/observers/ProjectStatusBarObserver.ts new file mode 100644 index 0000000000..99e2a7f11c --- /dev/null +++ b/src/observers/ProjectStatusBarObserver.ts @@ -0,0 +1,78 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { DocumentFilter, DocumentSelector, StatusBarItem, vscode } from '../vscodeAdapter'; +import { basename } from 'path'; +import { OmnisharpServerOnServerError, BaseEvent, OmnisharpOnMultipleLaunchTargets, OmnisharpOnBeforeServerInstall, OmnisharpOnBeforeServerStart, ActiveTextEditorChanged, OmnisharpServerOnStop, OmnisharpServerOnStart, WorkspaceInformationUpdated } from "../omnisharp/loggingEvents"; + +export class ProjectStatusBarObserver { + constructor(private vscode: vscode, private statusBarItem: StatusBarItem) { + } +/* Notes: Since we have removed the listeners and the disposables from the server start and stop event, +we will not show up the status bar item :) +*/ + public post = (event: BaseEvent) => { + switch (event.constructor.name) { + case OmnisharpOnMultipleLaunchTargets.name: + this.SetAndRenderStatusBar('Select project', 'o.pickProjectAndStart', 'rgb(90, 218, 90)'); + break; + case ActiveTextEditorChanged.name: + //this.render(); + break; + /* + cross check what do we need to do on these events. Should we call the hide when the stop event is there + case OmnisharpServerOnStop.name: + this.projectStatus = undefined; + this.defaultStatus.text = undefined; + break; + case OmnisharpServerOnStart.name: + SetStatus(this.defaultStatus, '$(flame) Running', 'o.pickProjectAndStart', ''); + this.render(); + break;*/ + case WorkspaceInformationUpdated.name: + this.handleWorkspaceInformationUpdated(event); + } + } + + private handleWorkspaceInformationUpdated(event: WorkspaceInformationUpdated) { + interface Project { + Path: string; + SourceFiles: string[]; + } + + let fileNames: DocumentFilter[] = []; + let label: string; + + function addProjectFileNames(project: Project) { + fileNames.push({ pattern: project.Path }); + + if (project.SourceFiles) { + for (let sourceFile of project.SourceFiles) { + fileNames.push({ pattern: sourceFile }); + } + } + } + + let info = event.info; + // show sln-file if applicable + if (info.MsBuild && info.MsBuild.SolutionPath) { + label = basename(info.MsBuild.SolutionPath); //workspace.getRelativePath(info.MsBuild.SolutionPath); + fileNames.push({ pattern: info.MsBuild.SolutionPath }); + + for (let project of info.MsBuild.Projects) { + addProjectFileNames(project); + } + } + + this.SetAndRenderStatusBar(label, 'o.pickProjectAndStart'); + } + + private SetAndRenderStatusBar(text: string, command: string, color?: string) { + this.statusBarItem.text = text; + this.statusBarItem.command = command; + this.statusBarItem.color = color; + this.statusBarItem.show(); + } +} \ No newline at end of file diff --git a/src/vscodeAdapter.ts b/src/vscodeAdapter.ts index bc2581d83e..a7283c3141 100644 --- a/src/vscodeAdapter.ts +++ b/src/vscodeAdapter.ts @@ -297,7 +297,7 @@ export interface TextEditor { /** * The document associated with this text editor. The document will be the same for the entire lifetime of this text editor. */ - document: TextDocument; + document: any; } /** * A universal resource identifier representing either a file on disk From c0cb935267f697f50e3f0f2e2983547c4c910b6f Mon Sep 17 00:00:00 2001 From: Akshita Agarwal Date: Mon, 2 Apr 2018 14:16:17 -0700 Subject: [PATCH 34/56] some more changes --- src/observers/ProjectStatusObserver.ts | 107 ------------------------- 1 file changed, 107 deletions(-) delete mode 100644 src/observers/ProjectStatusObserver.ts diff --git a/src/observers/ProjectStatusObserver.ts b/src/observers/ProjectStatusObserver.ts deleted file mode 100644 index 215255b37f..0000000000 --- a/src/observers/ProjectStatusObserver.ts +++ /dev/null @@ -1,107 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { DocumentFilter, DocumentSelector, StatusBarItem, vscode } from '../vscodeAdapter'; -import { basename } from 'path'; -import { OmnisharpServerOnServerError, BaseEvent, OmnisharpOnMultipleLaunchTargets, OmnisharpOnBeforeServerInstall, OmnisharpOnBeforeServerStart, ActiveTextEditorChanged, OmnisharpServerOnStop, OmnisharpServerOnStart, WorkspaceInformationUpdated } from "../omnisharp/loggingEvents"; -export class OmnisharpStatusBarObserver { - constructor(private vscode: vscode, private statusBarItem: StatusBarItem) { - } -/* Notes: Since we have removed the listeners and the disposables from the server start and stop event, -we will not show up the status bar item :) -*/ - public post = (event: BaseEvent) => { - switch (event.constructor.name) { - case OmnisharpOnMultipleLaunchTargets.name: - SetStatus(this.defaultStatus, '$(flame) Select project', 'o.pickProjectAndStart', 'rgb(90, 218, 90)'); - this.render(); - break; - case ActiveTextEditorChanged.name: - this.render(); - break; - case OmnisharpServerOnStop.name: - this.projectStatus = undefined; - this.defaultStatus.text = undefined; - break; - case OmnisharpServerOnStart.name: - SetStatus(this.defaultStatus, '$(flame) Running', 'o.pickProjectAndStart', ''); - this.render(); - break; - case WorkspaceInformationUpdated.name: - this.handleWorkspaceInformationUpdated(event); - } - } - - private handleWorkspaceInformationUpdated(event: WorkspaceInformationUpdated) { - interface Project { - Path: string; - SourceFiles: string[]; - } - - let fileNames: DocumentFilter[] = []; - let label: string; - - function addProjectFileNames(project: Project) { - fileNames.push({ pattern: project.Path }); - - if (project.SourceFiles) { - for (let sourceFile of project.SourceFiles) { - fileNames.push({ pattern: sourceFile }); - } - } - } - - let info = event.info; - // show sln-file if applicable - if (info.MsBuild && info.MsBuild.SolutionPath) { - label = basename(info.MsBuild.SolutionPath); //workspace.getRelativePath(info.MsBuild.SolutionPath); - fileNames.push({ pattern: info.MsBuild.SolutionPath }); - - for (let project of info.MsBuild.Projects) { - addProjectFileNames(project); - } - } - - // set project info - this.projectStatus = new Status(fileNames); - SetStatus(this.projectStatus, '$(flame) ' + label, 'o.pickProjectAndStart'); - SetStatus(this.defaultStatus, '$(flame) Switch projects', 'o.pickProjectAndStart'); - this.render(); - } - - private render = () => { - let activeTextEditor = this.vscode.window.activeTextEditor; - if (!activeTextEditor) { - this.statusBarItem.hide(); - return; - } - - let document = activeTextEditor.document; - let status: Status; - - if (this.projectStatus && this.vscode.languages.match(this.projectStatus.selector, document)) { - status = this.projectStatus; - } else if (this.defaultStatus.text && this.vscode.languages.match(this.defaultStatus.selector, document)) { - status = this.defaultStatus; - } - - if (status) { - this.statusBarItem.text = status.text; - this.statusBarItem.command = status.command; - this.statusBarItem.color = status.color; - this.statusBarItem.show(); - return; - } - - this.statusBarItem.hide(); - } - - private SetAndRenderStatusBar(text: string, command: string, color?: string) { - this.statusBarItem.text = text; - this.statusBarItem.command = command; - this.statusBarItem.color = color; - this.statusBarItem.show(); - } -} \ No newline at end of file From 5d31fd3c4597aea8e6eebad1eb001d22d796cf36 Mon Sep 17 00:00:00 2001 From: Unknown Date: Mon, 2 Apr 2018 16:04:34 -0700 Subject: [PATCH 35/56] Lot of questions! --- src/observers/BaseStatusBarItemObserver.ts | 30 +++++++++++ src/observers/OmnisharpStatusBarObserver.ts | 30 +++-------- src/observers/ProjectStatusBarObserver.ts | 27 +++++----- .../OmnisharpStatusBarObserver.test.ts | 53 +++++++------------ .../logging/ProjectStatusBarObserver.test.ts | 48 ++++++++++++++++- 5 files changed, 116 insertions(+), 72 deletions(-) create mode 100644 src/observers/BaseStatusBarItemObserver.ts diff --git a/src/observers/BaseStatusBarItemObserver.ts b/src/observers/BaseStatusBarItemObserver.ts new file mode 100644 index 0000000000..f0c0f24699 --- /dev/null +++ b/src/observers/BaseStatusBarItemObserver.ts @@ -0,0 +1,30 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { vscode, StatusBarItem } from '../vscodeAdapter'; +import { ViewColumn } from "../vscodeAdapter"; +import { BaseEvent } from '../omnisharp/loggingEvents'; + +export abstract class BaseStatusBarItemObserver { + + constructor(private vscode: vscode, private statusBarItem: StatusBarItem) { + } + + public SetAndShowStatusBar(text: string, command: string, color?: string) { + this.statusBarItem.text = text; + this.statusBarItem.command = command; + this.statusBarItem.color = color; + this.statusBarItem.show(); + } + + public ResetAndHideStatusBar() { + this.statusBarItem.text = undefined; + this.statusBarItem.command = undefined; + this.statusBarItem.color = undefined; + this.statusBarItem.hide(); + } + + abstract post: (event: BaseEvent) => void; +} \ No newline at end of file diff --git a/src/observers/OmnisharpStatusBarObserver.ts b/src/observers/OmnisharpStatusBarObserver.ts index 56649e6256..cc4e69ce72 100644 --- a/src/observers/OmnisharpStatusBarObserver.ts +++ b/src/observers/OmnisharpStatusBarObserver.ts @@ -6,45 +6,31 @@ import { DocumentFilter, DocumentSelector, StatusBarItem, vscode } from '../vscodeAdapter'; import { basename } from 'path'; import { OmnisharpServerOnServerError, BaseEvent, OmnisharpOnMultipleLaunchTargets, OmnisharpOnBeforeServerInstall, OmnisharpOnBeforeServerStart, ActiveTextEditorChanged, OmnisharpServerOnStop, OmnisharpServerOnStart, WorkspaceInformationUpdated } from "../omnisharp/loggingEvents"; +import { BaseStatusBarItemObserver } from './BaseStatusBarItemObserver'; -export class OmnisharpStatusBarObserver { - - constructor(private vscode: vscode, private statusBarItem: StatusBarItem) { - } +export class OmnisharpStatusBarObserver extends BaseStatusBarItemObserver { //This should not care about the public post = (event: BaseEvent) => { switch (event.constructor.name) { case OmnisharpServerOnServerError.name: - this.SetAndRenderStatusBar('$(flame) Error starting OmniSharp', 'o.showOutput', ''); - break; - case OmnisharpOnMultipleLaunchTargets.name: - this.SetAndRenderStatusBar('$(flame) Select project', 'o.showOutput', 'rgb(90, 218, 90)'); + this.SetAndShowStatusBar('$(flame) Error starting OmniSharp', 'o.showOutput', ''); break; case OmnisharpOnBeforeServerInstall.name: - this.SetAndRenderStatusBar('$(flame) Installing OmniSharp...', 'o.showOutput', ''); + this.SetAndShowStatusBar('$(flame) Installing OmniSharp...', 'o.showOutput', ''); break; case OmnisharpOnBeforeServerStart.name: - this.SetAndRenderStatusBar('$(flame) Starting...', 'o.showOutput', ''); + this.SetAndShowStatusBar('$(flame) Starting...', 'o.showOutput', ''); break; + // Do we need active text editor here ???? case ActiveTextEditorChanged.name: break; case OmnisharpServerOnStop.name: - this.statusBarItem.text = undefined; - this.statusBarItem.command = undefined; - this.statusBarItem.color = undefined; - this.statusBarItem.hide(); + this.ResetAndHideStatusBar(); break; case OmnisharpServerOnStart.name: - this.SetAndRenderStatusBar('$(flame) Running', 'o.showOutput', ''); + this.SetAndShowStatusBar('$(flame)', 'o.showOutput', ''); break; } } - - private SetAndRenderStatusBar(text: string, command: string, color?: string) { - this.statusBarItem.text = text; - this.statusBarItem.command = command; - this.statusBarItem.color = color; - this.statusBarItem.show(); - } } diff --git a/src/observers/ProjectStatusBarObserver.ts b/src/observers/ProjectStatusBarObserver.ts index 99e2a7f11c..ae3afc2160 100644 --- a/src/observers/ProjectStatusBarObserver.ts +++ b/src/observers/ProjectStatusBarObserver.ts @@ -6,17 +6,16 @@ import { DocumentFilter, DocumentSelector, StatusBarItem, vscode } from '../vscodeAdapter'; import { basename } from 'path'; import { OmnisharpServerOnServerError, BaseEvent, OmnisharpOnMultipleLaunchTargets, OmnisharpOnBeforeServerInstall, OmnisharpOnBeforeServerStart, ActiveTextEditorChanged, OmnisharpServerOnStop, OmnisharpServerOnStart, WorkspaceInformationUpdated } from "../omnisharp/loggingEvents"; +import { BaseStatusBarItemObserver } from './BaseStatusBarItemObserver'; -export class ProjectStatusBarObserver { - constructor(private vscode: vscode, private statusBarItem: StatusBarItem) { - } -/* Notes: Since we have removed the listeners and the disposables from the server start and stop event, -we will not show up the status bar item :) -*/ +export class ProjectStatusBarObserver extends BaseStatusBarItemObserver{ + /* Notes: Since we have removed the listeners and the disposables from the server start and stop event, + we will not show up the status bar item :) + */ public post = (event: BaseEvent) => { switch (event.constructor.name) { case OmnisharpOnMultipleLaunchTargets.name: - this.SetAndRenderStatusBar('Select project', 'o.pickProjectAndStart', 'rgb(90, 218, 90)'); + this.SetAndShowStatusBar('Select project', 'o.pickProjectAndStart', 'rgb(90, 218, 90)'); break; case ActiveTextEditorChanged.name: //this.render(); @@ -57,6 +56,11 @@ we will not show up the status bar item :) let info = event.info; // show sln-file if applicable + + // if we remove the matching then this whole concept of making the filenames array doesnot make any sense\ + // What to do ????? + // And is it possible that we have the inof but no msbuild so that label is undefined + // should we include the set thing inside the if ???? if (info.MsBuild && info.MsBuild.SolutionPath) { label = basename(info.MsBuild.SolutionPath); //workspace.getRelativePath(info.MsBuild.SolutionPath); fileNames.push({ pattern: info.MsBuild.SolutionPath }); @@ -66,13 +70,6 @@ we will not show up the status bar item :) } } - this.SetAndRenderStatusBar(label, 'o.pickProjectAndStart'); - } - - private SetAndRenderStatusBar(text: string, command: string, color?: string) { - this.statusBarItem.text = text; - this.statusBarItem.command = command; - this.statusBarItem.color = color; - this.statusBarItem.show(); + this.SetAndShowStatusBar(label, 'o.pickProjectAndStart'); } } \ No newline at end of file diff --git a/test/unitTests/logging/OmnisharpStatusBarObserver.test.ts b/test/unitTests/logging/OmnisharpStatusBarObserver.test.ts index c078c9bc50..8ca26c691a 100644 --- a/test/unitTests/logging/OmnisharpStatusBarObserver.test.ts +++ b/test/unitTests/logging/OmnisharpStatusBarObserver.test.ts @@ -4,31 +4,33 @@ *--------------------------------------------------------------------------------------------*/ import { DocumentSelector, StatusBarItem, vscode } from '../../../src/vscodeAdapter'; -import { OmnisharpOnBeforeServerInstall, OmnisharpOnBeforeServerStart, OmnisharpOnMultipleLaunchTargets, OmnisharpServerOnServerError, OmnisharpServerOnStart } from '../../../src/omnisharp/loggingEvents'; +import { OmnisharpOnBeforeServerInstall, OmnisharpOnBeforeServerStart, OmnisharpOnMultipleLaunchTargets, OmnisharpServerOnServerError, OmnisharpServerOnStart, OmnisharpServerOnStop } from '../../../src/omnisharp/loggingEvents'; import { expect, should } from 'chai'; import { OmnisharpStatusBarObserver } from '../../../src/observers/OmnisharpStatusBarObserver'; import { getFakeVsCode, getWorkspaceInformationUpdated, getMSBuildWorkspaceInformation } from './Fakes'; -suite('OmnisharpServerStatusObserver', () => { +suite('OmnisharpStatusBarObserver', () => { suiteSetup(() => should()); let output = ''; let showCalled: boolean; + let hideCalled: boolean; setup(() => { output = ''; showCalled = false; + hideCalled = false; }); let vscode: vscode = getFakeVsCode(); vscode.window.activeTextEditor = { document: undefined }; - vscode.languages.match = (selector: DocumentSelector, document: any) => { return 2; }; let statusBarItem = { - show: () => { showCalled = true; } + show: () => { showCalled = true; }, + hide: () => { hideCalled = true; } }; let observer = new OmnisharpStatusBarObserver(vscode, statusBarItem); - test('OnServerError: If there is no project status yet, status bar is shown with the error text', () => { + test('OnServerError: Status bar is shown with the error text', () => { let event = new OmnisharpServerOnServerError("someError"); observer.post(event); expect(showCalled).to.be.true; @@ -36,7 +38,7 @@ suite('OmnisharpServerStatusObserver', () => { expect(statusBarItem.command).to.equal('o.showOutput'); }); - test('OnBeforeServerInstall: If there is no project status yet, status bar is shown with the installation text', () => { + test('OnBeforeServerInstall: Status bar is shown with the installation text', () => { let event = new OmnisharpOnBeforeServerInstall(); observer.post(event); expect(showCalled).to.be.true; @@ -44,7 +46,7 @@ suite('OmnisharpServerStatusObserver', () => { expect(statusBarItem.command).to.equal('o.showOutput'); }); - test('OnBeforeServerStart: If there is no project status yet, status bar is shown with the starting text', () => { + test('OnBeforeServerStart: Status bar is shown with the starting text', () => { let event = new OmnisharpOnBeforeServerStart(); observer.post(event); expect(showCalled).to.be.true; @@ -52,37 +54,20 @@ suite('OmnisharpServerStatusObserver', () => { expect(statusBarItem.command).to.equal('o.showOutput'); }); - test('OnMultipleLaunchTargets: If there is no project status yet, status bar is shown with the select project option and the comand to pick a project', () => { - let event = new OmnisharpOnMultipleLaunchTargets([]); - observer.post(event); - expect(showCalled).to.be.true; - expect(statusBarItem.text).to.be.equal('$(flame) Select project'); - expect(statusBarItem.command).to.equal('o.pickProjectAndStart'); - }); - - test('OnServerStart: If there is no project status, default status should be shown as Running', () => { + test('OnServerStart: Status bar is shown with the flame and "Running" text', () => { let event = new OmnisharpServerOnStart(); observer.post(event); expect(showCalled).to.be.true; - expect(statusBarItem.text).to.be.equal('$(flame) Running'); - expect(statusBarItem.command).to.equal('o.pickProjectAndStart'); + expect(statusBarItem.text).to.be.equal('$(flame)'); + expect(statusBarItem.command).to.equal('o.showOutput'); }); - suite('WorkspaceInformationUpdated', () => { - /*test('Project status is shown', () => { - let event = getWorkspaceInformationUpdated(null); - observer.post(event); - expect(showCalled).to.be.true; - expect(statusBarItem.text).to.be.equal('$(flame) '); - expect(statusBarItem.command).to.equal('o.pickProjectAndStart'); - }); - - test('Project status is shown', () => { - let event = getWorkspaceInformationUpdated(getMSBuildWorkspaceInformation("somePath", [])); - observer.post(event); - expect(showCalled).to.be.true; - expect(statusBarItem.text).to.be.equal('$(flame) '+`${event.info.MsBuild.SolutionPath}`); - expect(statusBarItem.command).to.equal('o.pickProjectAndStart'); - });*/ + test('OnServerStop: Status bar is hidden and the attributes are set to undefined', () => { + let event = new OmnisharpServerOnStop(); + observer.post(event); + expect(hideCalled).to.be.true; + expect(statusBarItem.text).to.be.undefined; + expect(statusBarItem.command).to.be.undefined; + expect(statusBarItem.color).to.be.undefined; }); }); \ No newline at end of file diff --git a/test/unitTests/logging/ProjectStatusBarObserver.test.ts b/test/unitTests/logging/ProjectStatusBarObserver.test.ts index 2eaafbeb17..2df2c761bb 100644 --- a/test/unitTests/logging/ProjectStatusBarObserver.test.ts +++ b/test/unitTests/logging/ProjectStatusBarObserver.test.ts @@ -3,10 +3,56 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { should } from 'chai'; +import { expect, should } from 'chai'; +import { getFakeVsCode, getWorkspaceInformationUpdated, getMSBuildWorkspaceInformation } from './Fakes'; +import { vscode, StatusBarItem } from '../../../src/vscodeAdapter'; +import { ProjectStatusBarObserver } from '../../../src/observers/ProjectStatusBarObserver'; +import { OmnisharpOnMultipleLaunchTargets } from '../../../src/omnisharp/loggingEvents'; suite('ProjectStatusBarObserver', () => { suiteSetup(() => should()); + let output = ''; + let showCalled: boolean; + setup(() => { + output = ''; + showCalled = false; + }); + + let vscode: vscode = getFakeVsCode(); + vscode.window.activeTextEditor = { document: undefined }; + + let statusBarItem = { + show: () => { showCalled = true; } + }; + + let observer = new ProjectStatusBarObserver(vscode, statusBarItem); + test('OnMultipleLaunchTargets: If there is no project status yet, status bar is shown with the select project option and the comand to pick a project', () => { + let event = new OmnisharpOnMultipleLaunchTargets([]); + observer.post(event); + expect(showCalled).to.be.true; + expect(statusBarItem.text).to.be.equal('Select project'); + expect(statusBarItem.command).to.equal('o.pickProjectAndStart'); + }); + + // What to do of this test case here ?????? + + /*suite('WorkspaceInformationUpdated', () => { + test('Project status is shown', () => { + let event = getWorkspaceInformationUpdated(null); + observer.post(event); + expect(showCalled).to.be.true; + expect(statusBarItem.text).to.be.equal('$(flame) '); + expect(statusBarItem.command).to.equal('o.pickProjectAndStart'); + });*/ + + test('Project status is shown', () => { + let event = getWorkspaceInformationUpdated(getMSBuildWorkspaceInformation("somePath", [])); + observer.post(event); + expect(showCalled).to.be.true; + expect(statusBarItem.text).to.be.equal(event.info.MsBuild.SolutionPath); + expect(statusBarItem.command).to.equal('o.pickProjectAndStart'); + }); + }); }); \ No newline at end of file From 49cd55fab06ad4dc82916711122f21e522b94010 Mon Sep 17 00:00:00 2001 From: Unknown Date: Mon, 2 Apr 2018 16:38:59 -0700 Subject: [PATCH 36/56] build issues --- .../logging/ProjectStatusBarObserver.test.ts | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/test/unitTests/logging/ProjectStatusBarObserver.test.ts b/test/unitTests/logging/ProjectStatusBarObserver.test.ts index 2df2c761bb..e2db500631 100644 --- a/test/unitTests/logging/ProjectStatusBarObserver.test.ts +++ b/test/unitTests/logging/ProjectStatusBarObserver.test.ts @@ -26,7 +26,7 @@ suite('ProjectStatusBarObserver', () => { }; let observer = new ProjectStatusBarObserver(vscode, statusBarItem); - + test('OnMultipleLaunchTargets: If there is no project status yet, status bar is shown with the select project option and the comand to pick a project', () => { let event = new OmnisharpOnMultipleLaunchTargets([]); observer.post(event); @@ -36,7 +36,7 @@ suite('ProjectStatusBarObserver', () => { }); // What to do of this test case here ?????? - + /*suite('WorkspaceInformationUpdated', () => { test('Project status is shown', () => { let event = getWorkspaceInformationUpdated(null); @@ -46,13 +46,11 @@ suite('ProjectStatusBarObserver', () => { expect(statusBarItem.command).to.equal('o.pickProjectAndStart'); });*/ - test('Project status is shown', () => { - let event = getWorkspaceInformationUpdated(getMSBuildWorkspaceInformation("somePath", [])); - observer.post(event); - expect(showCalled).to.be.true; - expect(statusBarItem.text).to.be.equal(event.info.MsBuild.SolutionPath); - expect(statusBarItem.command).to.equal('o.pickProjectAndStart'); - }); + test('Project status is shown', () => { + let event = getWorkspaceInformationUpdated(getMSBuildWorkspaceInformation("somePath", [])); + observer.post(event); + expect(showCalled).to.be.true; + expect(statusBarItem.text).to.be.equal(event.info.MsBuild.SolutionPath); + expect(statusBarItem.command).to.equal('o.pickProjectAndStart'); }); - }); \ No newline at end of file From 8b03d5de6c916b98f7e810cc7879246e45247804 Mon Sep 17 00:00:00 2001 From: Akshita Agarwal Date: Mon, 2 Apr 2018 17:05:45 -0700 Subject: [PATCH 37/56] Remove usings --- src/observers/BaseStatusBarItemObserver.ts | 1 - src/observers/OmnisharpStatusBarObserver.ts | 5 ++--- src/observers/ProjectStatusBarObserver.ts | 4 ++-- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/observers/BaseStatusBarItemObserver.ts b/src/observers/BaseStatusBarItemObserver.ts index f0c0f24699..c74e9e1207 100644 --- a/src/observers/BaseStatusBarItemObserver.ts +++ b/src/observers/BaseStatusBarItemObserver.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import { vscode, StatusBarItem } from '../vscodeAdapter'; -import { ViewColumn } from "../vscodeAdapter"; import { BaseEvent } from '../omnisharp/loggingEvents'; export abstract class BaseStatusBarItemObserver { diff --git a/src/observers/OmnisharpStatusBarObserver.ts b/src/observers/OmnisharpStatusBarObserver.ts index cc4e69ce72..782c33b7ac 100644 --- a/src/observers/OmnisharpStatusBarObserver.ts +++ b/src/observers/OmnisharpStatusBarObserver.ts @@ -3,9 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { DocumentFilter, DocumentSelector, StatusBarItem, vscode } from '../vscodeAdapter'; -import { basename } from 'path'; -import { OmnisharpServerOnServerError, BaseEvent, OmnisharpOnMultipleLaunchTargets, OmnisharpOnBeforeServerInstall, OmnisharpOnBeforeServerStart, ActiveTextEditorChanged, OmnisharpServerOnStop, OmnisharpServerOnStart, WorkspaceInformationUpdated } from "../omnisharp/loggingEvents"; +import { DocumentFilter, DocumentSelector } from '../vscodeAdapter'; +import { OmnisharpServerOnServerError, BaseEvent, OmnisharpOnBeforeServerInstall, OmnisharpOnBeforeServerStart, ActiveTextEditorChanged, OmnisharpServerOnStop, OmnisharpServerOnStart } from "../omnisharp/loggingEvents"; import { BaseStatusBarItemObserver } from './BaseStatusBarItemObserver'; export class OmnisharpStatusBarObserver extends BaseStatusBarItemObserver { diff --git a/src/observers/ProjectStatusBarObserver.ts b/src/observers/ProjectStatusBarObserver.ts index ae3afc2160..53d734b59d 100644 --- a/src/observers/ProjectStatusBarObserver.ts +++ b/src/observers/ProjectStatusBarObserver.ts @@ -3,9 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { DocumentFilter, DocumentSelector, StatusBarItem, vscode } from '../vscodeAdapter'; +import { DocumentFilter } from '../vscodeAdapter'; import { basename } from 'path'; -import { OmnisharpServerOnServerError, BaseEvent, OmnisharpOnMultipleLaunchTargets, OmnisharpOnBeforeServerInstall, OmnisharpOnBeforeServerStart, ActiveTextEditorChanged, OmnisharpServerOnStop, OmnisharpServerOnStart, WorkspaceInformationUpdated } from "../omnisharp/loggingEvents"; +import { BaseEvent, OmnisharpOnMultipleLaunchTargets, ActiveTextEditorChanged, WorkspaceInformationUpdated } from "../omnisharp/loggingEvents"; import { BaseStatusBarItemObserver } from './BaseStatusBarItemObserver'; export class ProjectStatusBarObserver extends BaseStatusBarItemObserver{ From f908b1deee034a52f578c75dcececcfcea7241d3 Mon Sep 17 00:00:00 2001 From: Unknown Date: Mon, 2 Apr 2018 19:09:43 -0700 Subject: [PATCH 38/56] comments --- src/observers/ProjectStatusBarObserver.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/observers/ProjectStatusBarObserver.ts b/src/observers/ProjectStatusBarObserver.ts index 53d734b59d..f58078bd59 100644 --- a/src/observers/ProjectStatusBarObserver.ts +++ b/src/observers/ProjectStatusBarObserver.ts @@ -34,6 +34,7 @@ export class ProjectStatusBarObserver extends BaseStatusBarItemObserver{ this.handleWorkspaceInformationUpdated(event); } } + //care about stop private handleWorkspaceInformationUpdated(event: WorkspaceInformationUpdated) { interface Project { From 1c85073383e2910ef0efc74c251130a8bacc85d0 Mon Sep 17 00:00:00 2001 From: Unknown Date: Tue, 3 Apr 2018 10:53:51 -0700 Subject: [PATCH 39/56] Remove unnecessary cases --- src/main.ts | 4 +- src/observers/BaseStatusBarItemObserver.ts | 4 +- src/observers/OmnisharpStatusBarObserver.ts | 3 -- src/observers/ProjectStatusBarObserver.ts | 53 +++++---------------- 4 files changed, 15 insertions(+), 49 deletions(-) diff --git a/src/main.ts b/src/main.ts index 0a9e06ea8e..e56848a2db 100644 --- a/src/main.ts +++ b/src/main.ts @@ -65,11 +65,11 @@ export async function activate(context: vscode.ExtensionContext): Promise<{ init eventStream.subscribe(informationMessageObserver.post); let omnisharpStatusBar = new StatusBarItemAdapter(vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, Number.MIN_VALUE)); - let omnisharpStatusBarObserver = new OmnisharpStatusBarObserver(vscode, omnisharpStatusBar); + let omnisharpStatusBarObserver = new OmnisharpStatusBarObserver(omnisharpStatusBar); eventStream.subscribe(omnisharpStatusBarObserver.post); let projectStatusBar = new StatusBarItemAdapter(vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left)); - let projectStatusBarObserver = new ProjectStatusBarObserver(vscode, projectStatusBar); + let projectStatusBarObserver = new ProjectStatusBarObserver(projectStatusBar); eventStream.subscribe(projectStatusBarObserver.post); const debugMode = false; diff --git a/src/observers/BaseStatusBarItemObserver.ts b/src/observers/BaseStatusBarItemObserver.ts index c74e9e1207..fa47c107a4 100644 --- a/src/observers/BaseStatusBarItemObserver.ts +++ b/src/observers/BaseStatusBarItemObserver.ts @@ -3,12 +3,12 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { vscode, StatusBarItem } from '../vscodeAdapter'; +import { StatusBarItem } from '../vscodeAdapter'; import { BaseEvent } from '../omnisharp/loggingEvents'; export abstract class BaseStatusBarItemObserver { - constructor(private vscode: vscode, private statusBarItem: StatusBarItem) { + constructor(private statusBarItem: StatusBarItem) { } public SetAndShowStatusBar(text: string, command: string, color?: string) { diff --git a/src/observers/OmnisharpStatusBarObserver.ts b/src/observers/OmnisharpStatusBarObserver.ts index 782c33b7ac..a536e20a82 100644 --- a/src/observers/OmnisharpStatusBarObserver.ts +++ b/src/observers/OmnisharpStatusBarObserver.ts @@ -20,9 +20,6 @@ export class OmnisharpStatusBarObserver extends BaseStatusBarItemObserver { case OmnisharpOnBeforeServerStart.name: this.SetAndShowStatusBar('$(flame) Starting...', 'o.showOutput', ''); break; - // Do we need active text editor here ???? - case ActiveTextEditorChanged.name: - break; case OmnisharpServerOnStop.name: this.ResetAndHideStatusBar(); break; diff --git a/src/observers/ProjectStatusBarObserver.ts b/src/observers/ProjectStatusBarObserver.ts index f58078bd59..bed5801de5 100644 --- a/src/observers/ProjectStatusBarObserver.ts +++ b/src/observers/ProjectStatusBarObserver.ts @@ -5,70 +5,39 @@ import { DocumentFilter } from '../vscodeAdapter'; import { basename } from 'path'; -import { BaseEvent, OmnisharpOnMultipleLaunchTargets, ActiveTextEditorChanged, WorkspaceInformationUpdated } from "../omnisharp/loggingEvents"; +import { BaseEvent, OmnisharpOnMultipleLaunchTargets, ActiveTextEditorChanged, WorkspaceInformationUpdated, OmnisharpServerOnStop } from "../omnisharp/loggingEvents"; import { BaseStatusBarItemObserver } from './BaseStatusBarItemObserver'; -export class ProjectStatusBarObserver extends BaseStatusBarItemObserver{ - /* Notes: Since we have removed the listeners and the disposables from the server start and stop event, - we will not show up the status bar item :) - */ +export class ProjectStatusBarObserver extends BaseStatusBarItemObserver { + public post = (event: BaseEvent) => { switch (event.constructor.name) { case OmnisharpOnMultipleLaunchTargets.name: this.SetAndShowStatusBar('Select project', 'o.pickProjectAndStart', 'rgb(90, 218, 90)'); break; - case ActiveTextEditorChanged.name: - //this.render(); - break; - /* - cross check what do we need to do on these events. Should we call the hide when the stop event is there case OmnisharpServerOnStop.name: - this.projectStatus = undefined; - this.defaultStatus.text = undefined; + this.ResetAndHideStatusBar(); break; - case OmnisharpServerOnStart.name: - SetStatus(this.defaultStatus, '$(flame) Running', 'o.pickProjectAndStart', ''); - this.render(); - break;*/ case WorkspaceInformationUpdated.name: this.handleWorkspaceInformationUpdated(event); } } - //care about stop private handleWorkspaceInformationUpdated(event: WorkspaceInformationUpdated) { - interface Project { - Path: string; - SourceFiles: string[]; - } - let fileNames: DocumentFilter[] = []; let label: string; - function addProjectFileNames(project: Project) { - fileNames.push({ pattern: project.Path }); - - if (project.SourceFiles) { - for (let sourceFile of project.SourceFiles) { - fileNames.push({ pattern: sourceFile }); - } - } - } - let info = event.info; - // show sln-file if applicable - // if we remove the matching then this whole concept of making the filenames array doesnot make any sense\ - // What to do ????? - // And is it possible that we have the inof but no msbuild so that label is undefined - // should we include the set thing inside the if ???? + //Search for a way to check for the cake and the script project if (info.MsBuild && info.MsBuild.SolutionPath) { label = basename(info.MsBuild.SolutionPath); //workspace.getRelativePath(info.MsBuild.SolutionPath); - fileNames.push({ pattern: info.MsBuild.SolutionPath }); - - for (let project of info.MsBuild.Projects) { - addProjectFileNames(project); - } + } + else if (info.Cake && info.Cake.Path) { + label = basename(info.Cake.Path); + } + else if (info.ScriptCs && info.ScriptCs.Path) { + label = basename(info.ScriptCs.Path); } this.SetAndShowStatusBar(label, 'o.pickProjectAndStart'); From 32afd9766e7f4a7d010e5a7cb6fbb58160ef23e0 Mon Sep 17 00:00:00 2001 From: Akshita Agarwal Date: Tue, 3 Apr 2018 13:27:11 -0700 Subject: [PATCH 40/56] Changes --- src/observers/OmnisharpStatusBarObserver.ts | 3 +- src/observers/ProjectStatusBarObserver.ts | 7 ++-- .../OmnisharpStatusBarObserver.test.ts | 10 +++--- .../logging/ProjectStatusBarObserver.test.ts | 33 ++++++++++++------- 4 files changed, 29 insertions(+), 24 deletions(-) diff --git a/src/observers/OmnisharpStatusBarObserver.ts b/src/observers/OmnisharpStatusBarObserver.ts index a536e20a82..e6c732ef90 100644 --- a/src/observers/OmnisharpStatusBarObserver.ts +++ b/src/observers/OmnisharpStatusBarObserver.ts @@ -8,7 +8,6 @@ import { OmnisharpServerOnServerError, BaseEvent, OmnisharpOnBeforeServerInstall import { BaseStatusBarItemObserver } from './BaseStatusBarItemObserver'; export class OmnisharpStatusBarObserver extends BaseStatusBarItemObserver { - //This should not care about the public post = (event: BaseEvent) => { switch (event.constructor.name) { case OmnisharpServerOnServerError.name: @@ -24,7 +23,7 @@ export class OmnisharpStatusBarObserver extends BaseStatusBarItemObserver { this.ResetAndHideStatusBar(); break; case OmnisharpServerOnStart.name: - this.SetAndShowStatusBar('$(flame)', 'o.showOutput', ''); + this.SetAndShowStatusBar('$(flame) Running', 'o.showOutput', ''); break; } } diff --git a/src/observers/ProjectStatusBarObserver.ts b/src/observers/ProjectStatusBarObserver.ts index bed5801de5..ef1f1d317d 100644 --- a/src/observers/ProjectStatusBarObserver.ts +++ b/src/observers/ProjectStatusBarObserver.ts @@ -13,10 +13,10 @@ export class ProjectStatusBarObserver extends BaseStatusBarItemObserver { public post = (event: BaseEvent) => { switch (event.constructor.name) { case OmnisharpOnMultipleLaunchTargets.name: - this.SetAndShowStatusBar('Select project', 'o.pickProjectAndStart', 'rgb(90, 218, 90)'); + this.SetAndShowStatusBar('$(file-submodule) Select project', 'o.pickProjectAndStart', 'rgb(90, 218, 90)'); break; case OmnisharpServerOnStop.name: - this.ResetAndHideStatusBar(); + this.ResetAndHideStatusBar(); break; case WorkspaceInformationUpdated.name: this.handleWorkspaceInformationUpdated(event); @@ -26,7 +26,6 @@ export class ProjectStatusBarObserver extends BaseStatusBarItemObserver { private handleWorkspaceInformationUpdated(event: WorkspaceInformationUpdated) { let label: string; - let info = event.info; //Search for a way to check for the cake and the script project @@ -40,6 +39,6 @@ export class ProjectStatusBarObserver extends BaseStatusBarItemObserver { label = basename(info.ScriptCs.Path); } - this.SetAndShowStatusBar(label, 'o.pickProjectAndStart'); + this.SetAndShowStatusBar('$(file-directory) ' + label, 'o.pickProjectAndStart'); } } \ No newline at end of file diff --git a/test/unitTests/logging/OmnisharpStatusBarObserver.test.ts b/test/unitTests/logging/OmnisharpStatusBarObserver.test.ts index 8ca26c691a..35b56199af 100644 --- a/test/unitTests/logging/OmnisharpStatusBarObserver.test.ts +++ b/test/unitTests/logging/OmnisharpStatusBarObserver.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { DocumentSelector, StatusBarItem, vscode } from '../../../src/vscodeAdapter'; +import { DocumentSelector, StatusBarItem } from '../../../src/vscodeAdapter'; import { OmnisharpOnBeforeServerInstall, OmnisharpOnBeforeServerStart, OmnisharpOnMultipleLaunchTargets, OmnisharpServerOnServerError, OmnisharpServerOnStart, OmnisharpServerOnStop } from '../../../src/omnisharp/loggingEvents'; import { expect, should } from 'chai'; import { OmnisharpStatusBarObserver } from '../../../src/observers/OmnisharpStatusBarObserver'; @@ -14,21 +14,19 @@ suite('OmnisharpStatusBarObserver', () => { let output = ''; let showCalled: boolean; let hideCalled: boolean; + setup(() => { output = ''; showCalled = false; hideCalled = false; }); - let vscode: vscode = getFakeVsCode(); - vscode.window.activeTextEditor = { document: undefined }; - let statusBarItem = { show: () => { showCalled = true; }, hide: () => { hideCalled = true; } }; - let observer = new OmnisharpStatusBarObserver(vscode, statusBarItem); + let observer = new OmnisharpStatusBarObserver( statusBarItem); test('OnServerError: Status bar is shown with the error text', () => { let event = new OmnisharpServerOnServerError("someError"); @@ -58,7 +56,7 @@ suite('OmnisharpStatusBarObserver', () => { let event = new OmnisharpServerOnStart(); observer.post(event); expect(showCalled).to.be.true; - expect(statusBarItem.text).to.be.equal('$(flame)'); + expect(statusBarItem.text).to.be.equal('$(flame) Running'); expect(statusBarItem.command).to.equal('o.showOutput'); }); diff --git a/test/unitTests/logging/ProjectStatusBarObserver.test.ts b/test/unitTests/logging/ProjectStatusBarObserver.test.ts index e2db500631..0f987e2f77 100644 --- a/test/unitTests/logging/ProjectStatusBarObserver.test.ts +++ b/test/unitTests/logging/ProjectStatusBarObserver.test.ts @@ -7,31 +7,40 @@ import { expect, should } from 'chai'; import { getFakeVsCode, getWorkspaceInformationUpdated, getMSBuildWorkspaceInformation } from './Fakes'; import { vscode, StatusBarItem } from '../../../src/vscodeAdapter'; import { ProjectStatusBarObserver } from '../../../src/observers/ProjectStatusBarObserver'; -import { OmnisharpOnMultipleLaunchTargets } from '../../../src/omnisharp/loggingEvents'; +import { OmnisharpOnMultipleLaunchTargets, OmnisharpServerOnStop } from '../../../src/omnisharp/loggingEvents'; suite('ProjectStatusBarObserver', () => { suiteSetup(() => should()); + let output = ''; let showCalled: boolean; + let hideCalled: boolean; + let statusBarItem = { + show: () => { showCalled = true; }, + hide: () => { hideCalled = true; } + }; + let observer = new ProjectStatusBarObserver(statusBarItem); + setup(() => { output = ''; showCalled = false; + hideCalled = false; }); - let vscode: vscode = getFakeVsCode(); - vscode.window.activeTextEditor = { document: undefined }; - - let statusBarItem = { - show: () => { showCalled = true; } - }; - - let observer = new ProjectStatusBarObserver(vscode, statusBarItem); + test('OnServerStop: Status bar is hidden and the attributes are set to undefined', () => { + let event = new OmnisharpServerOnStop(); + observer.post(event); + expect(hideCalled).to.be.true; + expect(statusBarItem.text).to.be.undefined; + expect(statusBarItem.command).to.be.undefined; + expect(statusBarItem.color).to.be.undefined; + }); - test('OnMultipleLaunchTargets: If there is no project status yet, status bar is shown with the select project option and the comand to pick a project', () => { + test('OnMultipleLaunchTargets: Status bar is shown with the select project option and the comand to pick a project', () => { let event = new OmnisharpOnMultipleLaunchTargets([]); observer.post(event); expect(showCalled).to.be.true; - expect(statusBarItem.text).to.be.equal('Select project'); + expect(statusBarItem.text).to.contain('Select project'); expect(statusBarItem.command).to.equal('o.pickProjectAndStart'); }); @@ -50,7 +59,7 @@ suite('ProjectStatusBarObserver', () => { let event = getWorkspaceInformationUpdated(getMSBuildWorkspaceInformation("somePath", [])); observer.post(event); expect(showCalled).to.be.true; - expect(statusBarItem.text).to.be.equal(event.info.MsBuild.SolutionPath); + expect(statusBarItem.text).to.contain(event.info.MsBuild.SolutionPath); expect(statusBarItem.command).to.equal('o.pickProjectAndStart'); }); }); \ No newline at end of file From 7d7447085a896c169cd6b6dc078eead4f292f48c Mon Sep 17 00:00:00 2001 From: Unknown Date: Tue, 3 Apr 2018 14:47:38 -0700 Subject: [PATCH 41/56] Remove usings --- src/observers/OmnisharpStatusBarObserver.ts | 3 +-- src/observers/ProjectStatusBarObserver.ts | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/observers/OmnisharpStatusBarObserver.ts b/src/observers/OmnisharpStatusBarObserver.ts index e6c732ef90..3258bb9666 100644 --- a/src/observers/OmnisharpStatusBarObserver.ts +++ b/src/observers/OmnisharpStatusBarObserver.ts @@ -3,8 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { DocumentFilter, DocumentSelector } from '../vscodeAdapter'; -import { OmnisharpServerOnServerError, BaseEvent, OmnisharpOnBeforeServerInstall, OmnisharpOnBeforeServerStart, ActiveTextEditorChanged, OmnisharpServerOnStop, OmnisharpServerOnStart } from "../omnisharp/loggingEvents"; +import { OmnisharpServerOnServerError, BaseEvent, OmnisharpOnBeforeServerInstall, OmnisharpOnBeforeServerStart, OmnisharpServerOnStop, OmnisharpServerOnStart } from "../omnisharp/loggingEvents"; import { BaseStatusBarItemObserver } from './BaseStatusBarItemObserver'; export class OmnisharpStatusBarObserver extends BaseStatusBarItemObserver { diff --git a/src/observers/ProjectStatusBarObserver.ts b/src/observers/ProjectStatusBarObserver.ts index ef1f1d317d..e901c1ded8 100644 --- a/src/observers/ProjectStatusBarObserver.ts +++ b/src/observers/ProjectStatusBarObserver.ts @@ -3,9 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { DocumentFilter } from '../vscodeAdapter'; import { basename } from 'path'; -import { BaseEvent, OmnisharpOnMultipleLaunchTargets, ActiveTextEditorChanged, WorkspaceInformationUpdated, OmnisharpServerOnStop } from "../omnisharp/loggingEvents"; +import { BaseEvent, OmnisharpOnMultipleLaunchTargets, WorkspaceInformationUpdated, OmnisharpServerOnStop } from "../omnisharp/loggingEvents"; import { BaseStatusBarItemObserver } from './BaseStatusBarItemObserver'; export class ProjectStatusBarObserver extends BaseStatusBarItemObserver { From 410e99b06a3a19c96fee93766a7277c3c383dd01 Mon Sep 17 00:00:00 2001 From: Unknown Date: Tue, 3 Apr 2018 15:24:51 -0700 Subject: [PATCH 42/56] Dipsose the disposables after the server stop event --- src/omnisharp/server.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/omnisharp/server.ts b/src/omnisharp/server.ts index 208abb2c9a..3eb4a7c169 100644 --- a/src/omnisharp/server.ts +++ b/src/omnisharp/server.ts @@ -361,8 +361,6 @@ export class OmniSharpServer { public stop(): Promise { - this._disposables.dispose(); - let cleanupPromise: Promise; if (this._telemetryIntervalId !== undefined) { @@ -407,6 +405,7 @@ export class OmniSharpServer { this._serverProcess = null; this._setState(ServerState.Stopped); this._fireEvent(Events.ServerStop, this); + this._disposables.dispose(); }); } From 371d24a593fae435d3235c843ba49919e6ceeec4 Mon Sep 17 00:00:00 2001 From: Unknown Date: Wed, 4 Apr 2018 13:45:32 -0700 Subject: [PATCH 43/56] Remove the cake thing --- src/observers/ProjectStatusBarObserver.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/observers/ProjectStatusBarObserver.ts b/src/observers/ProjectStatusBarObserver.ts index e901c1ded8..2646a65047 100644 --- a/src/observers/ProjectStatusBarObserver.ts +++ b/src/observers/ProjectStatusBarObserver.ts @@ -31,12 +31,9 @@ export class ProjectStatusBarObserver extends BaseStatusBarItemObserver { if (info.MsBuild && info.MsBuild.SolutionPath) { label = basename(info.MsBuild.SolutionPath); //workspace.getRelativePath(info.MsBuild.SolutionPath); } - else if (info.Cake && info.Cake.Path) { - label = basename(info.Cake.Path); - } - else if (info.ScriptCs && info.ScriptCs.Path) { + /*else if (info.Script && info.ScriptCs.Path) { label = basename(info.ScriptCs.Path); - } + }*/ this.SetAndShowStatusBar('$(file-directory) ' + label, 'o.pickProjectAndStart'); } From 8694e106d2d3f16769cb86d4403fbaa806d9ac82 Mon Sep 17 00:00:00 2001 From: Akshita Agarwal Date: Wed, 4 Apr 2018 16:04:09 -0700 Subject: [PATCH 44/56] Project Status Bar --- src/observers/ProjectStatusBarObserver.ts | 12 +++------ .../logging/ProjectStatusBarObserver.test.ts | 27 +++++++++---------- 2 files changed, 17 insertions(+), 22 deletions(-) diff --git a/src/observers/ProjectStatusBarObserver.ts b/src/observers/ProjectStatusBarObserver.ts index 2646a65047..fd37b7b20e 100644 --- a/src/observers/ProjectStatusBarObserver.ts +++ b/src/observers/ProjectStatusBarObserver.ts @@ -23,18 +23,14 @@ export class ProjectStatusBarObserver extends BaseStatusBarItemObserver { } private handleWorkspaceInformationUpdated(event: WorkspaceInformationUpdated) { - let label: string; let info = event.info; - - //Search for a way to check for the cake and the script project if (info.MsBuild && info.MsBuild.SolutionPath) { label = basename(info.MsBuild.SolutionPath); //workspace.getRelativePath(info.MsBuild.SolutionPath); + this.SetAndShowStatusBar('$(file-directory) ' + label, 'o.pickProjectAndStart'); + } + else { + this.ResetAndHideStatusBar(); } - /*else if (info.Script && info.ScriptCs.Path) { - label = basename(info.ScriptCs.Path); - }*/ - - this.SetAndShowStatusBar('$(file-directory) ' + label, 'o.pickProjectAndStart'); } } \ No newline at end of file diff --git a/test/unitTests/logging/ProjectStatusBarObserver.test.ts b/test/unitTests/logging/ProjectStatusBarObserver.test.ts index 0f987e2f77..7b9ba51473 100644 --- a/test/unitTests/logging/ProjectStatusBarObserver.test.ts +++ b/test/unitTests/logging/ProjectStatusBarObserver.test.ts @@ -11,7 +11,7 @@ import { OmnisharpOnMultipleLaunchTargets, OmnisharpServerOnStop } from '../../. suite('ProjectStatusBarObserver', () => { suiteSetup(() => should()); - + let output = ''; let showCalled: boolean; let hideCalled: boolean; @@ -44,22 +44,21 @@ suite('ProjectStatusBarObserver', () => { expect(statusBarItem.command).to.equal('o.pickProjectAndStart'); }); - // What to do of this test case here ?????? - - /*suite('WorkspaceInformationUpdated', () => { - test('Project status is shown', () => { + suite('WorkspaceInformationUpdated', () => { + test('Project status is hidden if there is no MSBuild Object', () => { let event = getWorkspaceInformationUpdated(null); observer.post(event); + expect(hideCalled).to.be.true; + expect(statusBarItem.text).to.be.undefined; + expect(statusBarItem.command).to.be.undefined; + }); + + test('Project status is shown if there is an MSBuild object', () => { + let event = getWorkspaceInformationUpdated(getMSBuildWorkspaceInformation("somePath", [])); + observer.post(event); expect(showCalled).to.be.true; - expect(statusBarItem.text).to.be.equal('$(flame) '); + expect(statusBarItem.text).to.contain(event.info.MsBuild.SolutionPath); expect(statusBarItem.command).to.equal('o.pickProjectAndStart'); - });*/ - - test('Project status is shown', () => { - let event = getWorkspaceInformationUpdated(getMSBuildWorkspaceInformation("somePath", [])); - observer.post(event); - expect(showCalled).to.be.true; - expect(statusBarItem.text).to.contain(event.info.MsBuild.SolutionPath); - expect(statusBarItem.command).to.equal('o.pickProjectAndStart'); + }); }); }); \ No newline at end of file From 731053b736819c25a9d8d0a72584fb46fb83edd3 Mon Sep 17 00:00:00 2001 From: Unknown Date: Wed, 4 Apr 2018 16:38:38 -0700 Subject: [PATCH 45/56] Clean up the tests --- .../InformationMessageObserver.test.ts | 121 +++++++++--------- .../logging/WarningMessageObserver.test.ts | 2 +- 2 files changed, 58 insertions(+), 65 deletions(-) diff --git a/test/unitTests/logging/InformationMessageObserver.test.ts b/test/unitTests/logging/InformationMessageObserver.test.ts index 3315e05e32..a80eefed44 100644 --- a/test/unitTests/logging/InformationMessageObserver.test.ts +++ b/test/unitTests/logging/InformationMessageObserver.test.ts @@ -13,7 +13,7 @@ chaiUse(require('chai-as-promised')); chaiUse(require('chai-string')); suite("InformationMessageObserver", () => { - suiteSetup(() => should()); + suiteSetup(() => should()); let doClickOk: () => void; let doClickCancel: () => void; @@ -25,7 +25,7 @@ suite("InformationMessageObserver", () => { let infoMessage; let relativePath; let invokedCommand; - let observer: InformationMessageObserver = new InformationMessageObserver(vscode); + let observer: InformationMessageObserver = new InformationMessageObserver(vscode); vscode.window.showInformationMessage = (message: string, ...items: string[]) => { infoMessage = message; @@ -46,12 +46,11 @@ suite("InformationMessageObserver", () => { return undefined; }; - vscode.workspace.asRelativePath = (pathOrUri?: string | Uri, includeWorspaceFolder? : boolean) => { + vscode.workspace.asRelativePath = (pathOrUri?: string | Uri, includeWorspaceFolder?: boolean) => { relativePath = pathOrUri; return relativePath; }; - setup(() => { infoMessage = undefined; relativePath = undefined; @@ -61,67 +60,61 @@ suite("InformationMessageObserver", () => { }); }); - test('If suppress dotnet configuration is set to true, the information message is not shown', () => { - let event = getUnresolvedDependenices("someFile"); - vscode.workspace.getConfiguration = (section?: string, resource?: Uri) => { - return { - ...getNullWorkspaceConfiguration(), - get: (section: string) => { - return true;// suppress the restore information - }}; - }; - observer.post(event); - expect(infoMessage).to.be.undefined; - }); - - test('If suppress dotnet configuration is set to false, the information message is shown', async () => { + suite('OmnisharpServerUnresolvedDependencies', () => { let event = getUnresolvedDependenices("someFile"); - vscode.workspace.getConfiguration = (section?: string, resource?: Uri) => { - return { - ...getNullWorkspaceConfiguration(), - get: (section: string) => { - return false; // do not suppress the restore info - }}; - }; - observer.post(event); - expect(relativePath).to.not.be.empty; - expect(infoMessage).to.not.be.empty; - doClickOk(); - await commandDone; - expect(invokedCommand).to.be.equal('dotnet.restore'); - }); - - test('Given an information message if the user clicks Restore, the command is executed', async () => { - let event = getUnresolvedDependenices("someFile"); - vscode.workspace.getConfiguration = (section?: string, resource?: Uri) => { - return { - ...getNullWorkspaceConfiguration(), - get: (section: string) => { - return false; // do not suppress the restore info - }}; - }; - - observer.post(event); - doClickOk(); - await commandDone; - expect(invokedCommand).to.be.equal('dotnet.restore'); - }); - - - test('Given an information message if the user clicks cancel, the command is not executed', async () => { - let event = getUnresolvedDependenices("someFile"); - vscode.workspace.getConfiguration = (section?: string, resource?: Uri) => { - return { - ...getNullWorkspaceConfiguration(), - get: (section: string) => { - return false; // do not suppress the restore info - }}; - }; - - observer.post(event); - doClickCancel(); - await expect(rx.Observable.fromPromise(commandDone).timeout(1).toPromise()).to.be.rejected; - expect(invokedCommand).to.be.undefined; + suite('Suppress Dotnet Restore Notification is true', () => { + setup(() => { + vscode.workspace.getConfiguration = (section?: string, resource?: Uri) => { + return { + ...getNullWorkspaceConfiguration(), + get: (section: string) => { + return true;// suppress the restore information + } + }; + }; + }); + + test('The information message is not shown', () => { + observer.post(event); + expect(infoMessage).to.be.undefined; + }); + }); + + suite('Suppress Dotnet Restore Notification is false', () => { + setup(() => { + vscode.workspace.getConfiguration = (section?: string, resource?: Uri) => { + return { + ...getNullWorkspaceConfiguration(), + get: (section: string) => { + return false; // do not suppress the restore info + } + }; + }; + }); + + test('The information message is shown', async () => { + observer.post(event); + expect(relativePath).to.not.be.empty; + expect(infoMessage).to.not.be.empty; + doClickOk(); + await commandDone; + expect(invokedCommand).to.be.equal('dotnet.restore'); + }); + + test('Given an information message if the user clicks Restore, the command is executed', async () => { + observer.post(event); + doClickOk(); + await commandDone; + expect(invokedCommand).to.be.equal('dotnet.restore'); + }); + + test('Given an information message if the user clicks cancel, the command is not executed', async () => { + observer.post(event); + doClickCancel(); + await expect(rx.Observable.fromPromise(commandDone).timeout(1).toPromise()).to.be.rejected; + expect(invokedCommand).to.be.undefined; + }); + }); }); }); \ No newline at end of file diff --git a/test/unitTests/logging/WarningMessageObserver.test.ts b/test/unitTests/logging/WarningMessageObserver.test.ts index d336fc18f2..203c3ea54a 100644 --- a/test/unitTests/logging/WarningMessageObserver.test.ts +++ b/test/unitTests/logging/WarningMessageObserver.test.ts @@ -14,7 +14,7 @@ import { Observable } from 'rx'; chaiUse(require('chai-as-promised')); chaiUse(require('chai-string')); -suite('OmnisharpServerStatusObserver', () => { +suite('WarningMessageObserver', () => { suiteSetup(() => should()); let doClickOk: () => void; From dd76b08237b601b6249b1cc6519a232c1f2f4fc9 Mon Sep 17 00:00:00 2001 From: Akshita Agarwal Date: Wed, 4 Apr 2018 17:58:55 -0700 Subject: [PATCH 46/56] Changed to dcoument --- src/vscodeAdapter.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vscodeAdapter.ts b/src/vscodeAdapter.ts index a7283c3141..1c870ba028 100644 --- a/src/vscodeAdapter.ts +++ b/src/vscodeAdapter.ts @@ -297,8 +297,9 @@ export interface TextEditor { /** * The document associated with this text editor. The document will be the same for the entire lifetime of this text editor. */ - document: any; + document: TextDocument; } + /** * A universal resource identifier representing either a file on disk * or another resource, like untitled resources. From 2ee30a0875adb0f8730cbbed7fd75605b5f5eae0 Mon Sep 17 00:00:00 2001 From: Akshita Agarwal Date: Wed, 4 Apr 2018 18:54:30 -0700 Subject: [PATCH 47/56] Remove unnecessary functions from the adapter --- src/vscodeAdapter.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/vscodeAdapter.ts b/src/vscodeAdapter.ts index 1c870ba028..7ab5d97bbf 100644 --- a/src/vscodeAdapter.ts +++ b/src/vscodeAdapter.ts @@ -313,7 +313,6 @@ export interface Uri { * @param path A file system or UNC path. * @return A new Uri instance. */ - file(path: string): Uri; /** * Create an URI from a string. Will throw if the given value is not @@ -322,8 +321,6 @@ export interface Uri { * @param value The string value of an Uri. * @return A new Uri instance. */ - parse(value: string): Uri; - /** * Scheme is the `http` part of `http://www.msft.com/some/path?query#fragment`. * The part before the first colon. @@ -600,7 +597,6 @@ export interface Position { * @param line A zero-based line value. * @param character A zero-based character value. */ - constructor(line: number, character: number); /** * Check if `other` is before this position. From 24d6b0c4350b2bb0a41c948295088d16e7ec386a Mon Sep 17 00:00:00 2001 From: Akshita Agarwal Date: Wed, 4 Apr 2018 18:58:12 -0700 Subject: [PATCH 48/56] remove unnecessary change --- src/observers/TelemetryObserver.ts | 169 ++++++++++++++++------------- 1 file changed, 95 insertions(+), 74 deletions(-) diff --git a/src/observers/TelemetryObserver.ts b/src/observers/TelemetryObserver.ts index 0f86b48a93..868b2eb8c7 100644 --- a/src/observers/TelemetryObserver.ts +++ b/src/observers/TelemetryObserver.ts @@ -2,88 +2,109 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { should, expect } from 'chai'; +import { getNullTelemetryReporter } from './Fakes'; +import { TelemetryObserver } from '../../../src/observers/TelemetryObserver'; +import { PlatformInformation } from '../../../src/platform'; +import { PackageInstallation, InstallationFailure, InstallationSuccess, TestExecutionCountReport, TelemetryEventWithMeasures, OmnisharpDelayTrackerEventMeasures, OmnisharpStart } from '../../../src/omnisharp/loggingEvents'; +import { PackageError, Package } from '../../../src/packages'; -import { PackageError } from "../packages"; -import { PlatformInformation } from "../platform"; -import { BaseEvent, PackageInstallation, InstallationFailure, InstallationSuccess, OmnisharpDelayTrackerEventMeasures, OmnisharpStart, TestExecutionCountReport, TelemetryEventWithMeasures } from "../omnisharp/loggingEvents"; +const chai = require('chai'); +chai.use(require('chai-arrays')); -export interface ITelemetryReporter { - sendTelemetryEvent(eventName: string, properties?: { [key: string]: string }, measures?: { [key: string]: number }): void; -} - -export class TelemetryObserver { - private reporter: ITelemetryReporter; - private platformInfo: PlatformInformation; - - constructor(platformInfo: PlatformInformation, reporterCreator: () => ITelemetryReporter) { - this.platformInfo = platformInfo; - this.reporter = reporterCreator(); - } - - public post = (event: BaseEvent) => { - let telemetryProps = this.getTelemetryProps(); - switch (event.constructor.name) { - case PackageInstallation.name: - this.reporter.sendTelemetryEvent("AcquisitionStart"); - break; - case InstallationFailure.name: - this.handleInstallationFailure(event, telemetryProps); - break; - case InstallationSuccess.name: - this.handleInstallationSuccess(telemetryProps); - break; - case OmnisharpDelayTrackerEventMeasures.name: - case OmnisharpStart.name: - this.handleTelemetryEventMeasures(event); - break; - case TestExecutionCountReport.name: - this.handleTestExecutionCountReport(event); - break; - } - } +suite('TelemetryReporterObserver', () => { + suiteSetup(() => should()); + let platformInfo = new PlatformInformation("platform", "architecture"); + let name = ""; + let property = null; + let measure = []; + let observer = new TelemetryObserver(platformInfo, () => { + return { + ...getNullTelemetryReporter, + sendTelemetryEvent: (eventName: string, properties?: { [key: string]: string }, measures?: { [key: string]: number }) => { + name += eventName; + property = properties; + measure.push(measures); + } + }; + }); - private handleTelemetryEventMeasures(event: TelemetryEventWithMeasures) { - this.reporter.sendTelemetryEvent(event.eventName, null, event.measures); - } + setup(() => { + name = ""; + property = null; + measure = []; + }); - private handleInstallationSuccess(telemetryProps: any) { - telemetryProps['installStage'] = 'completeSuccess'; - this.reporter.sendTelemetryEvent('Acquisition', telemetryProps); - } + test('PackageInstallation: AcquisitionStart is reported', () => { + let event = new PackageInstallation("somePackage"); + observer.post(event); + expect(name).to.be.not.empty; + }); - private handleInstallationFailure(event: InstallationFailure, telemetryProps: any) { - telemetryProps['installStage'] = event.stage; - if (event.error instanceof PackageError) { - // we can log the message in a PackageError to telemetry as we do not put PII in PackageError messages - telemetryProps['error.message'] = event.error.message; + test("InstallationFailure: Telemetry Props contains platform information, install stage and an event name", () => { + let event = new InstallationFailure("someStage", "someError"); + observer.post(event); + expect(property).to.have.property("platform.architecture", platformInfo.architecture); + expect(property).to.have.property("platform.platform", platformInfo.platform); + expect(property).to.have.property("installStage"); + expect(name).to.not.be.empty; + }); - if (event.error.pkg) { - telemetryProps['error.packageUrl'] = event.error.pkg.url; - } - } + test(`InstallationFailure: Telemetry Props contains message and packageUrl if error is package error`, () => { + let error = new PackageError("someError", { "description": "foo", "url": "someurl" }); + let event = new InstallationFailure("someStage", error); + observer.post(event); + expect(property).to.have.property("error.message", error.message); + expect(property).to.have.property("error.packageUrl", error.pkg.url); + }); - this.reporter.sendTelemetryEvent('Acquisition', telemetryProps); - } + test('InstallationSuccess: Telemetry props contain installation stage', () => { + let event = new InstallationSuccess(); + observer.post(event); + expect(name).to.be.equal("Acquisition"); + expect(property).to.have.property("installStage", "completeSuccess"); + }); - private handleTestExecutionCountReport(event: TestExecutionCountReport) { - if (event.debugCounts) { - this.reporter.sendTelemetryEvent('DebugTest', null, event.debugCounts); - } - if (event.runCounts) { - this.reporter.sendTelemetryEvent('RunTest', null, event.runCounts); - } - } + test('TestExecutionCountReport: SendTelemetryEvent is called for "RunTest" and "DebugTest"', () => { + let event = new TestExecutionCountReport({ "framework1": 20 }, { "framework2": 30 }); + observer.post(event); + expect(name).to.contain("RunTest"); + expect(name).to.contain("DebugTest"); + expect(measure).to.be.containingAllOf([event.debugCounts, event.runCounts]); + }); - private getTelemetryProps() { - let telemetryProps = { - 'platform.architecture': this.platformInfo.architecture, - 'platform.platform': this.platformInfo.platform - }; + test('TestExecutionCountReport: SendTelemetryEvent is not called for empty run count', () => { + let event = new TestExecutionCountReport({ "framework1": 20 }, null); + observer.post(event); + expect(name).to.not.contain("RunTest"); + expect(name).to.contain("DebugTest"); + expect(measure).to.be.containingAllOf([event.debugCounts]); + }); - if (this.platformInfo.distribution) { - telemetryProps['platform.distribution'] = this.platformInfo.distribution.toTelemetryString(); - } + test('TestExecutionCountReport: SendTelemetryEvent is not called for empty debug count', () => { + let event = new TestExecutionCountReport(null, { "framework1": 20 }); + observer.post(event); + expect(name).to.contain("RunTest"); + expect(name).to.not.contain("DebugTest"); + expect(measure).to.be.containingAllOf([event.runCounts]); + }); - return telemetryProps; - } -} + test('TestExecutionCountReport: SendTelemetryEvent is not called for empty debug and run counts', () => { + let event = new TestExecutionCountReport(null, null); + observer.post(event); + expect(name).to.be.empty; + expect(measure).to.be.empty; + }); + + [ + new OmnisharpDelayTrackerEventMeasures("someEvent", { someKey: 1 }), + new OmnisharpStart("startEvent", { someOtherKey: 2 }) + ].forEach((event: TelemetryEventWithMeasures) => { + test(`${event.constructor.name}`, () => { + observer.post(event); + expect(name).to.contain(event.eventName); + expect(measure).to.be.containingAllOf([event.measures]); + }); + }); + +}); From d4ba6be0711dc6bea66aed9439d893326758d275 Mon Sep 17 00:00:00 2001 From: Unknown Date: Wed, 4 Apr 2018 19:34:29 -0700 Subject: [PATCH 49/56] Remove some changes --- .../logging/CsharpLoggerObserver.test.ts | 15 +- .../logging/OmnisharpLoggerObserver.test.ts | 160 +++++++++--------- .../logging/TelemetryObserver.test.ts | 92 +++++----- 3 files changed, 131 insertions(+), 136 deletions(-) diff --git a/test/unitTests/logging/CsharpLoggerObserver.test.ts b/test/unitTests/logging/CsharpLoggerObserver.test.ts index 3efbc2d7ba..f0611272a8 100644 --- a/test/unitTests/logging/CsharpLoggerObserver.test.ts +++ b/test/unitTests/logging/CsharpLoggerObserver.test.ts @@ -85,21 +85,21 @@ suite('CsharpLoggerObsever', () => { expect(logOutput).to.contain("MyArchitecture"); }); - test('InstallationFailure: Stage and Error is logged if not a PackageError', () => { + test('Event.InstallationFailure: Stage and Error is logged if not a PackageError', () => { let event = new Event.InstallationFailure("someStage", new Error("someError")); observer.post(event); expect(logOutput).to.contain(event.stage); expect(logOutput).to.contain(event.error.toString()); }); - test('InstallationFailure: Stage and Error is logged if a PackageError without inner error', () => { + test('Event.InstallationFailure: Stage and Error is logged if a PackageError without inner error', () => { let event = new Event.InstallationFailure("someStage", new PackageError("someError", null, null)); observer.post(event); expect(logOutput).to.contain(event.stage); expect(logOutput).to.contain(event.error.message); }); - test('InstallationFailure: Stage and Inner error is logged if a PackageError without inner error', () => { + test('Event.InstallationFailure: Stage and Inner error is logged if a PackageError without inner error', () => { let event = new Event.InstallationFailure("someStage", new PackageError("someError", null, "innerError")); observer.post(event); expect(logOutput).to.contain(event.stage); @@ -122,25 +122,25 @@ suite('CsharpLoggerObsever', () => { expect(logOutput).to.contain(element.expected); })); - test(`ActivaltionFailure: Some message is logged`, () => { + test(`Event.ActivaltionFailure: Some message is logged`, () => { let event = new Event.ActivationFailure(); observer.post(event); expect(logOutput).to.not.be.empty; }); - test(`ProjectJsonDeprecatedWarning: Some message is logged`, () => { + test(`Event.ProjectJsonDeprecatedWarning: Some message is logged`, () => { let event = new Event.ProjectJsonDeprecatedWarning(); observer.post(event); expect(logOutput).to.not.be.empty; }); - test(`ProjectJsonDeprecatedWarning: Some message is logged`, () => { + test(`Event.ProjectJsonDeprecatedWarning: Some message is logged`, () => { let event = new Event.InstallationSuccess(); observer.post(event); expect(logOutput).to.not.be.empty; }); - test(`InstallationProgress: Progress message is logged`, () => { + test(`Event.InstallationProgress: Progress message is logged`, () => { let event = new Event.InstallationProgress("someStage", "someMessage"); observer.post(event); expect(logOutput).to.contain(event.message); @@ -151,4 +151,5 @@ suite('CsharpLoggerObsever', () => { observer.post(event); expect(logOutput).to.contain(event.packageInfo); }); + }); diff --git a/test/unitTests/logging/OmnisharpLoggerObserver.test.ts b/test/unitTests/logging/OmnisharpLoggerObserver.test.ts index 3602717b44..1d8e20eebb 100644 --- a/test/unitTests/logging/OmnisharpLoggerObserver.test.ts +++ b/test/unitTests/logging/OmnisharpLoggerObserver.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { should, expect } from 'chai'; -import { getNullChannel, getMSBuildDiagnosticsMessage, getOmnisharpMSBuildProjectDiagnosticsEvent } from './Fakes'; +import { getNullChannel } from './Fakes'; import { OmnisharpLoggerObserver } from '../../../src/observers/OmnisharpLoggerObserver'; import { OmnisharpServerMsBuildProjectDiagnostics, EventWithMessage, OmnisharpServerOnStdErr, OmnisharpServerMessage, OmnisharpServerOnServerError, OmnisharpInitialisation, OmnisharpLaunch, OmnisharpServerOnError, OmnisharpFailure, OmnisharpEventPacketReceived } from '../../../src/omnisharp/loggingEvents'; import { MSBuildDiagnosticsMessage } from '../../../src/omnisharp/protocol'; @@ -21,44 +21,53 @@ suite("OmnisharpLoggerObserver", () => { logOutput = ""; }); - suite('OmnisharpServerMsBuildProjectDiagnostics', () => { - test(`Logged message contains the Filename if there is atleast one error or warning`, () => { - let event = getOmnisharpMSBuildProjectDiagnosticsEvent("someFile", - [getMSBuildDiagnosticsMessage("warningFile", "", "", 0, 0, 0, 0)], - []); + [ + new OmnisharpServerMsBuildProjectDiagnostics({ + FileName: "someFile", + Warnings: [{ FileName: "warningFile", LogLevel: "", Text: "", StartLine: 0, EndLine: 0, StartColumn: 0, EndColumn: 0 }], + Errors: [] + }) + ].forEach((event: OmnisharpServerMsBuildProjectDiagnostics) => { + test(`${event.constructor.name}: Logged message contains the Filename if there is atleast one error or warning`, () => { observer.post(event); expect(logOutput).to.contain(event.diagnostics.FileName); }); + }); - test("Logged message is empty if there are no warnings and erros", () => { - let event = getOmnisharpMSBuildProjectDiagnosticsEvent("someFile", [], []); - observer.post(event); - expect(logOutput).to.be.empty; + test("OmnisharpServerMsBuildProjectDiagnostics: Logged message is empty if there are no warnings and erros", () => { + let event = new OmnisharpServerMsBuildProjectDiagnostics({ + FileName: "someFile", + Warnings: [], + Errors: [] }); + observer.post(event); + expect(logOutput).to.be.empty; + }); - [ - getOmnisharpMSBuildProjectDiagnosticsEvent("someFile", - [getMSBuildDiagnosticsMessage("warningFile", "", "someWarningText", 1, 2, 3, 4)], - [getMSBuildDiagnosticsMessage("errorFile", "", "someErrorText", 5, 6, 7, 8)]) - ].forEach((event: OmnisharpServerMsBuildProjectDiagnostics) => { - test(`Logged message contains the Filename, StartColumn, StartLine and Text for the diagnostic warnings`, () => { - observer.post(event); - event.diagnostics.Warnings.forEach(element => { - expect(logOutput).to.contain(element.FileName); - expect(logOutput).to.contain(element.StartLine); - expect(logOutput).to.contain(element.StartColumn); - expect(logOutput).to.contain(element.Text); - }); + [ + new OmnisharpServerMsBuildProjectDiagnostics({ + FileName: "someFile", + Warnings: [{ FileName: "warningFile", LogLevel: "", Text: "someWarningText", StartLine: 1, EndLine: 2, StartColumn: 3, EndColumn: 4 }], + Errors: [{ FileName: "errorFile", LogLevel: "", Text: "someErrorText", StartLine: 5, EndLine: 6, StartColumn: 7, EndColumn: 8 }] + }) + ].forEach((event: OmnisharpServerMsBuildProjectDiagnostics) => { + test(`${event.constructor.name}: Logged message contains the Filename, StartColumn, StartLine and Text for the diagnostic warnings`, () => { + observer.post(event); + event.diagnostics.Warnings.forEach(element => { + expect(logOutput).to.contain(element.FileName); + expect(logOutput).to.contain(element.StartLine); + expect(logOutput).to.contain(element.StartColumn); + expect(logOutput).to.contain(element.Text); }); + }); - test(`Logged message contains the Filename, StartColumn, StartLine and Text for the diagnostics errors`, () => { - observer.post(event); - event.diagnostics.Errors.forEach(element => { - expect(logOutput).to.contain(element.FileName); - expect(logOutput).to.contain(element.StartLine); - expect(logOutput).to.contain(element.StartColumn); - expect(logOutput).to.contain(element.Text); - }); + test(`${event.constructor.name}: Logged message contains the Filename, StartColumn, StartLine and Text for the diagnostics errors`, () => { + observer.post(event); + event.diagnostics.Errors.forEach(element => { + expect(logOutput).to.contain(element.FileName); + expect(logOutput).to.contain(element.StartLine); + expect(logOutput).to.contain(element.StartColumn); + expect(logOutput).to.contain(element.Text); }); }); }); @@ -66,6 +75,7 @@ suite("OmnisharpLoggerObserver", () => { [ new OmnisharpServerOnStdErr("on std error message"), new OmnisharpServerMessage("server message"), + new OmnisharpServerOnServerError("on server error message"), ].forEach((event: EventWithMessage) => { test(`${event.constructor.name}: Message is logged`, () => { observer.post(event); @@ -73,12 +83,6 @@ suite("OmnisharpLoggerObserver", () => { }); }); - test(`OmnisharpServerOnServerError: Error is logged`, () => { - let event = new OmnisharpServerOnServerError("on server error message"); - observer.post(event); - expect(logOutput).to.contain(event.err); - }); - [ new OmnisharpInitialisation(new Date(5), "somePath"), ].forEach((event: OmnisharpInitialisation) => { @@ -109,31 +113,29 @@ suite("OmnisharpLoggerObserver", () => { }); }); - suite('OmnisharpServerOnError', () => { - [ - new OmnisharpServerOnError({ Text: "someText", FileName: "someFile", Line: 1, Column: 2 }), - ].forEach((event: OmnisharpServerOnError) => { - test('Contains the error message text', () => { - observer.post(event); - expect(logOutput).to.contain(event.errorMessage.Text); - }); + [ + new OmnisharpServerOnError({ Text: "someText", FileName: "someFile", Line: 1, Column: 2 }), + ].forEach((event: OmnisharpServerOnError) => { + test(`${event.constructor.name}: Contains the error message text`, () => { + observer.post(event); + expect(logOutput).to.contain(event.errorMessage.Text); + }); - test(`Contains the error message FileName, Line and column if FileName is not null`, () => { - observer.post(event); + test(`${event.constructor.name}: Contains the error message FileName, Line and column if FileName is not null`, () => { + observer.post(event); + if (event.errorMessage.FileName) { expect(logOutput).to.contain(event.errorMessage.FileName); expect(logOutput).to.contain(event.errorMessage.Line); expect(logOutput).to.contain(event.errorMessage.Column); - }); - }); - - test(`Doesnot throw error if FileName is null`, () => { - let event = new OmnisharpServerOnError({ Text: "someText", FileName: null, Line: 1, Column: 2 }); - let fn = function () { observer.post(event); }; - expect(fn).to.not.throw(Error); + } }); }); - + test(`OmnisharpServerOnError: Doesnot throw error if FileName is null`, () => { + let event = new OmnisharpServerOnError({ Text: "someText", FileName: null, Line: 1, Column: 2 }); + let fn = function () { observer.post(event); }; + expect(fn).to.not.throw(Error); + }); test('OmnisharpFailure: Failure message is logged', () => { let event = new OmnisharpFailure("failureMessage", new Error("errorMessage")); @@ -141,34 +143,30 @@ suite("OmnisharpLoggerObserver", () => { expect(logOutput).to.contain(event.message); }); - suite('OmnisharpEventPacketReceived', () => { - [ - new OmnisharpEventPacketReceived("TRACE", "foo", "someMessage"), - new OmnisharpEventPacketReceived("DEBUG", "foo", "someMessage"), - new OmnisharpEventPacketReceived("INFORMATION", "foo", "someMessage"), - new OmnisharpEventPacketReceived("WARNING", "foo", "someMessage"), - new OmnisharpEventPacketReceived("ERROR", "foo", "someMessage"), - new OmnisharpEventPacketReceived("CRITICAL", "foo", "someMessage"), - ].forEach((event: OmnisharpEventPacketReceived) => { - test(`${event.logLevel} messages are logged with name and the message`, () => { - observer.post(event); - expect(logOutput).to.contain(event.name); - expect(logOutput).to.contain(event.message); - }); - }); - - test('Throws error on unknown log level', () => { - let event = new OmnisharpEventPacketReceived("random log level", "foo", "someMessage"); - let fn = function () { observer.post(event); }; - expect(fn).to.throw(Error); - }); - - test(`Information messages with name OmniSharp.Middleware.LoggingMiddleware and follow pattern /^\/[\/\w]+: 200 \d+ms/ are not logged`, () => { - let event = new OmnisharpEventPacketReceived("INFORMATION", "OmniSharp.Middleware.LoggingMiddleware", "/codecheck: 200 339ms"); + [ + new OmnisharpEventPacketReceived("TRACE", "foo", "someMessage"), + new OmnisharpEventPacketReceived("DEBUG", "foo", "someMessage"), + new OmnisharpEventPacketReceived("INFORMATION", "foo", "someMessage"), + new OmnisharpEventPacketReceived("WARNING", "foo", "someMessage"), + new OmnisharpEventPacketReceived("ERROR", "foo", "someMessage"), + new OmnisharpEventPacketReceived("CRITICAL", "foo", "someMessage"), + ].forEach((event: OmnisharpEventPacketReceived) => { + test(`OmnisharpEventPacketReceived: ${event.logLevel} messages are logged with name and the message`, () => { observer.post(event); - expect(logOutput).to.be.empty; + expect(logOutput).to.contain(event.name); + expect(logOutput).to.contain(event.message); }); }); - + test('OmnisharpEventPacketReceived: Throws error on unknown log level', () => { + let event = new OmnisharpEventPacketReceived("random log level", "foo", "someMessage"); + let fn = function () { observer.post(event); }; + expect(fn).to.throw(Error); + }); + + test(`OmnisharpEventPacketReceived: Information messages with name OmniSharp.Middleware.LoggingMiddleware and follow pattern /^\/[\/\w]+: 200 \d+ms/ are not logged`, () => { + let event = new OmnisharpEventPacketReceived("INFORMATION", "OmniSharp.Middleware.LoggingMiddleware", "/codecheck: 200 339ms"); + observer.post(event); + expect(logOutput).to.be.empty; + }); }); diff --git a/test/unitTests/logging/TelemetryObserver.test.ts b/test/unitTests/logging/TelemetryObserver.test.ts index 97513269e0..868b2eb8c7 100644 --- a/test/unitTests/logging/TelemetryObserver.test.ts +++ b/test/unitTests/logging/TelemetryObserver.test.ts @@ -41,23 +41,21 @@ suite('TelemetryReporterObserver', () => { expect(name).to.be.not.empty; }); - suite('InstallationFailure', () => { - test("Telemetry Props contains platform information, install stage and an event name", () => { - let event = new InstallationFailure("someStage", "someError"); - observer.post(event); - expect(property).to.have.property("platform.architecture", platformInfo.architecture); - expect(property).to.have.property("platform.platform", platformInfo.platform); - expect(property).to.have.property("installStage"); - expect(name).to.not.be.empty; - }); - - test(`Telemetry Props contains message and packageUrl if error is package error`, () => { - let error = new PackageError("someError", { "description": "foo", "url": "someurl" }); - let event = new InstallationFailure("someStage", error); - observer.post(event); - expect(property).to.have.property("error.message", error.message); - expect(property).to.have.property("error.packageUrl", error.pkg.url); - }); + test("InstallationFailure: Telemetry Props contains platform information, install stage and an event name", () => { + let event = new InstallationFailure("someStage", "someError"); + observer.post(event); + expect(property).to.have.property("platform.architecture", platformInfo.architecture); + expect(property).to.have.property("platform.platform", platformInfo.platform); + expect(property).to.have.property("installStage"); + expect(name).to.not.be.empty; + }); + + test(`InstallationFailure: Telemetry Props contains message and packageUrl if error is package error`, () => { + let error = new PackageError("someError", { "description": "foo", "url": "someurl" }); + let event = new InstallationFailure("someStage", error); + observer.post(event); + expect(property).to.have.property("error.message", error.message); + expect(property).to.have.property("error.packageUrl", error.pkg.url); }); test('InstallationSuccess: Telemetry props contain installation stage', () => { @@ -67,39 +65,37 @@ suite('TelemetryReporterObserver', () => { expect(property).to.have.property("installStage", "completeSuccess"); }); - suite('TestCountExecutionReport', () => { - test('SendTelemetryEvent is called for "RunTest" and "DebugTest"', () => { - let event = new TestExecutionCountReport({ "framework1": 20 }, { "framework2": 30 }); - observer.post(event); - expect(name).to.contain("RunTest"); - expect(name).to.contain("DebugTest"); - expect(measure).to.be.containingAllOf([event.debugCounts, event.runCounts]); - }); - - test('SendTelemetryEvent is not called for empty run count', () => { - let event = new TestExecutionCountReport({ "framework1": 20 }, null); - observer.post(event); - expect(name).to.not.contain("RunTest"); - expect(name).to.contain("DebugTest"); - expect(measure).to.be.containingAllOf([event.debugCounts]); - }); - - test('SendTelemetryEvent is not called for empty debug count', () => { - let event = new TestExecutionCountReport(null, { "framework1": 20 }); - observer.post(event); - expect(name).to.contain("RunTest"); - expect(name).to.not.contain("DebugTest"); - expect(measure).to.be.containingAllOf([event.runCounts]); - }); - - test('SendTelemetryEvent is not called for empty debug and run counts', () => { - let event = new TestExecutionCountReport(null, null); - observer.post(event); - expect(name).to.be.empty; - expect(measure).to.be.empty; - }); + test('TestExecutionCountReport: SendTelemetryEvent is called for "RunTest" and "DebugTest"', () => { + let event = new TestExecutionCountReport({ "framework1": 20 }, { "framework2": 30 }); + observer.post(event); + expect(name).to.contain("RunTest"); + expect(name).to.contain("DebugTest"); + expect(measure).to.be.containingAllOf([event.debugCounts, event.runCounts]); + }); + + test('TestExecutionCountReport: SendTelemetryEvent is not called for empty run count', () => { + let event = new TestExecutionCountReport({ "framework1": 20 }, null); + observer.post(event); + expect(name).to.not.contain("RunTest"); + expect(name).to.contain("DebugTest"); + expect(measure).to.be.containingAllOf([event.debugCounts]); + }); + + test('TestExecutionCountReport: SendTelemetryEvent is not called for empty debug count', () => { + let event = new TestExecutionCountReport(null, { "framework1": 20 }); + observer.post(event); + expect(name).to.contain("RunTest"); + expect(name).to.not.contain("DebugTest"); + expect(measure).to.be.containingAllOf([event.runCounts]); }); + test('TestExecutionCountReport: SendTelemetryEvent is not called for empty debug and run counts', () => { + let event = new TestExecutionCountReport(null, null); + observer.post(event); + expect(name).to.be.empty; + expect(measure).to.be.empty; + }); + [ new OmnisharpDelayTrackerEventMeasures("someEvent", { someKey: 1 }), new OmnisharpStart("startEvent", { someOtherKey: 2 }) From 72a2a1df3f837e254ce8365d988611312468be3f Mon Sep 17 00:00:00 2001 From: Akshita Agarwal Date: Wed, 4 Apr 2018 19:35:50 -0700 Subject: [PATCH 50/56] changes --- src/observers/TelemetryObserver.ts | 169 +++++++++++++---------------- 1 file changed, 74 insertions(+), 95 deletions(-) diff --git a/src/observers/TelemetryObserver.ts b/src/observers/TelemetryObserver.ts index 868b2eb8c7..0f86b48a93 100644 --- a/src/observers/TelemetryObserver.ts +++ b/src/observers/TelemetryObserver.ts @@ -2,109 +2,88 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { should, expect } from 'chai'; -import { getNullTelemetryReporter } from './Fakes'; -import { TelemetryObserver } from '../../../src/observers/TelemetryObserver'; -import { PlatformInformation } from '../../../src/platform'; -import { PackageInstallation, InstallationFailure, InstallationSuccess, TestExecutionCountReport, TelemetryEventWithMeasures, OmnisharpDelayTrackerEventMeasures, OmnisharpStart } from '../../../src/omnisharp/loggingEvents'; -import { PackageError, Package } from '../../../src/packages'; -const chai = require('chai'); -chai.use(require('chai-arrays')); +import { PackageError } from "../packages"; +import { PlatformInformation } from "../platform"; +import { BaseEvent, PackageInstallation, InstallationFailure, InstallationSuccess, OmnisharpDelayTrackerEventMeasures, OmnisharpStart, TestExecutionCountReport, TelemetryEventWithMeasures } from "../omnisharp/loggingEvents"; -suite('TelemetryReporterObserver', () => { - suiteSetup(() => should()); - let platformInfo = new PlatformInformation("platform", "architecture"); - let name = ""; - let property = null; - let measure = []; - let observer = new TelemetryObserver(platformInfo, () => { - return { - ...getNullTelemetryReporter, - sendTelemetryEvent: (eventName: string, properties?: { [key: string]: string }, measures?: { [key: string]: number }) => { - name += eventName; - property = properties; - measure.push(measures); - } - }; - }); +export interface ITelemetryReporter { + sendTelemetryEvent(eventName: string, properties?: { [key: string]: string }, measures?: { [key: string]: number }): void; +} - setup(() => { - name = ""; - property = null; - measure = []; - }); +export class TelemetryObserver { + private reporter: ITelemetryReporter; + private platformInfo: PlatformInformation; - test('PackageInstallation: AcquisitionStart is reported', () => { - let event = new PackageInstallation("somePackage"); - observer.post(event); - expect(name).to.be.not.empty; - }); + constructor(platformInfo: PlatformInformation, reporterCreator: () => ITelemetryReporter) { + this.platformInfo = platformInfo; + this.reporter = reporterCreator(); + } - test("InstallationFailure: Telemetry Props contains platform information, install stage and an event name", () => { - let event = new InstallationFailure("someStage", "someError"); - observer.post(event); - expect(property).to.have.property("platform.architecture", platformInfo.architecture); - expect(property).to.have.property("platform.platform", platformInfo.platform); - expect(property).to.have.property("installStage"); - expect(name).to.not.be.empty; - }); + public post = (event: BaseEvent) => { + let telemetryProps = this.getTelemetryProps(); + switch (event.constructor.name) { + case PackageInstallation.name: + this.reporter.sendTelemetryEvent("AcquisitionStart"); + break; + case InstallationFailure.name: + this.handleInstallationFailure(event, telemetryProps); + break; + case InstallationSuccess.name: + this.handleInstallationSuccess(telemetryProps); + break; + case OmnisharpDelayTrackerEventMeasures.name: + case OmnisharpStart.name: + this.handleTelemetryEventMeasures(event); + break; + case TestExecutionCountReport.name: + this.handleTestExecutionCountReport(event); + break; + } + } - test(`InstallationFailure: Telemetry Props contains message and packageUrl if error is package error`, () => { - let error = new PackageError("someError", { "description": "foo", "url": "someurl" }); - let event = new InstallationFailure("someStage", error); - observer.post(event); - expect(property).to.have.property("error.message", error.message); - expect(property).to.have.property("error.packageUrl", error.pkg.url); - }); + private handleTelemetryEventMeasures(event: TelemetryEventWithMeasures) { + this.reporter.sendTelemetryEvent(event.eventName, null, event.measures); + } - test('InstallationSuccess: Telemetry props contain installation stage', () => { - let event = new InstallationSuccess(); - observer.post(event); - expect(name).to.be.equal("Acquisition"); - expect(property).to.have.property("installStage", "completeSuccess"); - }); + private handleInstallationSuccess(telemetryProps: any) { + telemetryProps['installStage'] = 'completeSuccess'; + this.reporter.sendTelemetryEvent('Acquisition', telemetryProps); + } - test('TestExecutionCountReport: SendTelemetryEvent is called for "RunTest" and "DebugTest"', () => { - let event = new TestExecutionCountReport({ "framework1": 20 }, { "framework2": 30 }); - observer.post(event); - expect(name).to.contain("RunTest"); - expect(name).to.contain("DebugTest"); - expect(measure).to.be.containingAllOf([event.debugCounts, event.runCounts]); - }); + private handleInstallationFailure(event: InstallationFailure, telemetryProps: any) { + telemetryProps['installStage'] = event.stage; + if (event.error instanceof PackageError) { + // we can log the message in a PackageError to telemetry as we do not put PII in PackageError messages + telemetryProps['error.message'] = event.error.message; + + if (event.error.pkg) { + telemetryProps['error.packageUrl'] = event.error.pkg.url; + } + } - test('TestExecutionCountReport: SendTelemetryEvent is not called for empty run count', () => { - let event = new TestExecutionCountReport({ "framework1": 20 }, null); - observer.post(event); - expect(name).to.not.contain("RunTest"); - expect(name).to.contain("DebugTest"); - expect(measure).to.be.containingAllOf([event.debugCounts]); - }); + this.reporter.sendTelemetryEvent('Acquisition', telemetryProps); + } + + private handleTestExecutionCountReport(event: TestExecutionCountReport) { + if (event.debugCounts) { + this.reporter.sendTelemetryEvent('DebugTest', null, event.debugCounts); + } + if (event.runCounts) { + this.reporter.sendTelemetryEvent('RunTest', null, event.runCounts); + } + } + + private getTelemetryProps() { + let telemetryProps = { + 'platform.architecture': this.platformInfo.architecture, + 'platform.platform': this.platformInfo.platform + }; - test('TestExecutionCountReport: SendTelemetryEvent is not called for empty debug count', () => { - let event = new TestExecutionCountReport(null, { "framework1": 20 }); - observer.post(event); - expect(name).to.contain("RunTest"); - expect(name).to.not.contain("DebugTest"); - expect(measure).to.be.containingAllOf([event.runCounts]); - }); + if (this.platformInfo.distribution) { + telemetryProps['platform.distribution'] = this.platformInfo.distribution.toTelemetryString(); + } - test('TestExecutionCountReport: SendTelemetryEvent is not called for empty debug and run counts', () => { - let event = new TestExecutionCountReport(null, null); - observer.post(event); - expect(name).to.be.empty; - expect(measure).to.be.empty; - }); - - [ - new OmnisharpDelayTrackerEventMeasures("someEvent", { someKey: 1 }), - new OmnisharpStart("startEvent", { someOtherKey: 2 }) - ].forEach((event: TelemetryEventWithMeasures) => { - test(`${event.constructor.name}`, () => { - observer.post(event); - expect(name).to.contain(event.eventName); - expect(measure).to.be.containingAllOf([event.measures]); - }); - }); - -}); + return telemetryProps; + } +} From 06c4f3d1b41912afd5b8b2502305839b0efb0791 Mon Sep 17 00:00:00 2001 From: Akshita Agarwal Date: Wed, 4 Apr 2018 19:52:07 -0700 Subject: [PATCH 51/56] Test for server error --- test/unitTests/logging/OmnisharpLoggerObserver.test.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/test/unitTests/logging/OmnisharpLoggerObserver.test.ts b/test/unitTests/logging/OmnisharpLoggerObserver.test.ts index 1d8e20eebb..a2514b61b1 100644 --- a/test/unitTests/logging/OmnisharpLoggerObserver.test.ts +++ b/test/unitTests/logging/OmnisharpLoggerObserver.test.ts @@ -75,7 +75,6 @@ suite("OmnisharpLoggerObserver", () => { [ new OmnisharpServerOnStdErr("on std error message"), new OmnisharpServerMessage("server message"), - new OmnisharpServerOnServerError("on server error message"), ].forEach((event: EventWithMessage) => { test(`${event.constructor.name}: Message is logged`, () => { observer.post(event); @@ -83,6 +82,13 @@ suite("OmnisharpLoggerObserver", () => { }); }); + test(`OmnisharpServerOnServerError: Message is logged`, () => { + let event = new OmnisharpServerOnServerError("on server error message"); + observer.post(event); + expect(logOutput).to.contain(event.err); + }); + + [ new OmnisharpInitialisation(new Date(5), "somePath"), ].forEach((event: OmnisharpInitialisation) => { From 9776f2a6d748ad727a2fdf2e21081a9662aac998 Mon Sep 17 00:00:00 2001 From: Unknown Date: Thu, 5 Apr 2018 10:37:52 -0700 Subject: [PATCH 52/56] Removed comment and modified the initialisation --- src/omnisharp/server.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/omnisharp/server.ts b/src/omnisharp/server.ts index 3eb4a7c169..561cccc404 100644 --- a/src/omnisharp/server.ts +++ b/src/omnisharp/server.ts @@ -85,7 +85,7 @@ export class OmniSharpServer { private _omnisharpManager: OmnisharpManager; private eventStream: EventStream; - private updateProjectDebouncer: Subject; + private updateProjectDebouncer = new Subject(); private firstUpdateProject: boolean; constructor(eventStream: EventStream, packageJSON: any, platformInfo: PlatformInformation) { @@ -93,7 +93,6 @@ export class OmniSharpServer { this._requestQueue = new RequestQueueCollection(this.eventStream, 8, request => this._makeRequest(request)); let downloader = new OmnisharpDownloader(this.eventStream, packageJSON, platformInfo); this._omnisharpManager = new OmnisharpManager(downloader, platformInfo); - this.updateProjectDebouncer = new Subject(); this.updateProjectDebouncer.debounce(1500).subscribe((event) => { this.updateProjectInfo(); }); this.firstUpdateProject = true; } @@ -342,7 +341,6 @@ export class OmniSharpServer { }); } - // we could move this to a project updated observer and pass the server there. But we will need the event stream also there :( private updateTracker = () => { if (this.firstUpdateProject) { this.updateProjectInfo(); From 8382368447f2ca7a6942d626ec29074bf578384e Mon Sep 17 00:00:00 2001 From: Unknown Date: Thu, 5 Apr 2018 12:53:14 -0700 Subject: [PATCH 53/56] Empty disposable --- src/omnisharp/server.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/omnisharp/server.ts b/src/omnisharp/server.ts index 561cccc404..59d6e0aec8 100644 --- a/src/omnisharp/server.ts +++ b/src/omnisharp/server.ts @@ -71,7 +71,7 @@ export class OmniSharpServer { private static _nextId = 1; private _readLine: ReadLine; - private _disposables: CompositeDisposable = new CompositeDisposable(Disposable.empty); + private _disposables: CompositeDisposable; private _delayTrackers: { [requestName: string]: DelayTracker }; private _telemetryIntervalId: NodeJS.Timer = undefined; From dfcd44a652403eacfa19d04a24758e0264fb1539 Mon Sep 17 00:00:00 2001 From: Unknown Date: Thu, 5 Apr 2018 14:52:51 -0700 Subject: [PATCH 54/56] Corrected the usage of the disposables --- src/omnisharp/server.ts | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/omnisharp/server.ts b/src/omnisharp/server.ts index 59d6e0aec8..f70668b7ec 100644 --- a/src/omnisharp/server.ts +++ b/src/omnisharp/server.ts @@ -237,54 +237,51 @@ export class OmniSharpServer { private async _start(launchTarget: LaunchTarget): Promise { - let disposables = new CompositeDisposable(); - disposables.add(this.onServerError(err => + this._disposables = new CompositeDisposable(); + + this._disposables.add(this.onServerError(err => this.eventStream.post(new ObservableEvents.OmnisharpServerOnServerError(err)) )); - disposables.add(this.onError((message: protocol.ErrorMessage) => + this._disposables.add(this.onError((message: protocol.ErrorMessage) => this.eventStream.post(new ObservableEvents.OmnisharpServerOnError(message)) )); - disposables.add(this.onMsBuildProjectDiagnostics((message: protocol.MSBuildProjectDiagnostics) => + this._disposables.add(this.onMsBuildProjectDiagnostics((message: protocol.MSBuildProjectDiagnostics) => this.eventStream.post(new ObservableEvents.OmnisharpServerMsBuildProjectDiagnostics(message)) )); - disposables.add(this.onUnresolvedDependencies((message: protocol.UnresolvedDependenciesMessage) => + this._disposables.add(this.onUnresolvedDependencies((message: protocol.UnresolvedDependenciesMessage) => this.eventStream.post(new ObservableEvents.OmnisharpServerUnresolvedDependencies(message)) )); - disposables.add(this.onStderr((message: string) => + this._disposables.add(this.onStderr((message: string) => this.eventStream.post(new ObservableEvents.OmnisharpServerOnStdErr(message)) )); - disposables.add(this.onMultipleLaunchTargets((targets: LaunchTarget[]) => + this._disposables.add(this.onMultipleLaunchTargets((targets: LaunchTarget[]) => this.eventStream.post(new ObservableEvents.OmnisharpOnMultipleLaunchTargets(targets)) )); - disposables.add(this.onBeforeServerInstall(() => + this._disposables.add(this.onBeforeServerInstall(() => this.eventStream.post(new ObservableEvents.OmnisharpOnBeforeServerInstall()) )); - disposables.add(this.onBeforeServerStart(() => { + this._disposables.add(this.onBeforeServerStart(() => { this.eventStream.post(new ObservableEvents.OmnisharpOnBeforeServerStart()); })); - disposables.add(this.onServerStop(() => + this._disposables.add(this.onServerStop(() => this.eventStream.post(new ObservableEvents.OmnisharpServerOnStop()) )); - disposables.add(this.onServerStart(() => { + this._disposables.add(this.onServerStart(() => { this.eventStream.post(new ObservableEvents.OmnisharpServerOnStart()); })); - disposables.add(this.onProjectAdded(this.updateTracker)); - disposables.add(this.onProjectChange(this.updateTracker)); - disposables.add(this.onProjectRemoved(this.updateTracker)); - - this._disposables = new CompositeDisposable(Disposable.create(() => { - disposables.dispose(); - })); + this._disposables.add(this.onProjectAdded(this.updateTracker)); + this._disposables.add(this.onProjectChange(this.updateTracker)); + this._disposables.add(this.onProjectRemoved(this.updateTracker)); this._setState(ServerState.Starting); this._launchTarget = launchTarget; @@ -399,11 +396,14 @@ export class OmniSharpServer { }); } + let disposables = this._disposables; + this._disposables = null; + return cleanupPromise.then(() => { this._serverProcess = null; this._setState(ServerState.Stopped); this._fireEvent(Events.ServerStop, this); - this._disposables.dispose(); + disposables.dispose(); }); } From cd231acbdf7a67b8e531155d8e84f19d3a2cc3f4 Mon Sep 17 00:00:00 2001 From: Akshita Agarwal Date: Thu, 5 Apr 2018 15:07:24 -0700 Subject: [PATCH 55/56] Added comments for debouncer --- src/omnisharp/server.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/omnisharp/server.ts b/src/omnisharp/server.ts index f70668b7ec..1ee5f9e96c 100644 --- a/src/omnisharp/server.ts +++ b/src/omnisharp/server.ts @@ -279,9 +279,9 @@ export class OmniSharpServer { this.eventStream.post(new ObservableEvents.OmnisharpServerOnStart()); })); - this._disposables.add(this.onProjectAdded(this.updateTracker)); - this._disposables.add(this.onProjectChange(this.updateTracker)); - this._disposables.add(this.onProjectRemoved(this.updateTracker)); + this._disposables.add(this.onProjectAdded(this.debounceUpdateProjectWithLeadingTrue)); + this._disposables.add(this.onProjectChange(this.debounceUpdateProjectWithLeadingTrue)); + this._disposables.add(this.onProjectRemoved(this.debounceUpdateProjectWithLeadingTrue)); this._setState(ServerState.Starting); this._launchTarget = launchTarget; @@ -338,7 +338,10 @@ export class OmniSharpServer { }); } - private updateTracker = () => { + private debounceUpdateProjectWithLeadingTrue = () => { + // Call the updateProjectInfo directly if it is the first time, otherwise debounce the request + // This needs to be done so that we have a project information for the first incoming request + if (this.firstUpdateProject) { this.updateProjectInfo(); } From 68264b409e7169bd2e3c40b889535357e6bc7170 Mon Sep 17 00:00:00 2001 From: Akshita Agarwal Date: Thu, 5 Apr 2018 15:51:03 -0700 Subject: [PATCH 56/56] disposable try --- src/omnisharp/server.ts | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/src/omnisharp/server.ts b/src/omnisharp/server.ts index 1ee5f9e96c..a00d44a66f 100644 --- a/src/omnisharp/server.ts +++ b/src/omnisharp/server.ts @@ -237,51 +237,53 @@ export class OmniSharpServer { private async _start(launchTarget: LaunchTarget): Promise { - this._disposables = new CompositeDisposable(); + let disposables = new CompositeDisposable(); - this._disposables.add(this.onServerError(err => + disposables.add(this.onServerError(err => this.eventStream.post(new ObservableEvents.OmnisharpServerOnServerError(err)) )); - this._disposables.add(this.onError((message: protocol.ErrorMessage) => + disposables.add(this.onError((message: protocol.ErrorMessage) => this.eventStream.post(new ObservableEvents.OmnisharpServerOnError(message)) )); - this._disposables.add(this.onMsBuildProjectDiagnostics((message: protocol.MSBuildProjectDiagnostics) => + disposables.add(this.onMsBuildProjectDiagnostics((message: protocol.MSBuildProjectDiagnostics) => this.eventStream.post(new ObservableEvents.OmnisharpServerMsBuildProjectDiagnostics(message)) )); - this._disposables.add(this.onUnresolvedDependencies((message: protocol.UnresolvedDependenciesMessage) => + disposables.add(this.onUnresolvedDependencies((message: protocol.UnresolvedDependenciesMessage) => this.eventStream.post(new ObservableEvents.OmnisharpServerUnresolvedDependencies(message)) )); - this._disposables.add(this.onStderr((message: string) => + disposables.add(this.onStderr((message: string) => this.eventStream.post(new ObservableEvents.OmnisharpServerOnStdErr(message)) )); - this._disposables.add(this.onMultipleLaunchTargets((targets: LaunchTarget[]) => + disposables.add(this.onMultipleLaunchTargets((targets: LaunchTarget[]) => this.eventStream.post(new ObservableEvents.OmnisharpOnMultipleLaunchTargets(targets)) )); - this._disposables.add(this.onBeforeServerInstall(() => + disposables.add(this.onBeforeServerInstall(() => this.eventStream.post(new ObservableEvents.OmnisharpOnBeforeServerInstall()) )); - this._disposables.add(this.onBeforeServerStart(() => { + disposables.add(this.onBeforeServerStart(() => { this.eventStream.post(new ObservableEvents.OmnisharpOnBeforeServerStart()); })); - this._disposables.add(this.onServerStop(() => + disposables.add(this.onServerStop(() => this.eventStream.post(new ObservableEvents.OmnisharpServerOnStop()) )); - this._disposables.add(this.onServerStart(() => { + disposables.add(this.onServerStart(() => { this.eventStream.post(new ObservableEvents.OmnisharpServerOnStart()); })); - this._disposables.add(this.onProjectAdded(this.debounceUpdateProjectWithLeadingTrue)); - this._disposables.add(this.onProjectChange(this.debounceUpdateProjectWithLeadingTrue)); - this._disposables.add(this.onProjectRemoved(this.debounceUpdateProjectWithLeadingTrue)); + disposables.add(this.onProjectAdded(this.debounceUpdateProjectWithLeadingTrue)); + disposables.add(this.onProjectChange(this.debounceUpdateProjectWithLeadingTrue)); + disposables.add(this.onProjectRemoved(this.debounceUpdateProjectWithLeadingTrue)); + + this._disposables = disposables; this._setState(ServerState.Starting); this._launchTarget = launchTarget; @@ -406,7 +408,9 @@ export class OmniSharpServer { this._serverProcess = null; this._setState(ServerState.Stopped); this._fireEvent(Events.ServerStop, this); - disposables.dispose(); + if (disposables) { + disposables.dispose(); + } }); }