Skip to content

Commit

Permalink
Omnisharp config has changed
Browse files Browse the repository at this point in the history
  • Loading branch information
akshita31 committed May 16, 2018
1 parent c7f4525 commit 9b389b9
Show file tree
Hide file tree
Showing 6 changed files with 135 additions and 135 deletions.
10 changes: 5 additions & 5 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ import { ProjectStatusBarObserver } from './observers/ProjectStatusBarObserver';
import CSharpExtensionExports from './CSharpExtensionExports';
import { vscodeNetworkSettingsProvider, NetworkSettingsProvider } from './NetworkSettings';
import { ErrorMessageObserver } from './observers/ErrorMessageObserver';
import OptionStream from './observables/OptionStream';
import { createOptionStream } from './observables/OptionStream';
import OptionProvider from './observers/OptionProvider';
import { ShowOmniSharpConfigHasChanged } from './observers/OptionChangeObserver';
import { ShowOmniSharpConfigChangePrompt } from './observers/OptionChangeObserver';

export async function activate(context: vscode.ExtensionContext): Promise<CSharpExtensionExports> {

Expand All @@ -45,7 +45,7 @@ export async function activate(context: vscode.ExtensionContext): Promise<CSharp
util.setExtensionPath(extension.extensionPath);

const eventStream = new EventStream();
const optionStream = new OptionStream(vscode);
const optionStream = createOptionStream(vscode);
let optionProvider = new OptionProvider(optionStream);

let dotnetChannel = vscode.window.createOutputChannel('.NET');
Expand Down Expand Up @@ -112,8 +112,8 @@ export async function activate(context: vscode.ExtensionContext): Promise<CSharp
eventStream.post(new ActiveTextEditorChanged());
}));

context.subscriptions.push(optionStream);
context.subscriptions.push(ShowOmniSharpConfigHasChanged(optionStream, vscode));
context.subscriptions.push(optionProvider);
context.subscriptions.push(ShowOmniSharpConfigChangePrompt(optionStream, vscode));

