Skip to content

Commit

Permalink
Add executeSelection method to the extension host cell executor (#436)
Browse files Browse the repository at this point in the history
* Add `executeSelection` method to the extension host cell executor

* Add statement range provider

* Fix whitespace

* Register the statement range provider when activating the LSP

* Fix whitespace

* Fix code formatting
  • Loading branch information
juliasilge committed May 22, 2024
1 parent b47a56d commit f0d4538
Show file tree
Hide file tree
Showing 6 changed files with 118 additions and 18 deletions.
21 changes: 21 additions & 0 deletions apps/vscode/src/@types/hooks.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ declare module 'positron' {
export interface PositronApi {
version: string;
runtime: PositronRuntime;
languages: PositronLanguages;
window: PositronWindow;
}

Expand All @@ -19,6 +20,26 @@ declare module 'positron' {
): Thenable<boolean>;
}

export interface PositronLanguages {
registerStatementRangeProvider(
selector: vscode.DocumentSelector,
provider: StatementRangeProvider
): vscode.Disposable;
}

export interface StatementRangeProvider {
provideStatementRange(
document: vscode.TextDocument,
position: vscode.Position,
token: vscode.CancellationToken
): vscode.ProviderResult<StatementRange>;
}

export interface StatementRange {
readonly range: vscode.Range;
readonly code?: string;
}

export interface PositronWindow {
createPreviewPanel(
viewType: string,
Expand Down
2 changes: 1 addition & 1 deletion apps/vscode/src/host/executors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ const jupyterCellExecutor = (language: string) : VSCodeCellExecutor => ({
await commands.executeCommand("jupyter.execSelectionInteractive", code);
}
},
})
});

const pythonCellExecutor = jupyterCellExecutor("python");

Expand Down
74 changes: 64 additions & 10 deletions apps/vscode/src/host/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,13 @@
*
*/

import { Uri, WebviewPanelOptions, WebviewOptions, ViewColumn } from 'vscode';

import * as vscode from 'vscode';
import * as hooks from 'positron';

import { ExtensionHost, HostWebviewPanel } from '.';
import { ExtensionHost, HostWebviewPanel, HostStatementRangeProvider } from '.';
import { CellExecutor, cellExecutorForLanguage, executableLanguages, isKnitrDocument, pythonWithReticulate } from './executors';
import { TextDocument } from 'vscode';
import { MarkdownEngine } from '../markdown/engine';
import { virtualDoc, virtualDocUri, adjustedPosition } from "../vdoc/vdoc";

