Expand unit test infrastructure.

- This expansion mostly mocks out a few core types that are needed to do anythign with the VSCode APIs and also changes the programming model to manipulate the VSCode workspace, extensions and output channels.
- Also cleaned up a few missed changes in some src files.
This commit is contained in:
N. Taylor Mullen 2018-12-20 17:48:46 -08:00
Родитель 6275692e87
Коммит 216901eec6
12 изменённых файлов: 592 добавлений и 118 удалений

4
.vscode/tasks.json поставляемый
Просмотреть файл

@ -6,8 +6,8 @@
{
"label": "client-npm-compile",
"dependsOn": [
"client-npm-compile:shell",
"client-npm-compile:package"
"client-npm-compile:package",
"client-npm-compile:shell"
],
"group": {
"kind": "build",

Просмотреть файл

@ -2,7 +2,7 @@
"compilerOptions": {
"baseUrl": ".",
"module": "commonjs",
"target": "es5",
"target": "es6",
"outDir": "out",
"lib": [
"es6", "es2017.object"

Просмотреть файл

@ -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 'microsoft.aspnetcore.razor.vscode/dist/vscodeAdapter';
export class TestEventEmitter<T> implements vscode.EventEmitter<T> {
/**
* The event listeners can subscribe to.
*/
public readonly event: vscode.Event<T>;
private readonly listeners: Array<(e: T) => any> = [];
constructor() {
this.event = (listener: (e: T) => any, thisArgs?: any, disposables?: vscode.Disposable[]) => {
this.listeners.push(listener);
return {
dispose: Function,
};
};
}
/**
* Notify all subscribers of the [event](EventEmitter#event). Failure
* of one or more listener will not fail this function call.
*
* @param data The event object.
*/
public fire(data?: T) {
for (const listener of this.listeners) {
if (data) {
listener(data);
} else {
throw new Error('Event emitters do not implement firing events without data.');
}
}
}
/**
* Dispose this object and free resources.
*/
public dispose() {
// @ts-ignore
}
}

Просмотреть файл

@ -0,0 +1,13 @@
/* --------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
* ------------------------------------------------------------------------------------------ */
import { IEventEmitterFactory } from 'microsoft.aspnetcore.razor.vscode/dist/IEventEmitterFactory';
import { TestEventEmitter } from './TestEventEmitter';
export class TestEventEmitterFactory implements IEventEmitterFactory {
public create<T>() {
return new TestEventEmitter<T>();
}
}

Просмотреть файл

@ -0,0 +1,80 @@
/* --------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
* ------------------------------------------------------------------------------------------ */
import { RazorLanguage } from 'microsoft.aspnetcore.razor.vscode/dist/RazorLanguage';
import * as vscode from 'microsoft.aspnetcore.razor.vscode/dist/vscodeAdapter';
import { EndOfLine } from 'microsoft.aspnetcore.razor.vscode/dist/vscodeAdapter';
import * as os from 'os';
export class TestTextDocument implements vscode.TextDocument {
public readonly isUntitled = false;
public readonly version = 0;
public readonly isDirty: boolean = false;
public readonly lineCount: number;
constructor(
private readonly content: string,
public readonly uri: vscode.Uri,
public readonly languageId: string = RazorLanguage.id) {
this.lineCount = content.split(os.EOL).length;
}
public get fileName(): string {
throw new Error('Not implemented');
}
public get isClosed(): boolean {
throw new Error('Not implemented');
}
public get eol(): EndOfLine {
if (os.EOL.startsWith('\r')) {
return EndOfLine.CRLF;
}
return EndOfLine.LF;
}
public save(): vscode.Thenable<boolean> {
return new Promise<boolean>((resolve) => resolve(true));
}
public lineAt(line: any): vscode.TextLine {
throw new Error('Not implemented');
}
public offsetAt(position: vscode.Position): number {
throw new Error('Not implemented');
}
public positionAt(offset: number): vscode.Position {
throw new Error('Not implemented');
}
public getText(range?: vscode.Range): string {
if (range) {
throw new Error('getText is not implemented with range');
}
return this.content;
}
public getWordRangeAtPosition(position: vscode.Position, regex?: RegExp): vscode.Range | undefined {
throw new Error('Not implemented');
}
public validateRange(range: vscode.Range): vscode.Range {
throw new Error('Not implemented');
}
public validatePosition(position: vscode.Position): vscode.Position {
throw new Error('Not implemented');
}
}

Просмотреть файл

@ -0,0 +1,35 @@
/* --------------------------------------------------------------------------------------------
* 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 'microsoft.aspnetcore.razor.vscode/dist/vscodeAdapter';
export class TestUri implements vscode.Uri {
constructor(
public readonly path: string,
public readonly query = path,
public readonly scheme = 'unknown',
public readonly authority = 'unknown',
public readonly fragment = 'unknown',
public readonly fsPath = path) {
}
public with(change: {
scheme?: string;
authority?: string;
path?: string;
query?: string;
fragment?: string;
}): vscode.Uri {
throw new Error('Not Implemented.');
}
public toString(skipEncoding?: boolean): string {
throw new Error('Not Implemented.');
}
public toJSON(): any {
throw new Error('Not Implemented.');
}
}

Просмотреть файл

@ -0,0 +1,136 @@
/* --------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
* ------------------------------------------------------------------------------------------ */
import { RazorLogger } from 'microsoft.aspnetcore.razor.vscode/dist/RazorLogger';
import * as vscode from 'microsoft.aspnetcore.razor.vscode/dist/vscodeAdapter';
import * as os from 'os';
import { TestUri } from './TestUri';
export interface TestVSCodeApi extends vscode.api {
getOutputChannelSink(): { [logIdentifier: string]: string[] };
getRazorOutputChannel(): string[];
setWorkspaceDocuments(...workspaceDocuments: vscode.TextDocument[]): void;
setExtensions(...extensions: Array<vscode.Extension<any>>): void;
}
export function createTestVSCodeApi(): TestVSCodeApi {
const workspaceDocuments: vscode.TextDocument[] = [];
const extensions: Array<vscode.Extension<any>> = [];
const outputChannelSink: { [logIdentifier: string]: string[] } = {};
return {
// Non-VSCode APIs, for tests only
getOutputChannelSink: () => outputChannelSink,
getRazorOutputChannel: () => {
let razorOutputChannel = outputChannelSink[RazorLogger.logName];
if (!razorOutputChannel) {
razorOutputChannel = [];
outputChannelSink[RazorLogger.logName] = razorOutputChannel;
}
return razorOutputChannel;
},
setWorkspaceDocuments: (...documents) => {
workspaceDocuments.length = 0;
workspaceDocuments.push(...documents);
},
setExtensions: (...exts: Array<vscode.Extension<any>>) => {
extensions.length = 0;
extensions.push(...exts);
},
// VSCode APIs
commands: {
executeCommand: <T>(command: string, ...rest: any[]) => {
throw new Error('Not Implemented');
},
registerCommand: (command: string, callback: (...args: any[]) => any, thisArg?: any) => {
throw new Error('Not Implemented');
},
},
languages: {
match: (selector: vscode.DocumentSelector, document: vscode.TextDocument) => {
throw new Error('Not Implemented');
},
},
window: {
activeTextEditor: undefined,
showInformationMessage: <T extends vscode.MessageItem>(message: string, ...items: T[]) => {
throw new Error('Not Implemented');
},
showWarningMessage: <T extends vscode.MessageItem>(message: string, ...items: T[]) => {
throw new Error('Not Implemented');
},
showErrorMessage: (message: string, ...items: string[]) => {
throw new Error('Not Implemented');
},
createOutputChannel: (name: string) => {
if (!outputChannelSink[name]) {
outputChannelSink[name] = [];
}
const outputChannel: vscode.OutputChannel = {
name,
append: (message) => outputChannelSink[name].push(message),
appendLine: (message) => outputChannelSink[name].push(`${message}${os.EOL}`),
clear: () => outputChannelSink[name].length = 0,
dispose: Function,
hide: Function,
show: () => {
// @ts-ignore
},
};
return outputChannel;
},
registerWebviewPanelSerializer: (viewType: string, serializer: vscode.WebviewPanelSerializer) => {
throw new Error('Not implemented');
},
},
workspace: {
openTextDocument: (uri: vscode.Uri) => {
return new Promise((resolve) => {
for (const document of workspaceDocuments) {
if (document.uri === uri) {
resolve(document);
}
}
resolve(undefined);
});
},
getConfiguration: (section?: string, resource?: vscode.Uri) => {
throw new Error('Not Implemented');
},
asRelativePath: (pathOrUri: string | vscode.Uri, includeWorkspaceFolder?: boolean) => {
throw new Error('Not Implemented');
},
createFileSystemWatcher: (globPattern: vscode.GlobPattern, ignoreCreateEvents?: boolean, ignoreChangeEvents?: boolean, ignoreDeleteEvents?: boolean) => {
throw new Error('Not Implemented');
},
onDidChangeConfiguration: (listener: (e: vscode.ConfigurationChangeEvent) => any, thisArgs?: any, disposables?: vscode.Disposable[]): vscode.Disposable => {
throw new Error('Not Implemented');
},
},
extensions: {
getExtension: (id) => {
for (const extension of extensions) {
if (extension.id === id) {
return extension;
}
}
},
all: extensions,
},
Uri: {
parse: (path) => new TestUri(path),
},
Disposable: {
from: (...disposableLikes: Array<{ dispose: () => any }>) => {
throw new Error('Not Implemented');
},
},
version: '',
};
}

Просмотреть файл

@ -6,7 +6,8 @@
import * as assert from 'assert';
import { RazorLogger } from 'microsoft.aspnetcore.razor.vscode/dist/RazorLogger';
import { Trace } from 'microsoft.aspnetcore.razor.vscode/dist/Trace';
import { createLogSink as useOutputChannelSink, getFakeVsCodeApi } from './vscodeMock';
import { TestEventEmitterFactory } from './Mocks/TestEventEmitterFactory';
import { createTestVSCodeApi } from './Mocks/TestVSCodeApi';
describe('RazorLogger', () => {
function getAndAssertLog(sink: { [logName: string]: string[] }) {
@ -19,11 +20,11 @@ describe('Razor Logger', () => {
it('Always logs information header', () => {
// Arrange
const vscodeApi = getFakeVsCodeApi();
const sink = useOutputChannelSink(vscodeApi);
const api = createTestVSCodeApi();
const sink = api.getOutputChannelSink();
// Act
const logger = new RazorLogger(vscodeApi, Trace.Off);
const logger = new RazorLogger(api, Trace.Off);
// Assert
const log = getAndAssertLog(sink);
@ -33,9 +34,9 @@ describe('Razor Logger', () => {
it('logAlways logs when trace is Off', () => {
// Arrange
const vscodeApi = getFakeVsCodeApi();
const sink = useOutputChannelSink(vscodeApi);
const logger = new RazorLogger(vscodeApi, Trace.Off);
const api = createTestVSCodeApi();
const sink = api.getOutputChannelSink();
const logger = new RazorLogger(api, Trace.Off);
// Act
logger.logAlways('Test');
@ -48,9 +49,9 @@ describe('Razor Logger', () => {
it('logError logs when trace is Off', () => {
// Arrange
const vscodeApi = getFakeVsCodeApi();
const sink = useOutputChannelSink(vscodeApi);
const logger = new RazorLogger(vscodeApi, Trace.Off);
const api = createTestVSCodeApi();
const sink = api.getOutputChannelSink();
const logger = new RazorLogger(api, Trace.Off);
// Act
logger.logError('Test Error');
@ -63,9 +64,9 @@ describe('Razor Logger', () => {
it('logMessage does not log when trace is Off', () => {
// Arrange
const vscodeApi = getFakeVsCodeApi();
const sink = useOutputChannelSink(vscodeApi);
const logger = new RazorLogger(vscodeApi, Trace.Off);
const api = createTestVSCodeApi();
const sink = api.getOutputChannelSink();
const logger = new RazorLogger(api, Trace.Off);
// Act
logger.logMessage('Test message');
@ -79,9 +80,9 @@ describe('Razor Logger', () => {
for (const trace of [Trace.Messages, Trace.Verbose]) {
it(`logMessage logs when trace is ${Trace[trace]}`, () => {
// Arrange
const vscodeApi = getFakeVsCodeApi();
const sink = useOutputChannelSink(vscodeApi);
const logger = new RazorLogger(vscodeApi, trace);
const api = createTestVSCodeApi();
const sink = api.getOutputChannelSink();
const logger = new RazorLogger(api, trace);
// Act
logger.logMessage('Test message');
@ -96,9 +97,9 @@ describe('Razor Logger', () => {
for (const trace of [Trace.Off, Trace.Messages]) {
it(`logVerbose does not log when trace is ${Trace[trace]}`, () => {
// Arrange
const vscodeApi = getFakeVsCodeApi();
const sink = useOutputChannelSink(vscodeApi);
const logger = new RazorLogger(vscodeApi, trace);
const api = createTestVSCodeApi();
const sink = api.getOutputChannelSink();
const logger = new RazorLogger(api, trace);
// Act
logger.logVerbose('Test message');
@ -112,9 +113,9 @@ describe('Razor Logger', () => {
it('logVerbose logs when trace is Verbose', () => {
// Arrange
const vscodeApi = getFakeVsCodeApi();
const sink = useOutputChannelSink(vscodeApi);
const logger = new RazorLogger(vscodeApi, Trace.Verbose);
const api = createTestVSCodeApi();
const sink = api.getOutputChannelSink();
const logger = new RazorLogger(api, Trace.Verbose);
// Act
logger.logVerbose('Test message');

Просмотреть файл

@ -1,87 +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 'Microsoft.AspNetCore.Razor.VSCode/dist/vscodeAdapter';
import * as os from 'os';
export function createLogSink(api: vscode.api) {
const sink: { [logIdentifier: string]: string[] } = {};
api.window.createOutputChannel = (name) => {
if (!sink[name]) {
sink[name] = [];
}
const outputChannel: vscode.OutputChannel = {
name,
append: (message) => sink[name].push(message),
appendLine: (message) => sink[name].push(`${message}${os.EOL}`),
clear: () => sink[name].length = 0,
dispose: Function,
hide: Function,
show: () => {
// @ts-ignore
},
};
return outputChannel;
};
return sink;
}
export function getFakeVsCodeApi(): vscode.api {
return {
commands: {
executeCommand: <T>(command: string, ...rest: any[]) => {
throw new Error('Not Implemented');
},
},
languages: {
match: (selector: vscode.DocumentSelector, document: vscode.TextDocument) => {
throw new Error('Not Implemented');
},
},
window: {
activeTextEditor: undefined,
showInformationMessage: <T extends vscode.MessageItem>(message: string, ...items: T[]) => {
throw new Error('Not Implemented');
},
showWarningMessage: <T extends vscode.MessageItem>(message: string, ...items: T[]) => {
throw new Error('Not Implemented');
},
showErrorMessage: (message: string, ...items: string[]) => {
throw new Error('Not Implemented');
},
createOutputChannel: (name: string) => {
throw new Error('Not implemented');
},
},
workspace: {
getConfiguration: (section?: string, resource?: vscode.Uri) => {
throw new Error('Not Implemented');
},
asRelativePath: (pathOrUri: string | vscode.Uri, includeWorkspaceFolder?: boolean) => {
throw new Error('Not Implemented');
},
createFileSystemWatcher: (globPattern: vscode.GlobPattern, ignoreCreateEvents?: boolean, ignoreChangeEvents?: boolean, ignoreDeleteEvents?: boolean) => {
throw new Error('Not Implemented');
},
onDidChangeConfiguration: (listener: (e: vscode.ConfigurationChangeEvent) => any, thisArgs?: any, disposables?: vscode.Disposable[]): vscode.Disposable => {
throw new Error('Not Implemented');
},
},
extensions: {
getExtension: () => {
throw new Error('Not Implemented');
},
all: [],
},
Uri: {
parse: () => {
throw new Error('Not Implemented');
},
},
version: '',
};
}

Просмотреть файл

@ -378,6 +378,5 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer.ProjectSystem
return textAndVersion;
}
}
}
}

Просмотреть файл

@ -4,6 +4,6 @@
* ------------------------------------------------------------------------------------------ */
export class AddProjectRequest {
constructor(public readonly filePath: string, public readonly configurationName?: string) {
constructor(public readonly filePath: string) {
}
}

Просмотреть файл

@ -4,6 +4,58 @@
*--------------------------------------------------------------------------------------------*/
/* tslint:disable */
/**
* Represents a typed event.
*
* A function that represents an event to which you subscribe by calling it with
* a listener function as argument.
*
* @sample `item.onDidChange(function(event) { console.log("Event happened: " + event); });`
*/
export interface Event<T> {
/**
* A function that represents an event to which you subscribe by calling it with
* a listener function as argument.
*
* @param listener The listener function will be called when the event happens.
* @param thisArgs The `this`-argument which will be used when calling the event listener.
* @param disposables An array to which a [disposable](#Disposable) will be added.
* @return A disposable which unsubscribes the event listener.
*/
(listener: (e: T) => any, thisArgs?: any, disposables?: Disposable[]): Disposable;
}
/**
* An event emitter can be used to create and manage an [event](#Event) for others
* to subscribe to. One emitter always owns one event.
*
* Use this class if you want to provide event from within your extension, for instance
* inside a [TextDocumentContentProvider](#TextDocumentContentProvider) or when providing
* API to other extensions.
*/
export declare class EventEmitter<T> {
/**
* The event listeners can subscribe to.
*/
event: Event<T>;
/**
* Notify all subscribers of the [event](EventEmitter#event). Failure
* of one or more listener will not fail this function call.
*
* @param data The event object.
*/
fire(data?: T): void;
/**
* Dispose this object and free resources.
*/
dispose(): void;
}
export interface OutputChannel {
/**
@ -885,6 +937,197 @@ export interface ConfigurationChangeEvent {
affectsConfiguration(section: string, resource?: Uri): boolean;
}
export interface WebviewPanelSerializer {
/**
* Restore a webview panel from its seriailzed `state`.
*
* Called when a serialized webview first becomes visible.
*
* @param webviewPanel Webview panel to restore. The serializer should take ownership of this panel. The
* serializer must restore the webview's `.html` and hook up all webview events.
* @param state Persisted state from the webview content.
*
* @return Thanble indicating that the webview has been fully restored.
*/
deserializeWebviewPanel(webviewPanel: WebviewPanel, state: any): Thenable<void>;
}
/**
* Content settings for a webview panel.
*/
export interface WebviewPanelOptions {
/**
* Controls if the find widget is enabled in the panel.
*
* Defaults to false.
*/
readonly enableFindWidget?: boolean;
/**
* Controls if the webview panel's content (iframe) is kept around even when the panel
* is no longer visible.
*
* Normally the webview panel's html context is created when the panel becomes visible
* and destroyed when it is is hidden. Extensions that have complex state
* or UI can set the `retainContextWhenHidden` to make VS Code keep the webview
* context around, even when the webview moves to a background tab. When a webview using
* `retainContextWhenHidden` becomes hidden, its scripts and other dynamic content are suspended.
* When the panel becomes visible again, the context is automatically restored
* in the exact same state it was in originally. You cannot send messages to a
* hidden webview, even with `retainContextWhenHidden` enabled.
*
* `retainContextWhenHidden` has a high memory overhead and should only be used if
* your panel's context cannot be quickly saved and restored.
*/
readonly retainContextWhenHidden?: boolean;
}
/**
* A panel that contains a webview.
*/
export interface WebviewPanel {
/**
* Identifies the type of the webview panel, such as `'markdown.preview'`.
*/
readonly viewType: string;
/**
* Title of the panel shown in UI.
*/
title: string;
/**
* Webview belonging to the panel.
*/
readonly webview: Webview;
/**
* Content settings for the webview panel.
*/
readonly options: WebviewPanelOptions;
/**
* Editor position of the panel. This property is only set if the webview is in
* one of the editor view columns.
*
* @deprecated
*/
readonly viewColumn?: ViewColumn;
/**
* Whether the panel is active (focused by the user).
*/
readonly active: boolean;
/**
* Whether the panel is visible.
*/
readonly visible: boolean;
/**
* Fired when the panel's view state changes.
*/
readonly onDidChangeViewState: Event<WebviewPanelOnDidChangeViewStateEvent>;
/**
* Fired when the panel is disposed.
*
* This may be because the user closed the panel or because `.dispose()` was
* called on it.
*
* Trying to use the panel after it has been disposed throws an exception.
*/
readonly onDidDispose: Event<void>;
/**
* Show the webview panel in a given column.
*
* A webview panel may only show in a single column at a time. If it is already showing, this
* method moves it to a new column.
*
* @param viewColumn View column to show the panel in. Shows in the current `viewColumn` if undefined.
* @param preserveFocus When `true`, the webview will not take focus.
*/
reveal(viewColumn?: ViewColumn, preserveFocus?: boolean): void;
/**
* Dispose of the webview panel.
*
* This closes the panel if it showing and disposes of the resources owned by the webview.
* Webview panels are also disposed when the user closes the webview panel. Both cases
* fire the `onDispose` event.
*/
dispose(): any;
}
/**
* Event fired when a webview panel's view state changes.
*/
export interface WebviewPanelOnDidChangeViewStateEvent {
/**
* Webview panel whose view state changed.
*/
readonly webviewPanel: WebviewPanel;
}
/**
* A webview displays html content, like an iframe.
*/
export interface Webview {
/**
* Content settings for the webview.
*/
options: WebviewOptions;
/**
* Contents of the webview.
*
* Should be a complete html document.
*/
html: string;
/**
* Fired when the webview content posts a message.
*/
readonly onDidReceiveMessage: Event<any>;
/**
* Post a message to the webview content.
*
* Messages are only delivered if the webview is visible.
*
* @param message Body of the message.
*/
postMessage(message: any): Thenable<boolean>;
}
/**
* Content settings for a webview.
*/
export interface WebviewOptions {
/**
* Controls whether scripts are enabled in the webview content or not.
*
* Defaults to false (scripts-disabled).
*/
readonly enableScripts?: boolean;
/**
* Controls whether command uris are enabled in webview content or not.
*
* Defaults to false.
*/
readonly enableCommandUris?: boolean;
/**
* Root paths from which the webview can load local (filesystem) resources using the `vscode-resource:` scheme.
*
* Default to the root folders of the current workspace plus the extension's install directory.
*
* Pass in an empty array to disallow access to any local resources.
*/
readonly localResourceRoots?: ReadonlyArray<Uri>;
}
/**
* Thenable is a common denominator between ES6 promises, Q, jquery.Deferred, WinJS.Promise,
@ -892,7 +1135,7 @@ export interface ConfigurationChangeEvent {
* 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<T> {
export interface Thenable<T> {
/**
* Attaches callbacks for the resolution and/or rejection of the Promise.
* @param onfulfilled The callback to execute when the Promise is resolved.
@ -911,6 +1154,7 @@ export interface Extension<T>{
export interface api {
commands: {
executeCommand: <T>(command: string, ...rest: any[]) => Thenable<T | undefined>;
registerCommand(command: string, callback: (...args: any[]) => any, thisArg?: any): Disposable;
};
languages: {
match: (selector: DocumentSelector, document: TextDocument) => number;
@ -921,8 +1165,10 @@ export interface api {
showWarningMessage: <T extends MessageItem>(message: string, ...items: T[]) => Thenable<T | undefined>;
showErrorMessage(message: string, ...items: string[]): Thenable<string | undefined>;
createOutputChannel(name: string): OutputChannel;
registerWebviewPanelSerializer(viewType: string, serializer: WebviewPanelSerializer): Disposable;
};
workspace: {
openTextDocument: (uri: Uri) => Thenable<TextDocument>;
getConfiguration: (section?: string, resource?: Uri) => WorkspaceConfiguration;
asRelativePath: (pathOrUri: string | Uri, includeWorkspaceFolder?: boolean) => string;
createFileSystemWatcher(globPattern: GlobPattern, ignoreCreateEvents?: boolean, ignoreChangeEvents?: boolean, ignoreDeleteEvents?: boolean): FileSystemWatcher;
@ -935,6 +1181,9 @@ export interface api {
Uri: {
parse(value: string): Uri;
};
Disposable: {
from(...disposableLikes: { dispose: () => any }[]): Disposable;
};
version: string;
}