let coreClrDebugPromise = Promise.resolve();
if (runtimeDependenciesExist) {
Expand Down
28 changes: 9 additions & 19 deletions src/observables/OptionStream.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,29 +6,19 @@
import { Options } from "../omnisharp/options";
import { vscode } from "../vscodeAdapter";
import 'rxjs/add/operator/take';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import Disposable, { IDisposable } from "../Disposable";
import { Subject } from "rxjs/Subject";
import 'rxjs/add/operator/publishBehavior';
import { Observable } from "rxjs/Observable";
import { Observer } from "rxjs/Observer";

export default class OptionStream {
private optionStream: Subject<Options>;
private disposable: IDisposable;

constructor(vscode: vscode) {
this.optionStream = new BehaviorSubject<Options>(Options.Read(vscode));
this.disposable = vscode.workspace.onDidChangeConfiguration(e => {
export function createOptionStream(vscode: vscode): Observable<Options> {
return Observable.create((observer: Observer<Options>) => {
let disposable = vscode.workspace.onDidChangeConfiguration(e => {
//if the omnisharp or csharp configuration are affected only then read the options
if (e.affectsConfiguration('omnisharp') || e.affectsConfiguration('csharp')) {
this.optionStream.next(Options.Read(vscode));
observer.next(Options.Read(vscode));
}
});
}

public dispose = () => {
this.disposable.dispose();
}

public subscribe(observer: (options: Options) => void): Disposable {
return new Disposable(this.optionStream.subscribe(observer));
}
return () => disposable.dispose();
}).publishBehavior(Options.Read(vscode)).refCount();
}
24 changes: 17 additions & 7 deletions src/observers/OptionChangeObserver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,31 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import OptionStream from "../observables/OptionStream";
import { vscode } from "../vscodeAdapter";
import { Options } from "../omnisharp/options";
import ShowInformationMessage from "./utils/ShowInformationMessage";
import { Observable } from "rxjs/Observable";
import Disposable from "../Disposable";
import 'rxjs/add/operator/filter';
import 'rxjs/add/operator/distinctUntilChanged';

export function ShowOmniSharpConfigHasChanged(optionStream: OptionStream, vscode: vscode) {
function ConfigChangeObservable(optionObservable: Observable<Options>): Observable<Options> {
let options: Options;
return optionStream.subscribe(newOptions => {
if (options && hasChanged(options, newOptions)) {
return optionObservable. filter(newOptions => {
let changed = (options && hasChanged(options, newOptions));
options = newOptions;
return changed;
});
}

export function ShowOmniSharpConfigChangePrompt(optionObservable: Observable<Options>, vscode: vscode) {
let subscription = ConfigChangeObservable(optionObservable)
.subscribe(_ => {
let message = "OmniSharp configuration has changed. Would you like to relaunch the OmniSharp server with your changes?";
ShowInformationMessage(vscode, message, { title: "Restart Now", command: 'o.restart' });
}
});

options = newOptions;
});
return new Disposable(subscription);
}

function hasChanged(oldOptions: Options, newOptions: Options): boolean {
Expand Down
13 changes: 10 additions & 3 deletions src/observers/OptionProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,26 @@
*--------------------------------------------------------------------------------------------*/

import { Options } from "../omnisharp/options";
import OptionStream from "../observables/OptionStream";
import { Subscription } from "rxjs/Subscription";
import { Observable } from "rxjs/Observable";

export default class OptionProvider {
private options: Options;
private subscription: Subscription;

constructor(optionStream: OptionStream) {
optionStream.subscribe(options => this.options = options);
constructor(optionObservable: Observable<Options>) {
this.subscription = optionObservable.subscribe(options => this.options = options);
}

public GetLatestOptions(): Options {
if (!this.options) {
throw new Error("Error reading OmniSharp options");
}

return this.options;
}

public dispose = () => {
this.subscription.unsubscribe();
}
}
134 changes: 79 additions & 55 deletions test/unitTests/OptionObserver/OptionChangeObserver.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ import { updateConfig, getVSCodeWithConfig } from '../testAssets/Fakes';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/fromPromise';
import 'rxjs/add/operator/timeout';
import { vscode, ConfigurationChangeEvent } from '../../../src/vscodeAdapter';
import OptionStream from '../../../src/observables/OptionStream';
import Disposable from '../../../src/Disposable';
import { ShowOmniSharpConfigHasChanged } from '../../../src/observers/OptionChangeObserver';
import { GetConfigChangeEvent } from '../testAssets/GetConfigChangeEvent';
import { vscode } from '../../../src/vscodeAdapter';
import { ShowOmniSharpConfigChangePrompt } from '../../../src/observers/OptionChangeObserver';
import { Subject } from 'rxjs/Subject';
import { Options } from '../../../src/omnisharp/options';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';

chaiUse(require('chai-as-promised'));
chaiUse(require('chai-string'));
Expand All @@ -27,66 +27,66 @@ suite("OmniSharpConfigChangeObserver", () => {
let vscode: vscode;
let infoMessage: string;
let invokedCommand: string;
let listenerFunction: Array<(e: ConfigurationChangeEvent) => any>;
let optionObservable: Subject<Options>;

setup(() => {
listenerFunction = new Array<(e: ConfigurationChangeEvent) => any>();
vscode = getVSCodeWithConfig();
vscode.window.showInformationMessage = async <T>(message: string, ...items: T[]) => {
infoMessage = message;
return new Promise<T>(resolve => {
doClickCancel = () => {
resolve(undefined);
};
vscode = getVSCode();
optionObservable = new BehaviorSubject<Options>(Options.Read(vscode));
ShowOmniSharpConfigChangePrompt(optionObservable, vscode);
commandDone = new Promise<void>(resolve => {
signalCommandDone = () => { resolve(); };
});
});

doClickOk = () => {
resolve(...items);
};
[
{ config: "omnisharp", section: "path", value: "somePath" },
{ config: "omnisharp", section: "waitForDebugger", value: true },
{ config: "omnisharp", section: "useGlobalMono", value: "always" }

].forEach(elem => {
suite(`When the ${elem.config} ${elem.section} changes`, () => {
setup(() => {
expect(infoMessage).to.be.undefined;
expect(invokedCommand).to.be.undefined;
updateConfig(vscode, elem.config, elem.section, elem.value);
optionObservable.next(Options.Read(vscode));
});
};

vscode.commands.executeCommand = (command: string, ...rest: any[]) => {
invokedCommand = command;
signalCommandDone();
return undefined;
};
test(`The information message is shown`, async () => {
expect(infoMessage).to.be.equal("OmniSharp configuration has changed. Would you like to relaunch the OmniSharp server with your changes?");
});

vscode.workspace.onDidChangeConfiguration = (listener: (e: ConfigurationChangeEvent) => any, thisArgs?: any, disposables?: Disposable[]) => {
listenerFunction.push(listener);
return new Disposable(() => { });
};
test('Given an information message if the user clicks cancel, the command is not executed', async () => {
doClickCancel();
await expect(Observable.fromPromise(commandDone).timeout(1).toPromise()).to.be.rejected;
expect(invokedCommand).to.be.undefined;
});

let optionStream = new OptionStream(vscode);
ShowOmniSharpConfigHasChanged(optionStream, vscode);
commandDone = new Promise<void>(resolve => {
signalCommandDone = () => { resolve(); };
test('Given an information message if the user clicks Restore, the command is executed', async () => {
doClickOk();
await commandDone;
expect(invokedCommand).to.be.equal("o.restart");
});
});
});

suite('When there is a change in OmniSharp Config', () => {
test('The information message is shown when the config changes', async () => {
let changingConfig = "omnisharp";
updateConfig(vscode, changingConfig, 'path', "somePath");
listenerFunction.forEach(listener => listener(GetConfigChangeEvent(changingConfig)));
expect(infoMessage).to.be.equal("OmniSharp configuration has changed. Would you like to relaunch the OmniSharp server with your changes?");
});

test('Given an information message if the user clicks cancel, the command is not executed', async () => {
let changingConfig = "omnisharp";
updateConfig(vscode, changingConfig, 'path', "somePath");
listenerFunction.forEach(listener => listener(GetConfigChangeEvent(changingConfig)));
doClickCancel();
await expect(Observable.fromPromise(commandDone).timeout(1).toPromise()).to.be.rejected;
[
{ config: "csharp", section: 'disableCodeActions', value: true },
{ config: "csharp", section: 'testsCodeLens.enabled', value: false },
{ config: "omnisharp", section: 'referencesCodeLens.enabled', value: false },
{ config: "csharp", section: 'format.enable', value: false },
{ config: "omnisharp", section: 'useEditorFormattingSettings', value: false },
{ config: "omnisharp", section: 'maxProjectResults', value: 1000 },
{ config: "omnisharp", section: 'projectLoadTimeout', value: 1000 },
{ config: "omnisharp", section: 'autoStart', value: false },
{ config: "omnisharp", section: 'loggingLevel', value: 'verbose' }
].forEach(elem => {
test(`The information message is not shown when ${elem.config} ${elem.section} changes`, async () => {
expect(infoMessage).to.be.undefined;
expect(invokedCommand).to.be.undefined;
});

test('Given an information message if the user clicks Restore, the command is executed', async () => {
let changingConfig = "omnisharp";
updateConfig(vscode, changingConfig, 'path', "somePath");
listenerFunction.forEach(listener => listener(GetConfigChangeEvent(changingConfig)));
doClickOk();
await commandDone;
expect(invokedCommand).to.be.equal("o.restart");
updateConfig(vscode, elem.config, elem.section, elem.value);
optionObservable.next(Options.Read(vscode));
expect(infoMessage).to.be.undefined;
});
});

Expand All @@ -98,4 +98,28 @@ suite("OmniSharpConfigChangeObserver", () => {
signalCommandDone = undefined;
commandDone = undefined;
});
});

function getVSCode(): vscode {
let vscode = getVSCodeWithConfig();
vscode.window.showInformationMessage = async <T>(message: string, ...items: T[]) => {
infoMessage = message;
return new Promise<T>(resolve => {
doClickCancel = () => {
resolve(undefined);
};

doClickOk = () => {
resolve(...items);
};
});
};

vscode.commands.executeCommand = (command: string, ...rest: any[]) => {
invokedCommand = command;
signalCommandDone();
return undefined;
};

return vscode;
}
});
61 changes: 15 additions & 46 deletions test/unitTests/OptionObserver/OptionProvider.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,66 +5,35 @@

import { should, expect } from 'chai';
import { getVSCodeWithConfig, updateConfig } from "../testAssets/Fakes";
import OptionStream from "../../../src/observables/OptionStream";
import { vscode, ConfigurationChangeEvent } from "../../../src/vscodeAdapter";
import Disposable from "../../../src/Disposable";
import { vscode } from "../../../src/vscodeAdapter";
import OptionProvider from '../../../src/observers/OptionProvider';
import { GetConfigChangeEvent } from '../testAssets/GetConfigChangeEvent';
import { Subject } from 'rxjs/Subject';
import { Options } from '../../../src/omnisharp/options';

suite('OptionProvider', () => {
suiteSetup(() => should());

let vscode: vscode;
let listenerFunction: Array<(e: ConfigurationChangeEvent) => any>;
let optionProvider: OptionProvider;
let optionObservable: Subject<Options>;

setup(() => {
listenerFunction = new Array<(e: ConfigurationChangeEvent) => any>();
vscode = getVSCode(listenerFunction);
let optionStream = new OptionStream(vscode);
optionProvider = new OptionProvider(optionStream);
vscode = getVSCodeWithConfig();
optionObservable = new Subject<Options>();
optionProvider = new OptionProvider(optionObservable);
});

test("Gives the default options if there is no change", () => {
let options = optionProvider.GetLatestOptions();
expect(options.path).to.be.null;
options.useGlobalMono.should.equal("auto");
options.waitForDebugger.should.equal(false);
options.loggingLevel.should.equal("information");
options.autoStart.should.equal(true);
options.projectLoadTimeout.should.equal(60);
options.maxProjectResults.should.equal(250);
options.useEditorFormattingSettings.should.equal(true);
options.useFormatting.should.equal(true);
options.showReferencesCodeLens.should.equal(true);
options.showTestsCodeLens.should.equal(true);
options.disableCodeActions.should.equal(false);
options.disableCodeActions.should.equal(false);
test("Throws exception when no options are pushed", () => {
expect(optionProvider.GetLatestOptions).to.throw();
});

test("Gives the latest options if there are changes in omnisharp config", () => {
test("Gives the latest options when options are changed", () => {
let changingConfig = "omnisharp";
updateConfig(vscode, changingConfig, 'path', "somePath");
listenerFunction.forEach(listener => listener(GetConfigChangeEvent(changingConfig)));
let options = optionProvider.GetLatestOptions();
expect(options.path).to.be.equal("somePath");
});

test("Gives the latest options if there are changes in csharp config", () => {
let changingConfig = 'csharp';
updateConfig(vscode, changingConfig, 'disableCodeActions', true);
listenerFunction.forEach(listener => listener(GetConfigChangeEvent(changingConfig)));
optionObservable.next(Options.Read(vscode));
updateConfig(vscode, changingConfig, 'path', "anotherPath");
optionObservable.next(Options.Read(vscode));
let options = optionProvider.GetLatestOptions();
expect(options.disableCodeActions).to.be.equal(true);
expect(options.path).to.be.equal("anotherPath");
});
});

function getVSCode(listenerFunction: Array<(e: ConfigurationChangeEvent) => any>): vscode {
let vscode = getVSCodeWithConfig();
vscode.workspace.onDidChangeConfiguration = (listener: (e: ConfigurationChangeEvent) => any, thisArgs?: any, disposables?: Disposable[]) => {
listenerFunction.push(listener);
return new Disposable(() => { });
};

return vscode;
}
});

0 comments on commit 9b389b9

Please sign in to comment.