declare global {
function acquirePositronApi() : hooks.PositronApi;
Expand Down Expand Up @@ -49,14 +48,14 @@ export function hooksExtensionHost() : ExtensionHost {
// w/o runtimes so we support all languages)
executableLanguages,

cellExecutorForLanguage: async (language: string, document: TextDocument, engine: MarkdownEngine, silent?: boolean)
cellExecutorForLanguage: async (language: string, document: vscode.TextDocument, engine: MarkdownEngine, silent?: boolean)
: Promise<CellExecutor | undefined> => {
switch(language) {
// use hooks for known runtimes
case "python":
case "r":
return {
execute: async (blocks: string[], _editorUri?: Uri) : Promise<void> => {
execute: async (blocks: string[], _editorUri?: vscode.Uri) : Promise<void> => {
for (const block of blocks) {
let code = block;
if (language === "python" && isKnitrDocument(document, engine)) {
Expand All @@ -65,7 +64,10 @@ export function hooksExtensionHost() : ExtensionHost {
}
await hooksApi()?.runtime.executeCode(language, code, false);
}
}
},
executeSelection: async () : Promise<void> => {
await vscode.commands.executeCommand('workbench.action.positronConsole.executeCode', {languageId: language});
}
};

// delegate for other languages
Expand All @@ -74,11 +76,20 @@ export function hooksExtensionHost() : ExtensionHost {
}
},

registerStatementRangeProvider: (engine: MarkdownEngine): vscode.Disposable => {
const hooks = hooksApi();
if (hooks) {
return hooks.languages.registerStatementRangeProvider('quarto',
new EmbeddedStatementRangeProvider(engine));
}
return new vscode.Disposable(() => {});
},

createPreviewPanel: (
viewType: string,
title: string,
preserveFocus?: boolean,
options?: WebviewPanelOptions & WebviewOptions
options?: vscode.WebviewPanelOptions & vscode.WebviewOptions
): HostWebviewPanel => {

// create preview panel
Expand Down Expand Up @@ -106,10 +117,53 @@ class HookWebviewPanel implements HostWebviewPanel {

get webview() { return this.panel_.webview; };
get visible() { return this.panel_.visible; };
reveal(_viewColumn?: ViewColumn, preserveFocus?: boolean) {
reveal(_viewColumn?: vscode.ViewColumn, preserveFocus?: boolean) {
this.panel_.reveal(preserveFocus);
}
onDidChangeViewState = this.panel_.onDidChangeViewState;
onDidDispose = this.panel_.onDidDispose;
dispose() { this.panel_.dispose(); };
}
}

class EmbeddedStatementRangeProvider implements HostStatementRangeProvider {
private readonly _engine: MarkdownEngine;

constructor(
readonly engine: MarkdownEngine,
) {
this._engine = engine;
}

async provideStatementRange(
document: vscode.TextDocument,
position: vscode.Position,
token: vscode.CancellationToken): Promise<hooks.StatementRange | undefined> {
const vdoc = await virtualDoc(document, position, this._engine);
if (vdoc) {
const vdocUri = await virtualDocUri(vdoc, document.uri, "statementRange");
try {
return getStatementRange(vdocUri.uri, adjustedPosition(vdoc.language, position));
} catch (error) {
return undefined;
} finally {
if (vdocUri.cleanup) {
await vdocUri.cleanup();
}
}
} else {
return undefined;
}
};
}

async function getStatementRange(
uri: vscode.Uri,
position: vscode.Position,
) {
return await vscode.commands.executeCommand<hooks.StatementRange>(
"vscode.executeStatementRangeProvider",
uri,
position
);
}

32 changes: 27 additions & 5 deletions apps/vscode/src/host/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,19 @@ export interface HostWebviewPanel extends vscode.Disposable {
readonly onDidDispose: vscode.Event<void>;
}

export interface HostStatementRangeProvider {
provideStatementRange(
document: vscode.TextDocument,
position: vscode.Position,
token: vscode.CancellationToken
): vscode.ProviderResult<HostStatementRange>;
}

export interface HostStatementRange {
readonly range: vscode.Range;
readonly code?: string;
}

export interface ExtensionHost {

// code execution
Expand All @@ -45,6 +58,11 @@ export interface ExtensionHost {
silent?: boolean
) : Promise<CellExecutor | undefined>;

// statement range provider
registerStatementRangeProvider(
engine: MarkdownEngine,
): vscode.Disposable;

// preview
createPreviewPanel(
viewType: string,
Expand Down Expand Up @@ -73,14 +91,18 @@ function defaultExtensionHost() : ExtensionHost {
return {
executableLanguages: (visualMode: boolean, document: TextDocument, engine: MarkdownEngine) => {

const languages = executableLanguages();
const knitr = isKnitrDocument(document, engine);
const languages = executableLanguages();
const knitr = isKnitrDocument(document, engine);

// jupyter python (as distinct from knitr python) doesn't work in visual mode b/c
// jupyter.execSelectionInteractive wants a text editor to be active
return languages.filter(language => knitr || !visualMode || (language !== "python"));
// jupyter python (as distinct from knitr python) doesn't work in visual mode b/c
// jupyter.execSelectionInteractive wants a text editor to be active
return languages.filter(language => knitr || !visualMode || (language !== "python"));
},
cellExecutorForLanguage,
// in the default extension host, this is a noop:
registerStatementRangeProvider: (engine: MarkdownEngine): vscode.Disposable => {
return new vscode.Disposable(() => {});
},
createPreviewPanel,
};
}
4 changes: 3 additions & 1 deletion apps/vscode/src/lsp/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ import {
import { getHover, getSignatureHelpHover } from "../core/hover";
import { imageHover } from "../providers/hover-image";
import { LspInitializationOptions, QuartoContext } from "quarto-core";
import { extensionHost } from "../host";

let client: LanguageClient;

Expand Down Expand Up @@ -108,7 +109,8 @@ export async function activateLsp(
if (config.get("cells.signatureHelp.enabled", true)) {
middleware.provideSignatureHelp = embeddedSignatureHelpProvider(engine);
}

extensionHost().registerStatementRangeProvider(engine);

// create client options
const initializationOptions : LspInitializationOptions = {
quartoBinPath: quartoContext.binPath
Expand Down
3 changes: 2 additions & 1 deletion apps/vscode/src/vdoc/vdoc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,8 @@ export type VirtualDocAction =
"hover" |
"signature" |
"definition" |
"format";
"format" |
"statementRange";

export type VirtualDocUri = { uri: Uri, cleanup?: () => Promise<void> };

Expand Down

0 comments on commit f0d4538

Please sign in to comment.