diff --git a/src/extension/content/export.ts b/src/extension/content/export.ts index c2701cb..e181426 100644 --- a/src/extension/content/export.ts +++ b/src/extension/content/export.ts @@ -1,6 +1,5 @@ import { commands, NotebookCell, NotebookCellKind, Uri, window, workspace } from 'vscode'; -import { isKustoNotebook } from '../kernel/provider'; -import { registerDisposable } from '../utils'; +import { isKustoNotebook, registerDisposable } from '../utils'; import { getConnectionInfoFromDocumentMetadata } from '../kusto/connections/notebookConnection'; import { updateCache } from '../cache'; import { ICodeCell, IMarkdownCell, INotebookContent } from './jupyter'; diff --git a/src/extension/index.ts b/src/extension/index.ts index a0d9f01..c14e738 100644 --- a/src/extension/index.ts +++ b/src/extension/index.ts @@ -18,6 +18,7 @@ import { AzureAuthenticatedConnection } from './kusto/connections/azAuth'; import KustoClient from 'azure-kusto-data/source/client'; import { registerConnection } from './kusto/connections/baseConnection'; import { AppInsightsConnection } from './kusto/connections/appInsights'; +import { CellCodeLensProvider } from './interactive/cells'; let client: LanguageClient; @@ -44,6 +45,7 @@ export async function activate(context: ExtensionContext) { monitorJupyterCells(); registerInteractiveExperience(); registerExportCommand(); + CellCodeLensProvider.regsiter(); } export async function deactivate(): Promise { diff --git a/src/extension/interactive/cells.ts b/src/extension/interactive/cells.ts new file mode 100644 index 0000000..d4006aa --- /dev/null +++ b/src/extension/interactive/cells.ts @@ -0,0 +1,63 @@ +import { + CancellationToken, + CodeLens, + CodeLensProvider, + EventEmitter, + FoldingRange, + languages, + Range, + TextDocument +} from 'vscode'; +import { FoldingRangesProvider } from '../languageServer'; +import { IDisposable } from '../types'; +import { disposeAllDisposables, isNotebookCell, registerDisposable } from '../utils'; + +export class CellCodeLensProvider implements CodeLensProvider, IDisposable { + private readonly _onDidChangeCodeLenses = new EventEmitter(); + private readonly disposables: IDisposable[] = []; + constructor() { + FoldingRangesProvider.instance.onDidChange(() => this._onDidChangeCodeLenses.fire(), this, this.disposables); + } + public static regsiter() { + const provider = new CellCodeLensProvider(); + registerDisposable(languages.registerCodeLensProvider({ language: 'kusto' }, provider)); + registerDisposable(provider); + } + public dispose() { + disposeAllDisposables(this.disposables); + } + public async provideCodeLenses(document: TextDocument, _token: CancellationToken): Promise { + if (isNotebookCell(document)) { + return []; + } + const ranges = await FoldingRangesProvider.instance.getRanges(document); + const codelenses: CodeLens[] = []; + ranges.forEach((item) => { + const index = getStartOfCode(document, item); + if (!index) { + return; + } + codelenses.push( + new CodeLens(new Range(index, 0, item.end, 0), { + title: 'Run Query', + command: 'kusto.executeSelectedQuery', + arguments: [document, index, item.end] + }) + ); + }); + return codelenses; + } +} + +function isComment(document: TextDocument, index: number) { + const line = document.lineAt(index); + return line.isEmptyOrWhitespace || line.text.trim().startsWith('//'); +} + +function getStartOfCode(document: TextDocument, range: FoldingRange): number | undefined { + for (let index = range.start; index <= range.end; index++) { + if (!isComment(document, index)) { + return index; + } + } +} diff --git a/src/extension/kernel/interactive.ts b/src/extension/kernel/interactive.ts index d84723b..b2ab1e2 100644 --- a/src/extension/kernel/interactive.ts +++ b/src/extension/kernel/interactive.ts @@ -1,13 +1,89 @@ -import { TextEditor } from 'vscode'; +import { + NotebookCellData, + NotebookCellKind, + NotebookDocument, + NotebookRange, + Range, + TextDocument, + Uri, + ViewColumn, + workspace, + WorkspaceEdit +} from 'vscode'; import { commands } from 'vscode'; -import { isKustoFile, registerDisposable } from '../utils'; +import { registerDisposable } from '../utils'; +import { Kernel } from './provider'; export function registerInteractiveExperience() { - registerDisposable(commands.registerTextEditorCommand('kusto.executeSelectedQuery', executeSelectedQuery)); + registerDisposable(commands.registerCommand('kusto.executeSelectedQuery', executeSelectedQuery)); } -async function executeSelectedQuery(editor: TextEditor) { - if (!isKustoFile(editor.document)) { +type INativeInteractiveWindow = { notebookUri: Uri; inputUri: Uri }; +const documentInteractiveDocuments = new WeakMap>(); +async function executeSelectedQuery(document: TextDocument, start: number, end: number) { + if (!documentInteractiveDocuments.has(document)) { + documentInteractiveDocuments.set(document, getNotebookDocument()); + } + const notebook = await documentInteractiveDocuments.get(document); + if (!notebook) { return; } + // Ensure its visible. + await commands.executeCommand('interactive.open', undefined, notebook.uri, undefined); + const cell = await createCell(notebook, document, start, end); + Kernel.instance.executeInteractive([cell], document, Kernel.instance.interactiveController); +} + +async function getNotebookDocument() { + // eslint-disable-next-line prefer-const + let notebookUri: Uri | undefined; + let isSelected = false; + const selected = new Promise((resolve) => { + const disposable = Kernel.instance.interactiveController.onDidChangeSelectedNotebooks( + ({ notebook, selected }) => { + if (!selected) { + return; + } + if (!notebookUri) { + notebookUri = notebook.uri; + isSelected = true; + return resolve(); + } + if (notebook.uri.toString() !== notebookUri.toString()) { + return; + } + isSelected = true; + resolve(); + } + ); + registerDisposable(disposable); + }); + const info = (await commands.executeCommand( + 'interactive.open', + ViewColumn.Beside, + undefined, + 'kustoInteractive' + )) as INativeInteractiveWindow; + if (!isSelected) { + notebookUri = info.notebookUri; + await Promise.all([selected, commands.executeCommand('notebook.selectKernel')]); + } + + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + return workspace.notebookDocuments.find((item) => item.uri.toString() === notebookUri!.toString()); +} +async function createCell(notebook: NotebookDocument, document: TextDocument, start: number, end: number) { + const text = document.getText(new Range(document.lineAt(start).range.start, document.lineAt(end).range.end)); + const edit = new WorkspaceEdit(); + const cell = new NotebookCellData(NotebookCellKind.Code, text.trim(), 'kusto'); + cell.metadata = { + interactiveWindowCellMarker: document.lineAt(start).text, + interactive: { + file: document.uri.fsPath, + line: start + } + }; + edit.replaceNotebookCells(notebook.uri, new NotebookRange(notebook.cellCount, notebook.cellCount), [cell]); + await workspace.applyEdit(edit); + return notebook.cellAt(notebook.cellCount - 1); } diff --git a/src/extension/kernel/kernel.ts b/src/extension/kernel/kernel.ts deleted file mode 100644 index fc2e390..0000000 --- a/src/extension/kernel/kernel.ts +++ /dev/null @@ -1,175 +0,0 @@ -// import { format } from 'util'; -// import { -// notebook, -// NotebookCell, -// NotebookCellData, -// NotebookCellKind, -// NotebookCellMetadata, -// NotebookCellOutput, -// NotebookCellOutputItem, -// NotebookRange, -// NotebookDocument, -// TextEditor, -// workspace, -// WorkspaceEdit, -// NotebookKernelPreload -// } from 'vscode'; -// import { Client } from '../kusto/client'; -// import { getChartType } from '../output/chart'; -// import { createPromiseFromToken } from '../utils'; -// import { isKustoInteractive } from './provider'; - -// export class Kernel implements NotebookKernel { -// // todo@API make this mandatory? -// public readonly id = 'kusto'; - -// public readonly label = 'Kusto'; -// public readonly description = 'Execute Kusto Queries'; -// public readonly detail = ''; -// public readonly isPreferred: boolean = true; -// public readonly preloads: NotebookKernelPreload[] = []; -// public readonly supportedLanguages?: string[] = ['kusto']; -// private static interactiveKernel?: Kernel; -// public static get InteractiveKernel() { -// return Kernel.interactiveKernel; -// } -// constructor(public readonly document: NotebookDocument) { -// if (isKustoInteractive(document)) { -// Kernel.interactiveKernel = this; -// } -// } -// public async executeInteractiveSelection(textEditor: TextEditor): Promise { -// const source = textEditor.document.getText(textEditor.selection); -// let edit = new WorkspaceEdit(); -// edit.replaceNotebookCells(this.document.uri, this.document.cellCount, 0, [ -// new NotebookCellData(NotebookCellKind.Code, source.trim(), 'kusto', [], new NotebookCellMetadata()) -// ]); -// await workspace.applyEdit(edit); -// const cell = this.document.cellAt[this.document.cellCount - 1]; -// const task = notebook.createNotebookCellExecutionTask(cell.notebook.uri, cell.index, this.id); -// if (!task) { -// return; -// } -// const client = await Client.create(textEditor.document); -// if (!client) { -// task.end(); -// return; -// } -// const startTime = Date.now(); -// task.start({ startTime }); -// task.clearOutput(); -// let success = false; -// try { -// const results = await Promise.race([ -// createPromiseFromToken(task.token, { action: 'resolve', value: undefined }), -// client.execute(source) -// ]); -// console.log(results); -// if (task.token.isCancellationRequested || !results) { -// return task.end(); -// } -// success = true; - -// // Dump the primary results table from the list of tables. -// // We already have that information as a seprate property name `primaryResults`. -// // This will reduce the amount of JSON (save) in knb file. -// if (!Array.isArray(results.primaryResults) || results.primaryResults.length === 0) { -// results.primaryResults = results.tables.filter((item) => item.name === 'PrimaryResult'); -// } -// const chartType = getChartType(results); -// results.tables = results.tables.filter((item) => item.name !== 'PrimaryResult'); -// results.tableNames = results.tableNames.filter((item) => item !== 'PrimaryResult'); - -// const outputItems: NotebookCellOutputItem[] = []; -// if (chartType && chartType !== 'table') { -// outputItems.push(new NotebookCellOutputItem('application/vnd.kusto.result.viz+json', results)); -// } else { -// outputItems.push(new NotebookCellOutputItem('application/vnd.kusto.result+json', results)); -// } -// task.appendOutput(new NotebookCellOutput(outputItems)); -// } catch (ex) { -// const error: Error = ex; -// const data = { -// ename: ex.message || 'Failed to execute query', -// evalue: ex.evalue || ex['@type'] || '', -// traceback: [error.stack || format(ex)] -// }; - -// task.appendOutput( -// new NotebookCellOutput([new NotebookCellOutputItem('application/x.notebook.error-traceback', data)]) -// ); -// } finally { -// task.end({ endTime: Date.now(), success }); -// } -// } -// public async executeCellsRequest(document: NotebookDocument, ranges: NotebookRange[]): Promise { -// if (isKustoInteractive(document)) { -// return; -// } -// const cells = document -// .getCells() -// .filter( -// (cell) => -// cell.kind === NotebookCellKind.Code && -// ranges.some((range) => range.start <= cell.index && cell.index < range.end) -// ); -// await Promise.all(cells.map(this.executeCell.bind(this))); -// } -// private async executeCell(cell: NotebookCell): Promise { -// const task = notebook.createNotebookCellExecutionTask(cell.notebook.uri, cell.index, this.id); -// if (!task) { -// return; -// } -// const client = await Client.create(cell.notebook); -// if (!client) { -// task.end(); -// return; -// } -// const startTime = Date.now(); -// task.start({ startTime }); -// task.clearOutput(); -// let success = false; -// try { -// const results = await Promise.race([ -// createPromiseFromToken(task.token, { action: 'resolve', value: undefined }), -// client.execute(cell.document.getText()) -// ]); -// console.log(results); -// if (task.token.isCancellationRequested || !results) { -// return task.end(); -// } -// success = true; - -// // Dump the primary results table from the list of tables. -// // We already have that information as a seprate property name `primaryResults`. -// // This will reduce the amount of JSON (save) in knb file. -// if (!Array.isArray(results.primaryResults) || results.primaryResults.length === 0) { -// results.primaryResults = results.tables.filter((item) => item.name === 'PrimaryResult'); -// } -// const chartType = getChartType(results); -// results.tables = results.tables.filter((item) => item.name !== 'PrimaryResult'); -// results.tableNames = results.tableNames.filter((item) => item !== 'PrimaryResult'); - -// const outputItems: NotebookCellOutputItem[] = []; -// if (chartType && chartType !== 'table') { -// outputItems.push(new NotebookCellOutputItem('application/vnd.kusto.result.viz+json', results)); -// } else { -// outputItems.push(new NotebookCellOutputItem('application/vnd.kusto.result+json', results)); -// } -// task.appendOutput(new NotebookCellOutput(outputItems)); -// } catch (ex) { -// const error: Error | undefined = ex; -// const data = { -// ename: error?.name || 'Failed to execute query', -// evalue: error?.message || '', -// traceback: [error?.stack || format(ex)] -// }; - -// task.appendOutput( -// new NotebookCellOutput([new NotebookCellOutputItem('application/x.notebook.error-traceback', data)]) -// ); -// } finally { -// task.end({ endTime: Date.now(), success }); -// } -// } -// } diff --git a/src/extension/kernel/provider.ts b/src/extension/kernel/provider.ts index 85c8360..fa0ea65 100644 --- a/src/extension/kernel/provider.ts +++ b/src/extension/kernel/provider.ts @@ -8,11 +8,12 @@ import { WorkspaceEdit, NotebookController, ExtensionContext, - Disposable + Disposable, + TextDocument } from 'vscode'; import { Client } from '../kusto/client'; import { getChartType } from '../output/chart'; -import { createPromiseFromToken } from '../utils'; +import { createPromiseFromToken, InteractiveWindowView } from '../utils'; export class KernelProvider { public static register(context: ExtensionContext) { @@ -21,25 +22,20 @@ export class KernelProvider { } export class Kernel extends Disposable { - controller: NotebookController; + notebookController: NotebookController; + public readonly interactiveController: NotebookController; + public static instance: Kernel; constructor() { super(() => { this.dispose(); }); - this.controller = notebooks.createNotebookController( - 'kusto', - 'kusto-notebook', - 'Kusto', - this.execute.bind(this), - [] - ); - this.controller.supportedLanguages = ['kusto']; - this.controller.supportsExecutionOrder = true; - this.controller.description = 'Execute Kusto Queries'; + this.notebookController = this.createController('kusto', 'kusto-notebook'); + this.interactiveController = this.createController('kustoInteractive', InteractiveWindowView); + Kernel.instance = this; } dispose() { - this.controller.dispose(); + this.notebookController.dispose(); } public execute(cells: NotebookCell[], notebook: NotebookDocument, controller: NotebookController) { @@ -47,10 +43,26 @@ export class Kernel extends Disposable { this.executeCell(cell, controller); }); } + public executeInteractive(cells: NotebookCell[], textDocument: TextDocument, controller: NotebookController) { + cells.forEach((cell) => { + this.executeCell(cell, controller, textDocument); + }); + } - private async executeCell(cell: NotebookCell, controller: NotebookController): Promise { + private createController(id: string, view: string) { + const controller = notebooks.createNotebookController(id, view, 'Kusto', this.execute.bind(this), []); + controller.supportedLanguages = ['kusto']; + controller.supportsExecutionOrder = true; + controller.description = 'Execute Kusto Queries'; + return controller; + } + private async executeCell( + cell: NotebookCell, + controller: NotebookController, + textDocument?: TextDocument + ): Promise { const task = controller.createNotebookCellExecution(cell); - const client = await Client.create(cell.notebook); + const client = await Client.create(textDocument || cell.notebook); if (!client) { task.end(false); return; @@ -118,10 +130,3 @@ export class Kernel extends Disposable { } } } - -export function isJupyterNotebook(document?: NotebookDocument) { - return document?.notebookType === 'jupyter-notebook'; -} -export function isKustoNotebook(document: NotebookDocument) { - return document.notebookType === 'kusto-notebook'; -} diff --git a/src/extension/kusto/connections/notebookConnection.ts b/src/extension/kusto/connections/notebookConnection.ts index fbfb025..6bf131e 100644 --- a/src/extension/kusto/connections/notebookConnection.ts +++ b/src/extension/kusto/connections/notebookConnection.ts @@ -1,8 +1,7 @@ import { EventEmitter, NotebookCell, NotebookCellKind, NotebookCellsChangeEvent, TextDocument, window } from 'vscode'; import { commands, notebooks, NotebookDocument, Uri, workspace, WorkspaceEdit } from 'vscode'; import { IConnectionInfo } from './types'; -import { registerDisposable } from '../../utils'; -import { isJupyterNotebook, isKustoNotebook } from '../../kernel/provider'; +import { isJupyterNotebook, isKustoNotebook, registerDisposable } from '../../utils'; import { isEqual } from 'lodash'; import { captureConnectionFromUser } from './management'; import { getFromCache, updateCache } from '../../cache'; diff --git a/src/extension/languageServer/browser/index.ts b/src/extension/languageServer/browser/index.ts index 2385a6b..fd5b24c 100644 --- a/src/extension/languageServer/browser/index.ts +++ b/src/extension/languageServer/browser/index.ts @@ -24,11 +24,10 @@ import { WorkspaceEdit } from 'vscode'; import { EngineSchema } from '../../kusto/schema'; -import { getNotebookDocument, NotebookCellScheme, registerDisposable } from '../../utils'; +import { getNotebookDocument, isJupyterNotebook, NotebookCellScheme, registerDisposable } from '../../utils'; import { ILanguageServiceExport, LanguageService } from './kustoLanguageService'; import * as vsclientConverter from 'vscode-languageclient/lib/common/protocolConverter'; import { TextDocument as LSTextDocument } from 'vscode-languageserver-textdocument'; -import { isJupyterNotebook } from '../../kernel/provider'; // eslint-disable-next-line @typescript-eslint/no-var-requires const languageService: ILanguageServiceExport = require('../../../../libs/kusto/languageService/kustoLanguageService'); diff --git a/src/extension/languageServer/index.ts b/src/extension/languageServer/index.ts index 082ac5c..8b05591 100644 --- a/src/extension/languageServer/index.ts +++ b/src/extension/languageServer/index.ts @@ -1,9 +1,21 @@ import { isEqual } from 'lodash'; import * as path from 'path'; -import { env, ExtensionContext, NotebookDocument, TextDocument, UIKind, window, workspace } from 'vscode'; +import { + env, + Event, + EventEmitter, + ExtensionContext, + FoldingRange, + NotebookDocument, + TextDocument, + UIKind, + Uri, + window, + workspace +} from 'vscode'; +import * as vsclientConverter from 'vscode-languageclient/lib/common/protocolConverter'; import { LanguageClientOptions } from 'vscode-languageclient'; import { LanguageClient, ServerOptions, State, TransportKind } from 'vscode-languageclient/node'; -import { isJupyterNotebook, isKustoNotebook } from '../kernel/provider'; import { fromConnectionInfo } from '../kusto/connections'; import { addDocumentConnectionHandler, @@ -12,11 +24,32 @@ import { } from '../kusto/connections/notebookConnection'; import { IConnectionInfo } from '../kusto/connections/types'; import { EngineSchema } from '../kusto/schema'; -import { getNotebookDocument, isNotebookCell, registerDisposable } from '../utils'; +import { getNotebookDocument, isJupyterNotebook, isKustoNotebook, isNotebookCell, registerDisposable } from '../utils'; import { setDocumentEngineSchema } from './browser'; let client: LanguageClient; let clientIsReady: boolean | undefined; +export class FoldingRangesProvider { + private ranges = new WeakMap(); + private _onDidChange = new EventEmitter(); + private readonly protocolConverter = vsclientConverter.createConverter(undefined, undefined); + public static instance = new FoldingRangesProvider(); + public get onDidChange(): Event { + return this._onDidChange.event; + } + public setRanges(uri: Uri, ranges: any[]) { + const document = workspace.textDocuments.find((item) => item.uri.toString() === uri.toString()); + if (!document) { + return; + } + this._onDidChange.fire(document); + const foldingRanges = this.protocolConverter.asFoldingRanges(ranges); + this.ranges.set(document, foldingRanges); + } + public async getRanges(document: TextDocument): Promise { + return this.ranges.get(document) || []; + } +} export async function initialize(context: ExtensionContext) { if (env.uiKind === UIKind.Desktop) { startLanguageServer(context); @@ -68,6 +101,15 @@ function startLanguageServer(context: ExtensionContext) { registerDisposable(onDidChangeStateHandler); // Start the client. This will also launch the server client.start(); + client.onReady().then(() => { + client.onNotification( + 'foldingRanges', + ({ uri, foldingRanges }: { uri: string; foldingRanges: FoldingRange[] }) => { + console.error(`Got notification for folding ranges`, uri, foldingRanges); + FoldingRangesProvider.instance.setRanges(Uri.parse(uri), foldingRanges); + } + ); + }); } const lastSentConnectionForDocument = new WeakMap>(); diff --git a/src/extension/languageServer/jupyterNotebook.ts b/src/extension/languageServer/jupyterNotebook.ts index 35bdadf..aa9a263 100644 --- a/src/extension/languageServer/jupyterNotebook.ts +++ b/src/extension/languageServer/jupyterNotebook.ts @@ -1,7 +1,6 @@ import { languages, NotebookCellKind, NotebookDocument, notebooks, TextDocument, workspace } from 'vscode'; import { useProposedApi } from '../constants'; -import { isJupyterNotebook } from '../kernel/provider'; -import { registerDisposable } from '../utils'; +import { isJupyterNotebook, registerDisposable } from '../utils'; export function monitorJupyterCells() { registerDisposable(workspace.onDidOpenNotebookDocument(updateKustoCellsOfDocument)); diff --git a/src/extension/utils.ts b/src/extension/utils.ts index 9095d0c..958454e 100644 --- a/src/extension/utils.ts +++ b/src/extension/utils.ts @@ -137,3 +137,14 @@ export function getHash(value: string) { export function getNotebookDocument(document: TextDocument | NotebookDocument): NotebookDocument | undefined { return workspace.notebookDocuments.find((item) => item.uri.path === document.uri.path); } +export function isJupyterNotebook(document?: NotebookDocument) { + return document?.notebookType === 'jupyter-notebook'; +} +export function isKustoNotebook(document: NotebookDocument) { + return document.notebookType === 'kusto-notebook'; +} +export const InteractiveWindowView = 'interactive'; +export const InteractiveWindowScheme = 'vscode-interactive'; +export function isInteractiveWindow(document: NotebookDocument) { + return document.notebookType === InteractiveWindowView; +} diff --git a/src/extension/vscode.d.ts b/src/extension/vscode.d.ts index 83e8531..f5cdab6 100644 --- a/src/extension/vscode.d.ts +++ b/src/extension/vscode.d.ts @@ -1055,7 +1055,7 @@ declare module 'vscode' { /** * A message that should be rendered when hovering over the decoration. */ - hoverMessage?: MarkedString | MarkedString[]; + hoverMessage?: MarkdownString | MarkedString | Array; /** * Render options applied to the current decoration. For performance reasons, keep the @@ -1700,6 +1700,7 @@ declare module 'vscode' { /** * Set to `true` to keep the picker open when focus moves to another part of the editor or to another window. + * This setting is ignored on iPad and is always false. */ ignoreFocusOut?: boolean; @@ -1726,6 +1727,7 @@ declare module 'vscode' { /** * Set to `true` to keep the picker open when focus moves to another part of the editor or to another window. + * This setting is ignored on iPad and is always false. */ ignoreFocusOut?: boolean; } @@ -1858,6 +1860,12 @@ declare module 'vscode' { * Indicates that this message should be modal. */ modal?: boolean; + + /** + * Human-readable detail message that is rendered less prominent. _Note_ that detail + * is only shown for {@link MessageOptions.modal modal} messages. + */ + detail?: string; } /** @@ -1900,6 +1908,7 @@ declare module 'vscode' { /** * Set to `true` to keep the input box open when focus moves to another part of the editor or to another window. + * This setting is ignored on iPad and is always false. */ ignoreFocusOut?: boolean; @@ -1989,7 +1998,7 @@ declare module 'vscode' { * { language: 'typescript', scheme: 'file' } * * @example A language filter that applies to all package.json paths - * { language: 'json', scheme: 'untitled', pattern: '**​/package.json' } + * { language: 'json', pattern: '**​/package.json' } */ export interface DocumentFilter { @@ -2560,8 +2569,8 @@ declare module 'vscode' { * The MarkdownString represents human-readable text that supports formatting via the * markdown syntax. Standard markdown is supported, also tables, but no embedded html. * - * When created with `supportThemeIcons` then rendering of {@link ThemeIcon theme icons} via - * the `$()`-syntax is supported. + * Rendering of {@link ThemeIcon theme icons} via the `$()`-syntax is supported + * when the {@link MarkdownString.supportThemeIcons `supportThemeIcons`} is set to `true`. */ export class MarkdownString { @@ -2579,7 +2588,7 @@ declare module 'vscode' { /** * Indicates that this markdown string can contain {@link ThemeIcon ThemeIcons}, e.g. `$(zap)`. */ - readonly supportThemeIcons?: boolean; + supportThemeIcons?: boolean; /** * Creates a new markdown string with the given value. @@ -2616,7 +2625,7 @@ declare module 'vscode' { * * @deprecated This type is deprecated, please use {@link MarkdownString `MarkdownString`} instead. */ - export type MarkedString = MarkdownString | string | { language: string; value: string }; + export type MarkedString = string | { language: string; value: string }; /** * A hover represents additional information for a symbol or word. Hovers are @@ -2627,7 +2636,7 @@ declare module 'vscode' { /** * The contents of this hover. */ - contents: MarkedString[]; + contents: Array; /** * The range to which this hover applies. When missing, the @@ -2642,7 +2651,7 @@ declare module 'vscode' { * @param contents The contents of the hover. * @param range The range to which the hover applies. */ - constructor(contents: MarkedString | MarkedString[], range?: Range); + constructor(contents: MarkdownString | MarkedString | Array, range?: Range); } /** @@ -3966,6 +3975,31 @@ declare module 'vscode' { readonly retriggerCharacters: readonly string[]; } + /** + * A structured label for a {@link CompletionItem completion item}. + */ + export interface CompletionItemLabel { + + /** + * The label of this completion item. + * + * By default this is also the text that is inserted when this completion is selected. + */ + label: string; + + /** + * An optional string which is rendered less prominently directly after {@link CompletionItemLabel.label label}, + * without any spacing. Should be used for function signatures or type annotations. + */ + detail?: string; + + /** + * An optional string which is rendered less prominently after {@link CompletionItemLabel.detail}. Should be used + * for fully qualified names or file path. + */ + description?: string; + } + /** * Completion item kinds. */ @@ -4032,7 +4066,7 @@ declare module 'vscode' { * this is also the text that is inserted when selecting * this completion. */ - label: string; + label: string | CompletionItemLabel; /** * The kind of this completion item. Based on the kind @@ -4156,7 +4190,7 @@ declare module 'vscode' { * @param label The label of the completion. * @param kind The {@link CompletionItemKind kind} of the completion. */ - constructor(label: string, kind?: CompletionItemKind); + constructor(label: string | CompletionItemLabel, kind?: CompletionItemKind); } /** @@ -5127,7 +5161,7 @@ declare module 'vscode' { * - configuration to workspace folder when there is no workspace folder settings. * - configuration to workspace folder when {@link WorkspaceConfiguration} is not scoped to a resource. */ - update(section: string, value: any, configurationTarget?: ConfigurationTarget | boolean, overrideInLanguage?: boolean): Thenable; + update(section: string, value: any, configurationTarget?: ConfigurationTarget | boolean | null, overrideInLanguage?: boolean): Thenable; /** * Readable dictionary that backs this configuration. @@ -5792,7 +5826,7 @@ declare module 'vscode' { /** * A link on a terminal line. */ - export interface TerminalLink { + export class TerminalLink { /** * The start index of the link on {@link TerminalLinkContext.line}. */ @@ -5811,6 +5845,47 @@ declare module 'vscode' { * depending on OS, user settings, and localization. */ tooltip?: string; + + /** + * Creates a new terminal link. + * @param startIndex The start index of the link on {@link TerminalLinkContext.line}. + * @param length The length of the link on {@link TerminalLinkContext.line}. + * @param tooltip The tooltip text when you hover over this link. + * + * If a tooltip is provided, is will be displayed in a string that includes instructions on + * how to trigger the link, such as `{0} (ctrl + click)`. The specific instructions vary + * depending on OS, user settings, and localization. + */ + constructor(startIndex: number, length: number, tooltip?: string); + } + + /** + * Provides a terminal profile for the contributed terminal profile when launched via the UI or + * command. + */ + export interface TerminalProfileProvider { + /** + * Provide the terminal profile. + * @param token A cancellation token that indicates the result is no longer needed. + * @returns The terminal profile. + */ + provideTerminalProfile(token: CancellationToken): ProviderResult; + } + + /** + * A terminal profile defines how a terminal will be launched. + */ + export class TerminalProfile { + /** + * The options that the terminal will launch with. + */ + options: TerminalOptions | ExtensionTerminalOptions; + + /** + * Creates a new terminal profile. + * @param options The options that the terminal will launch with. + */ + constructor(options: TerminalOptions | ExtensionTerminalOptions); } /** @@ -6139,6 +6214,13 @@ declare module 'vscode' { */ export interface Memento { + /** + * Returns the stored keys. + * + * @return The stored keys. + */ + keys(): readonly string[]; + /** * Return a value. * @@ -7605,8 +7687,8 @@ declare module 'vscode' { } /** - * A webview based view. - */ + * A webview based view. + */ export interface WebviewView { /** * Identifies the type of the webview view, such as `'hexEditor.dataView'`. @@ -8181,8 +8263,7 @@ declare module 'vscode' { export function openExternal(target: Uri): Thenable; /** - * Resolves a uri to form that is accessible externally. Currently only supports `https:`, `http:` and - * `vscode.env.uriScheme` uris. + * Resolves a uri to a form that is accessible externally. * * #### `http:` or `https:` scheme * @@ -8202,7 +8283,7 @@ declare module 'vscode' { * Creates a uri that - if opened in a browser (e.g. via `openExternal`) - will result in a registered {@link UriHandler} * to trigger. * - * Extensions should not make any assumptions about the resulting uri and should not alter it in anyway. + * Extensions should not make any assumptions about the resulting uri and should not alter it in any way. * Rather, extensions can e.g. use this uri in an authentication flow, by adding the uri as callback query * argument to the server to authenticate to. * @@ -8227,6 +8308,11 @@ declare module 'vscode' { * a system or user action — for example, in remote cases, a user may close a port forwarding tunnel that was opened by * `asExternalUri`. * + * #### Any other scheme + * + * Any other scheme will be handled as if the provided URI is a workspace URI. In that case, the method will return + * a URI which, when handled, will make the editor open the workspace. + * * @return A uri that can be used on the client machine. */ export function asExternalUri(target: Uri): Thenable; @@ -8994,6 +9080,12 @@ declare module 'vscode' { */ export function registerTerminalLinkProvider(provider: TerminalLinkProvider): Disposable; + /** + * Registers a provider for a contributed terminal profile. + * @param id The ID of the contributed terminal profile. + * @param provider The terminal profile provider. + */ + export function registerTerminalProfileProvider(id: string, provider: TerminalProfileProvider): Disposable; /** * Register a file decoration provider. * @@ -9385,6 +9477,11 @@ declare module 'vscode' { * a setting text style. */ message?: string; + + /** + * The icon path or {@link ThemeIcon} for the terminal. + */ + iconPath?: Uri | { light: Uri; dark: Uri } | ThemeIcon; } /** @@ -9401,6 +9498,11 @@ declare module 'vscode' { * control a terminal. */ pty: Pseudoterminal; + + /** + * The icon path or {@link ThemeIcon} for the terminal. + */ + iconPath?: Uri | { light: Uri; dark: Uri } | ThemeIcon; } /** @@ -9489,6 +9591,24 @@ declare module 'vscode' { */ onDidClose?: Event; + /** + * An event that when fired allows changing the name of the terminal. + * + * **Example:** Change the terminal name to "My new terminal". + * ```typescript + * const writeEmitter = new vscode.EventEmitter(); + * const changeNameEmitter = new vscode.EventEmitter(); + * const pty: vscode.Pseudoterminal = { + * onDidWrite: writeEmitter.event, + * onDidChangeName: changeNameEmitter.event, + * open: () => changeNameEmitter.fire('My new terminal'), + * close: () => {} + * }; + * vscode.window.createTerminal({ name: 'My terminal', pty }); + * ``` + */ + onDidChangeName?: Event; + /** * Implement to handle when the pty is open and ready to start firing events. * @@ -9780,6 +9900,7 @@ declare module 'vscode' { /** * If the UI should stay open even when loosing UI focus. Defaults to false. + * This setting is ignored on iPad and is always false. */ ignoreFocusOut: boolean; @@ -9884,7 +10005,7 @@ declare module 'vscode' { /** * An event signaling when the active items have changed. */ - readonly onDidChangeActive: Event; + readonly onDidChangeActive: Event; /** * Selected items. This can be read and updated by the extension. @@ -9894,7 +10015,7 @@ declare module 'vscode' { /** * An event signaling when the selected items have changed. */ - readonly onDidChangeSelection: Event; + readonly onDidChangeSelection: Event; } /** @@ -11409,7 +11530,7 @@ declare module 'vscode' { readonly outputs: readonly NotebookCellOutput[]; /** - * The most recent {@link NotebookCellExecutionSummary excution summary} for this cell. + * The most recent {@link NotebookCellExecutionSummary execution summary} for this cell. */ readonly executionSummary?: NotebookCellExecutionSummary; } @@ -11477,7 +11598,7 @@ declare module 'vscode' { /** * Get the cells of this notebook. A subset can be retrieved by providing - * a range. The range will be adjuset to the notebook. + * a range. The range will be adjusted to the notebook. * * @param range A notebook range. * @returns The cells contained by the range or all cells. @@ -11515,7 +11636,7 @@ declare module 'vscode' { } /** - * A notebook range represents an ordered pair of two cell indicies. + * A notebook range represents an ordered pair of two cell indices. * It is guaranteed that start is less than or equal to end. */ export class NotebookRange { @@ -11626,7 +11747,7 @@ declare module 'vscode' { data: Uint8Array; /** - * Create a new notbook cell output item. + * Create a new notebook cell output item. * * @param data The value of the output item. * @param mime The mime type of the output item. @@ -11718,9 +11839,9 @@ declare module 'vscode' { } /** - * NotebookData is the raw representation of notebooks. + * Raw representation of a notebook. * - * Extensions are responsible to create {@link NotebookData `NotebookData`} so that the editor + * Extensions are responsible for creating {@link NotebookData `NotebookData`} so that the editor * can create a {@link NotebookDocument `NotebookDocument`}. * * @see {@link NotebookSerializer} @@ -12122,7 +12243,7 @@ declare module 'vscode' { /** * Namespace for notebooks. * - * The notebooks functionality is composed of three loosly coupled components: + * The notebooks functionality is composed of three loosely coupled components: * * 1. {@link NotebookSerializer} enable the editor to open, show, and save notebooks * 2. {@link NotebookController} own the execution of notebooks, e.g they create output from code cells. @@ -13512,17 +13633,17 @@ declare module 'vscode' { */ export interface AuthenticationProviderAuthenticationSessionsChangeEvent { /** - * The {@link AuthenticationSession}s of the {@link AuthentiationProvider AuthenticationProvider} that have been added. + * The {@link AuthenticationSession}s of the {@link AuthenticationProvider} that have been added. */ readonly added?: readonly AuthenticationSession[]; /** - * The {@link AuthenticationSession}s of the {@link AuthentiationProvider AuthenticationProvider} that have been removed. + * The {@link AuthenticationSession}s of the {@link AuthenticationProvider} that have been removed. */ readonly removed?: readonly AuthenticationSession[]; /** - * The {@link AuthenticationSession}s of the {@link AuthentiationProvider AuthenticationProvider} that have been changed. + * The {@link AuthenticationSession}s of the {@link AuthenticationProvider} that have been changed. * A session changes when its data excluding the id are updated. An example of this is a session refresh that results in a new * access token being set for the session. */ diff --git a/src/extension/vscode.proposed.d.ts b/src/extension/vscode.proposed.d.ts index 7924435..bc6c46a 100644 --- a/src/extension/vscode.proposed.d.ts +++ b/src/extension/vscode.proposed.d.ts @@ -16,48 +16,6 @@ declare module 'vscode' { - //#region auth provider: https://github.com/microsoft/vscode/issues/88309 - - /** - * An {@link Event} which fires when an {@link AuthenticationProvider} is added or removed. - */ - export interface AuthenticationProvidersChangeEvent { - /** - * The ids of the {@link AuthenticationProvider}s that have been added. - */ - readonly added: ReadonlyArray; - - /** - * The ids of the {@link AuthenticationProvider}s that have been removed. - */ - readonly removed: ReadonlyArray; - } - - export namespace authentication { - /** - * @deprecated - getSession should now trigger extension activation. - * Fires with the provider id that was registered or unregistered. - */ - export const onDidChangeAuthenticationProviders: Event; - - /** - * @deprecated - * An array of the information of authentication providers that are currently registered. - */ - export const providers: ReadonlyArray; - - /** - * @deprecated - * Logout of a specific session. - * @param providerId The id of the provider to use - * @param sessionId The session id to remove - * provider - */ - export function logout(providerId: string, sessionId: string): Thenable; - } - - //#endregion - // eslint-disable-next-line vscode-dts-region-comments //#region @alexdima - resolvers @@ -92,6 +50,7 @@ declare module 'vscode' { localAddressPort?: number; label?: string; public?: boolean; + protocol?: string; } export interface TunnelDescription { @@ -99,6 +58,8 @@ declare module 'vscode' { //The complete local address(ex. localhost:1234) localAddress: { port: number, host: string; } | string; public?: boolean; + // If protocol is not provided it is assumed to be http, regardless of the localAddress. + protocol?: string; } export interface Tunnel extends TunnelDescription { @@ -226,6 +187,7 @@ declare module 'vscode' { tildify?: boolean; normalizeDriveLetter?: boolean; workspaceSuffix?: string; + workspaceTooltip?: string; authorityPrefix?: string; stripPathStartingSeparator?: boolean; } @@ -898,45 +860,6 @@ declare module 'vscode' { //#endregion - //#region Terminal icon https://github.com/microsoft/vscode/issues/120538 - - export interface TerminalOptions { - /** - * The icon path or {@link ThemeIcon} for the terminal. - */ - readonly iconPath?: Uri | { light: Uri; dark: Uri } | ThemeIcon; - } - - export interface ExtensionTerminalOptions { - /** - * A themeIcon, Uri, or light and dark Uris to use as the terminal tab icon - */ - readonly iconPath?: Uri | { light: Uri; dark: Uri } | ThemeIcon; - } - - //#endregion - - //#region Terminal profile provider https://github.com/microsoft/vscode/issues/120369 - - export namespace window { - /** - * Registers a provider for a contributed terminal profile. - * @param id The ID of the contributed terminal profile. - * @param provider The terminal profile provider. - */ - export function registerTerminalProfileProvider(id: string, provider: TerminalProfileProvider): Disposable; - } - - export interface TerminalProfileProvider { - /** - * Provide terminal profile options for the requested terminal. - * @param token A cancellation token that indicates the result is no longer needed. - */ - provideProfileOptions(token: CancellationToken): ProviderResult; - } - - //#endregion - // eslint-disable-next-line vscode-dts-region-comments //#region @jrieken -> exclusive document filters @@ -953,7 +876,22 @@ declare module 'vscode' { //#endregion //#region Custom Tree View Drag and Drop https://github.com/microsoft/vscode/issues/32592 + /** + * A data provider that provides tree data + */ + export interface TreeDataProvider { + /** + * An optional event to signal that an element or root has changed. + * This will trigger the view to update the changed element/root and its children recursively (if shown). + * To signal that root has changed, do not pass any argument or pass `undefined` or `null`. + */ + onDidChangeTreeData2?: Event; + } + export interface TreeViewOptions { + /** + * An optional interface to implement drag and drop in the tree view. + */ dragAndDropController?: DragAndDropController; } @@ -1416,41 +1354,6 @@ declare module 'vscode' { //#endregion - //#region https://github.com/microsoft/vscode/issues/39441 - - export interface CompletionItem { - /** - * Will be merged into CompletionItem#label - */ - label2?: CompletionItemLabel; - } - - export interface CompletionItemLabel { - /** - * The function or variable. Rendered leftmost. - */ - name: string; - - /** - * The parameters without the return type. Render after `name`. - */ - //todo@API rename to signature - parameters?: string; - - /** - * The fully qualified name, like package name or file path. Rendered after `signature`. - */ - //todo@API find better name - qualifier?: string; - - /** - * The return-type of a function or type of a property/variable. Rendered rightmost. - */ - type?: string; - } - - //#endregion - //#region @https://github.com/microsoft/vscode/issues/123601, notebook messaging export interface NotebookRendererMessage { @@ -1847,60 +1750,24 @@ declare module 'vscode' { //#region https://github.com/microsoft/vscode/issues/107467 export namespace test { /** - * Registers a controller that can discover and - * run tests in workspaces and documents. + * Creates a new test controller. + * + * @param id Identifier for the controller, must be globally unique. */ - export function registerTestController(testController: TestController): Disposable; + export function createTestController(id: string): TestController; /** * Requests that tests be run by their controller. * @param run Run options to use * @param token Cancellation token for the test run */ - export function runTests(run: TestRunRequest, token?: CancellationToken): Thenable; - - /** - * Returns an observer that retrieves tests in the given workspace folder. - * @stability experimental - */ - export function createWorkspaceTestObserver(workspaceFolder: WorkspaceFolder): TestObserver; + export function runTests(run: TestRunRequest, token?: CancellationToken): Thenable; /** - * Returns an observer that retrieves tests in the given text document. + * Returns an observer that watches and can request tests. * @stability experimental */ - export function createDocumentTestObserver(document: TextDocument): TestObserver; - - /** - * Creates a {@link TestRun}. This should be called by the - * {@link TestRunner} when a request is made to execute tests, and may also - * be called if a test run is detected externally. Once created, tests - * that are included in the results will be moved into the - * {@link TestResultState.Pending} state. - * - * @param request Test run request. Only tests inside the `include` may be - * modified, and tests in its `exclude` are ignored. - * @param name The human-readable name of the run. This can be used to - * disambiguate multiple sets of results in a test run. It is useful if - * tests are run across multiple platforms, for example. - * @param persist Whether the results created by the run should be - * persisted in the editor. This may be false if the results are coming from - * a file already saved externally, such as a coverage information file. - */ - export function createTestRun(request: TestRunRequest, name?: string, persist?: boolean): TestRun; - - /** - * Creates a new managed {@link TestItem} instance. - * @param options Initial/required options for the item - * @param data Custom data to be stored in {@link TestItem.data} - */ - export function createTestItem(options: TestItemOptions, data: T): TestItem; - - /** - * Creates a new managed {@link TestItem} instance. - * @param options Initial/required options for the item - */ - export function createTestItem(options: TestItemOptions): TestItem; + export function createTestObserver(): TestObserver; /** * List of test results stored by the editor, sorted in descending @@ -1923,7 +1790,7 @@ declare module 'vscode' { /** * List of tests returned by test provider for files in the workspace. */ - readonly tests: ReadonlyArray>; + readonly tests: ReadonlyArray; /** * An event that fires when an existing test in the collection changes, or @@ -1932,16 +1799,6 @@ declare module 'vscode' { */ readonly onDidChangeTest: Event; - /** - * An event that fires when all test providers have signalled that the tests - * the observer references have been discovered. Providers may continue to - * watch for changes and cause {@link onDidChangeTest} to fire as files - * change, until the observer is disposed. - * - * @todo as below - */ - readonly onDidDiscoverInitialTests: Event; - /** * Dispose of the observer, allowing the editor to eventually tell test * providers that they no longer need to update tests. @@ -1956,96 +1813,152 @@ declare module 'vscode' { /** * List of all tests that are newly added. */ - readonly added: ReadonlyArray>; + readonly added: ReadonlyArray; /** * List of existing tests that have updated. */ - readonly updated: ReadonlyArray>; + readonly updated: ReadonlyArray; /** * List of existing tests that have been removed. */ - readonly removed: ReadonlyArray>; + readonly removed: ReadonlyArray; } /** * Interface to discover and execute tests. */ - export interface TestController { + export interface TestController { /** - * Requests that tests be provided for the given workspace. This will - * be called when tests need to be enumerated for the workspace, such as - * when the user opens the test explorer. - * - * It's guaranteed that this method will not be called again while - * there is a previous uncancelled call for the given workspace folder. + * The ID of the controller, passed in {@link vscode.test.createTestController} + */ + readonly id: string; + + /** + * Root test item. Tests in the workspace should be added as children of + * the root. The extension controls when to add these, although the + * editor may request children using the {@link resolveChildrenHandler}, + * and the extension should add tests for a file when + * {@link vscode.workspace.onDidOpenTextDocument} fires in order for + * decorations for tests within the file to be visible. * - * @param workspace The workspace in which to observe tests - * @param cancellationToken Token that signals the used asked to abort the test run. - * @returns the root test item for the workspace + * Tests in this collection should be watched and updated by the extension + * as files change. See {@link resolveChildrenHandler} for details around + * for the lifecycle of watches. */ - createWorkspaceTestRoot(workspace: WorkspaceFolder, token: CancellationToken): ProviderResult>; + // todo@API a little weird? what is its label, id, busy state etc? Can I dispose this? + // todo@API allow createTestItem-calls without parent and simply treat them as root (similar to createSourceControlResourceGroup) + readonly root: TestItem; /** - * Requests that tests be provided for the given document. This will be - * called when tests need to be enumerated for a single open file, for - * instance by code lens UI. + * Creates a new managed {@link TestItem} instance as a child of this + * one. + * @param id Unique identifier for the TestItem. + * @param label Human-readable label of the test item. + * @param parent Parent of the item. This is required; top-level items + * should be created as children of the {@link root}. + * @param uri URI this TestItem is associated with. May be a file or directory. + * @param data Custom data to be stored in {@link TestItem.data} + */ + createTestItem( + id: string, + label: string, + parent: TestItem, + uri?: Uri, + ): TestItem; + + + /** + * A function provided by the extension that the editor may call to request + * children of a test item, if the {@link TestItem.canExpand} is `true`. + * When called, the item should discover children and call + * {@link TestController.createTestItem} as children are discovered. * - * It's suggested that the provider listen to change events for the text - * document to provide information for tests that might not yet be - * saved. + * The item in the explorer will automatically be marked as "busy" until + * the function returns or the returned thenable resolves. * - * If the test system is not able to provide or estimate for tests on a - * per-file basis, this method may not be implemented. In that case, the - * editor will request and use the information from the workspace tree. + * The controller may wish to set up listeners or watchers to update the + * children as files and documents change. * - * @param document The document in which to observe tests - * @param cancellationToken Token that signals the used asked to abort the test run. - * @returns the root test item for the document + * @param item An unresolved test item for which + * children are being requested */ - createDocumentTestRoot?(document: TextDocument, token: CancellationToken): ProviderResult>; + resolveChildrenHandler?: (item: TestItem) => Thenable | void; /** * Starts a test run. When called, the controller should call - * {@link vscode.test.createTestRun}. All tasks associated with the + * {@link TestController.createTestRun}. All tasks associated with the * run should be created before the function returns or the reutrned * promise is resolved. * - * @param options Options for this test run - * @param cancellationToken Token that signals the used asked to abort the test run. + * @param request Request information for the test run + * @param cancellationToken Token that signals the used asked to abort the + * test run. If cancellation is requested on this token, all {@link TestRun} + * instances associated with the request will be + * automatically cancelled as well. */ - runTests(options: TestRunRequest, token: CancellationToken): Thenable | void; + runHandler?: (request: TestRunRequest, token: CancellationToken) => Thenable | void; + /** + * Creates a {@link TestRun}. This should be called by the + * {@link TestRunner} when a request is made to execute tests, and may also + * be called if a test run is detected externally. Once created, tests + * that are included in the results will be moved into the + * {@link TestResultState.Pending} state. + * + * @param request Test run request. Only tests inside the `include` may be + * modified, and tests in its `exclude` are ignored. + * @param name The human-readable name of the run. This can be used to + * disambiguate multiple sets of results in a test run. It is useful if + * tests are run across multiple platforms, for example. + * @param persist Whether the results created by the run should be + * persisted in the editor. This may be false if the results are coming from + * a file already saved externally, such as a coverage information file. + */ + createTestRun(request: TestRunRequest, name?: string, persist?: boolean): TestRun; + + /** + * Unregisters the test controller, disposing of its associated tests + * and unpersisted results. + */ + dispose(): void; } /** * Options given to {@link test.runTests}. */ - export interface TestRunRequest { + export class TestRunRequest { /** * Array of specific tests to run. The controllers should run all of the * given tests and all children of the given tests, excluding any tests * that appear in {@link TestRunRequest.exclude}. */ - tests: TestItem[]; + tests: TestItem[]; /** * An array of tests the user has marked as excluded in the editor. May be * omitted if no exclusions were requested. Test controllers should not run * excluded tests or any children of excluded tests. */ - exclude?: TestItem[]; + exclude?: TestItem[]; /** * Whether tests in this run should be debugged. */ debug: boolean; + + /** + * @param tests Array of specific tests to run. + * @param exclude Tests to exclude from the run + * @param debug Whether tests in this run should be debugged. + */ + constructor(tests: readonly TestItem[], exclude?: readonly TestItem[], debug?: boolean); } /** * Options given to {@link TestController.runTests} */ - export interface TestRun { + export interface TestRun { /** * The human-readable name of the run. This can be used to * disambiguate multiple sets of results in a test run. It is useful if @@ -2053,6 +1966,12 @@ declare module 'vscode' { */ readonly name?: string; + /** + * A cancellation token which will be triggered when the test run is + * canceled from the UI. + */ + readonly token: CancellationToken; + /** * Updates the state of the test in the run. Calling with method with nodes * outside the {@link TestRunRequest.tests} or in the @@ -2060,9 +1979,10 @@ declare module 'vscode' { * * @param test The test to update * @param state The state to assign to the test - * @param duration Optionally sets how long the test took to run + * @param duration Optionally sets how long the test took to run, in milliseconds */ - setState(test: TestItem, state: TestResultState, duration?: number): void; + //todo@API is this "update" state or set final state? should this be called setTestResult? + setState(test: TestItem, state: TestResultState, duration?: number): void; /** * Appends a message, such as an assertion error, to the test item. @@ -2071,10 +1991,9 @@ declare module 'vscode' { * or in the {@link TestRunRequest.exclude} array will no-op. * * @param test The test to update - * @param state The state to assign to the test - * + * @param message The message to add */ - appendMessage(test: TestItem, message: TestMessage): void; + appendMessage(test: TestItem, message: TestMessage): void; /** * Appends raw output from the test runner. On the user's request, the @@ -2093,48 +2012,11 @@ declare module 'vscode' { end(): void; } - /** - * Indicates the the activity state of the {@link TestItem}. - */ - export enum TestItemStatus { - /** - * All children of the test item, if any, have been discovered. - */ - Resolved = 1, - - /** - * The test item may have children who have not been discovered yet. - */ - Pending = 0, - } - - /** - * Options initially passed into `vscode.test.createTestItem` - */ - export interface TestItemOptions { - /** - * Unique identifier for the TestItem. This is used to correlate - * test results and tests in the document with those in the workspace - * (test explorer). This cannot change for the lifetime of the TestItem. - */ - id: string; - - /** - * URI this TestItem is associated with. May be a file or directory. - */ - uri?: Uri; - - /** - * Display name describing the test item. - */ - label: string; - } - /** * A test item is an item shown in the "test explorer" view. It encompasses * both a suite and a test, since they have almost or identical capabilities. */ - export interface TestItem { + export interface TestItem { /** * Unique identifier for the TestItem. This is used to correlate * test results and tests in the document with those in the workspace @@ -2150,26 +2032,31 @@ declare module 'vscode' { /** * A mapping of children by ID to the associated TestItem instances. */ - readonly children: ReadonlyMap>; + //todo@API use array over es6-map + readonly children: ReadonlyMap; /** - * The parent of this item, if any. Assigned automatically when calling - * {@link TestItem.addChild}. + * The parent of this item, given in {@link TestController.createTestItem}. + * This is undefined only for the {@link TestController.root}. */ - readonly parent?: TestItem; + readonly parent?: TestItem; /** - * Indicates the state of the test item's children. The editor will show - * TestItems in the `Pending` state and with a `resolveHandler` as being - * expandable, and will call the `resolveHandler` to request items. + * Indicates whether this test item may have children discovered by resolving. + * If so, it will be shown as expandable in the Test Explorer view, and + * expanding the item will cause {@link TestController.resolveChildrenHandler} + * to be invoked with the item. * - * A TestItem in the `Resolved` state is assumed to have discovered and be - * watching for changes in its children if applicable. TestItems are in the - * `Resolved` state when initially created; if the editor should call - * the `resolveHandler` to discover children, set the state to `Pending` - * after creating the item. + * Default to false. */ - status: TestItemStatus; + canResolveChildren: boolean; + + /** + * Controls whether the item is shown as "busy" in the Test Explorer view. + * This is useful for showing status while discovering children. Defaults + * to false. + */ + busy: boolean; /** * Display name describing the test case. @@ -2206,12 +2093,6 @@ declare module 'vscode' { */ debuggable: boolean; - /** - * Custom extension data on the item. This data will never be serialized - * or shared outside the extenion who created the item. - */ - data: T; - /** * Marks the test as outdated. This can happen as a result of file changes, * for example. In "auto run" mode, tests that are outdated will be @@ -2220,40 +2101,10 @@ declare module 'vscode' { * * Extensions should generally not override this method. */ - invalidate(): void; + invalidateResults(): void; /** - * A function provided by the extension that the editor may call to request - * children of the item, if the {@link TestItem.status} is `Pending`. - * - * When called, the item should discover tests and call {@link TestItem.addChild}. - * The items should set its {@link TestItem.status} to `Resolved` when - * discovery is finished. - * - * The item should continue watching for changes to the children and - * firing updates until the token is cancelled. The process of watching - * the tests may involve creating a file watcher, for example. After the - * token is cancelled and watching stops, the TestItem should set its - * {@link TestItem.status} back to `Pending`. - * - * The editor will only call this method when it's interested in refreshing - * the children of the item, and will not call it again while there's an - * existing, uncancelled discovery for an item. - * - * @param token Cancellation for the request. Cancellation will be - * requested if the test changes before the previous call completes. - */ - resolveHandler?: (token: CancellationToken) => void; - - /** - * Attaches a child, created from the {@link test.createTestItem} function, - * to this item. A `TestItem` may be a child of at most one other item. - */ - addChild(child: TestItem): void; - - /** - * Removes the test and its children from the tree. Any tokens passed to - * child `resolveHandler` methods will be cancelled. + * Removes the test and its children from the tree. */ dispose(): void; } @@ -2586,69 +2437,6 @@ declare module 'vscode' { //#endregion - //#region @joaomoreno https://github.com/microsoft/vscode/issues/124263 - // This API change only affects behavior and documentation, not API surface. - - namespace env { - - /** - * Resolves a uri to form that is accessible externally. - * - * #### `http:` or `https:` scheme - * - * Resolves an *external* uri, such as a `http:` or `https:` link, from where the extension is running to a - * uri to the same resource on the client machine. - * - * This is a no-op if the extension is running on the client machine. - * - * If the extension is running remotely, this function automatically establishes a port forwarding tunnel - * from the local machine to `target` on the remote and returns a local uri to the tunnel. The lifetime of - * the port forwarding tunnel is managed by the editor and the tunnel can be closed by the user. - * - * *Note* that uris passed through `openExternal` are automatically resolved and you should not call `asExternalUri` on them. - * - * #### `vscode.env.uriScheme` - * - * Creates a uri that - if opened in a browser (e.g. via `openExternal`) - will result in a registered {@link UriHandler} - * to trigger. - * - * Extensions should not make any assumptions about the resulting uri and should not alter it in anyway. - * Rather, extensions can e.g. use this uri in an authentication flow, by adding the uri as callback query - * argument to the server to authenticate to. - * - * *Note* that if the server decides to add additional query parameters to the uri (e.g. a token or secret), it - * will appear in the uri that is passed to the {@link UriHandler}. - * - * **Example** of an authentication flow: - * ```typescript - * vscode.window.registerUriHandler({ - * handleUri(uri: vscode.Uri): vscode.ProviderResult { - * if (uri.path === '/did-authenticate') { - * console.log(uri.toString()); - * } - * } - * }); - * - * const callableUri = await vscode.env.asExternalUri(vscode.Uri.parse(`${vscode.env.uriScheme}://my.extension/did-authenticate`)); - * await vscode.env.openExternal(callableUri); - * ``` - * - * *Note* that extensions should not cache the result of `asExternalUri` as the resolved uri may become invalid due to - * a system or user action — for example, in remote cases, a user may close a port forwarding tunnel that was opened by - * `asExternalUri`. - * - * #### Any other scheme - * - * Any other scheme will be handled as if the provided URI is a workspace URI. In that case, the method will return - * a URI which, when handled, will make the editor open the workspace. - * - * @return A uri that can be used on the client machine. - */ - export function asExternalUri(target: Uri): Thenable; - } - - //#endregion - //#region https://github.com/Microsoft/vscode/issues/15178 // TODO@API must be a class @@ -2697,7 +2485,8 @@ declare module 'vscode' { OpenBrowser = 2, OpenPreview = 3, Silent = 4, - Ignore = 5 + Ignore = 5, + OpenBrowserOnce = 6 } export class PortAttributes { @@ -2889,15 +2678,235 @@ declare module 'vscode' { //#endregion - //#region https://github.com/microsoft/vscode/issues/87110 @eamodio + //#region https://github.com/microsoft/vscode/issues/126258 @aeschli + + export interface StatusBarItem { + + /** + * Will be merged into StatusBarItem#tooltip + */ + tooltip2: string | MarkdownString | undefined; + + } + + //#endregion + + //#region https://github.com/microsoft/vscode/issues/126280 @mjbvz + + export interface NotebookCellData { + /** + * Mime type determines how the cell's `value` is interpreted. + * + * The mime selects which notebook renders is used to render the cell. + * + * If not set, internally the cell is treated as having a mime type of `text/plain`. + * Cells that set `language` to `markdown` instead are treated as `text/markdown`. + */ + mime?: string; + } + + export interface NotebookCell { + /** + * Mime type determines how the markup cell's `value` is interpreted. + * + * The mime selects which notebook renders is used to render the cell. + * + * If not set, internally the cell is treated as having a mime type of `text/plain`. + * Cells that set `language` to `markdown` instead are treated as `text/markdown`. + */ + mime: string | undefined; + } + + //#endregion + + //#region https://github.com/microsoft/vscode/issues/123713 @connor4312 + export interface TestRun { + /** + * Test coverage provider for this result. An extension can defer setting + * this until after a run is complete and coverage is available. + */ + coverageProvider?: TestCoverageProvider + // ... + } + + /** + * Provides information about test coverage for a test result. + * Methods on the provider will not be called until the test run is complete + */ + export interface TestCoverageProvider { + /** + * Returns coverage information for all files involved in the test run. + * @param token A cancellation token. + * @return Coverage metadata for all files involved in the test. + */ + provideFileCoverage(token: CancellationToken): ProviderResult; + + /** + * Give a FileCoverage to fill in more data, namely {@link FileCoverage.detailedCoverage}. + * The editor will only resolve a FileCoverage once, and onyl if detailedCoverage + * is undefined. + * + * @param coverage A coverage object obtained from {@link provideFileCoverage} + * @param token A cancellation token. + * @return The resolved file coverage, or a thenable that resolves to one. It + * is OK to return the given `coverage`. When no result is returned, the + * given `coverage` will be used. + */ + resolveFileCoverage?(coverage: T, token: CancellationToken): ProviderResult; + } + + /** + * A class that contains information about a covered resource. A count can + * be give for lines, branches, and functions in a file. + */ + export class CoveredCount { + /** + * Number of items covered in the file. + */ + covered: number; + /** + * Total number of covered items in the file. + */ + total: number; + + /** + * @param covered Value for {@link CovereredCount.covered} + * @param total Value for {@link CovereredCount.total} + */ + constructor(covered: number, total: number); + } - export interface Memento { + /** + * Contains coverage metadata for a file. + */ + export class FileCoverage { + /** + * File URI. + */ + readonly uri: Uri; + + /** + * Statement coverage information. If the reporter does not provide statement + * coverage information, this can instead be used to represent line coverage. + */ + statementCoverage: CoveredCount; + + /** + * Branch coverage information. + */ + branchCoverage?: CoveredCount; + + /** + * Function coverage information. + */ + functionCoverage?: CoveredCount; + + /** + * Detailed, per-statement coverage. If this is undefined, the editor will + * call {@link TestCoverageProvider.resolveFileCoverage} when necessary. + */ + detailedCoverage?: DetailedCoverage[]; + + /** + * Creates a {@link FileCoverage} instance with counts filled in from + * the coverage details. + * @param uri Covered file URI + * @param detailed Detailed coverage information + */ + static fromDetails(uri: Uri, details: readonly DetailedCoverage[]): FileCoverage; /** - * The stored keys. + * @param uri Covered file URI + * @param statementCoverage Statement coverage information. If the reporter + * does not provide statement coverage information, this can instead be + * used to represent line coverage. + * @param branchCoverage Branch coverage information + * @param functionCoverage Function coverage information */ - readonly keys: readonly string[]; + constructor( + uri: Uri, + statementCoverage: CoveredCount, + branchCoverage?: CoveredCount, + functionCoverage?: CoveredCount, + ); } + /** + * Contains coverage information for a single statement or line. + */ + export class StatementCoverage { + /** + * The number of times this statement was executed. If zero, the + * statement will be marked as un-covered. + */ + executionCount: number; + + /** + * Statement location. + */ + location: Position | Range; + + /** + * Coverage from branches of this line or statement. If it's not a + * conditional, this will be empty. + */ + branches: BranchCoverage[]; + + /** + * @param location The statement position. + * @param executionCount The number of times this statement was + * executed. If zero, the statement will be marked as un-covered. + * @param branches Coverage from branches of this line. If it's not a + * conditional, this should be omitted. + */ + constructor(executionCount: number, location: Position | Range, branches?: BranchCoverage[]); + } + + /** + * Contains coverage information for a branch of a {@link StatementCoverage}. + */ + export class BranchCoverage { + /** + * The number of times this branch was executed. If zero, the + * branch will be marked as un-covered. + */ + executionCount: number; + + /** + * Branch location. + */ + location?: Position | Range; + + /** + * @param executionCount The number of times this branch was executed. + * @param location The branch position. + */ + constructor(executionCount: number, location?: Position | Range); + } + + /** + * Contains coverage information for a function or method. + */ + export class FunctionCoverage { + /** + * The number of times this function was executed. If zero, the + * function will be marked as un-covered. + */ + executionCount: number; + + /** + * Function location. + */ + location: Position | Range; + + /** + * @param executionCount The number of times this function was executed. + * @param location The function position. + */ + constructor(executionCount: number, location: Position | Range); + } + + export type DetailedCoverage = StatementCoverage | FunctionCoverage; + //#endregion } diff --git a/src/server/server.ts b/src/server/server.ts index 1279d36..409cb97 100644 --- a/src/server/server.ts +++ b/src/server/server.ts @@ -72,6 +72,9 @@ function isKustoFile(document: TextDocument) { return !isNotebookCell(document) && document.languageId === 'kusto'; } function isInteractiveDocument(document: TextDocument) { + if (document.uri.toLowerCase().includes('vscode-interactive')) { + return true; + } if (!isNotebookCell(document)) { return false; } @@ -163,7 +166,9 @@ connection.onFoldingRanges(async ({ textDocument }) => { return null; } connection.console.log(`Provide folding ${document.uri.toString()}`); - return doFolding(document); + const ranges = await doFolding(document); + connection.sendNotification('foldingRanges', { uri: document.uri.toString(), foldingRanges: ranges }); + return ranges; }); // Make the text document manager listen on the connection