Skip to content

Commit

Permalink
feat: Run Markdown codeblocks (#201)
Browse files Browse the repository at this point in the history
DH-18353: Run Markdown codeblocks

Testing
Open any Markdown file containing python or groovy codeblocks. Can use
this one for simple example:

````markdown
This is a test Markdown file containing some inline codeblocks.

```python
from deephaven import time_table

simple_ticking = time_table("PT2S")
```

Here is a groovy example

```groovy
simple_ticking = timeTable("PT2S")
```
````

A "Run Code Block" code lens should appear above each codeblock.
Clicking it should run the codeblock against a DH server.

---------

Co-authored-by: Mike Bender <[email protected]>
  • Loading branch information
bmingles and mofojed authored Jan 10, 2025
1 parent 9c9f5b1 commit 28f58b6
Show file tree
Hide file tree
Showing 13 changed files with 247 additions and 109 deletions.
9 changes: 9 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,11 @@
"title": "Run Deephaven File",
"icon": "$(run-all)"
},
{
"command": "vscode-deephaven.runMarkdownCodeblock",
"title": "Run Deephaven Markdown Codeblock",
"icon": "$(run)"
},
{
"command": "vscode-deephaven.runSelection",
"title": "Run Deephaven Selected Lines",
Expand Down Expand Up @@ -677,6 +682,10 @@
"command": "vscode-deephaven.runCode",
"when": "editorLangId == python || editorLangId == groovy"
},
{
"command": "vscode-deephaven.runMarkdownCodeblock",
"when": "false"
},
{
"command": "vscode-deephaven.runSelection",
"when": "editorLangId == python || editorLangId == groovy"
Expand Down
1 change: 1 addition & 0 deletions src/common/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export const REFRESH_SERVER_CONNECTION_TREE_CMD = cmd(
);
export const REFRESH_VARIABLE_PANELS_CMD = cmd('refreshVariablePanels');
export const RUN_CODE_COMMAND = cmd('runCode');
export const RUN_MARKDOWN_CODEBLOCK_CMD = cmd('runMarkdownCodeBlock');
export const RUN_SELECTION_COMMAND = cmd('runSelection');
export const SEARCH_CONNECTIONS_CMD = cmd('searchConnections');
export const SEARCH_PANELS_CMD = cmd('searchPanels');
Expand Down
61 changes: 38 additions & 23 deletions src/controllers/ConnectionController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import {
createConnectionQuickPickOptions,
createConnectStatusBarItem,
getConsoleType,
getEditorForUri,
isSupportedLanguageId,
Logger,
updateConnectionStatusBarItem,
Expand Down Expand Up @@ -135,7 +134,11 @@ export class ConnectionController extends ControllerBase implements Disposable {

const editor = vscode.window.activeTextEditor;

if (editor == null || !isSupportedLanguageId(editor.document.languageId)) {
if (
editor == null ||
(!isSupportedLanguageId(editor.document.languageId) &&
editor.document.languageId !== 'markdown')
) {
this._connectStatusBarItem.hide();
return;
}
Expand All @@ -158,7 +161,8 @@ export class ConnectionController extends ControllerBase implements Disposable {

connectEditor = async (
connectionOrServer: ConnectionState | ServerState,
editor: vscode.TextEditor
uri: vscode.Uri,
languageId: string
): Promise<void> => {
updateConnectionStatusBarItem(this._connectStatusBarItem, 'connecting');

Expand All @@ -167,7 +171,7 @@ export class ConnectionController extends ControllerBase implements Disposable {
if ('url' in connectionOrServer) {
const cn = await this._serverManager.connectToServer(
connectionOrServer.url,
getConsoleType(editor.document.languageId)
getConsoleType(languageId)
);

if (cn == null) {
Expand All @@ -179,7 +183,11 @@ export class ConnectionController extends ControllerBase implements Disposable {
}

try {
await this._serverManager.setEditorConnection(editor, connectionOrServer);
await this._serverManager.setEditorConnection(
uri,
languageId,
connectionOrServer
);
} catch (err) {
updateConnectionStatusBarItem(this._connectStatusBarItem, 'disconnected');

Expand All @@ -202,27 +210,27 @@ export class ConnectionController extends ControllerBase implements Disposable {

/**
* Get or create a connection for the given uri.
* @param uri
* @param uri Uri to get or create a connection for.
* @param languageId Language id to use for the connection.
*/
getOrCreateConnection = async (
uri: vscode.Uri
uri: vscode.Uri,
languageId: string
): Promise<ConnectionState | null> => {
assertDefined(this._outputChannel, 'outputChannel');
assertDefined(this._serverManager, 'serverManager');
assertDefined(this._toaster, 'toaster');

const editor = await getEditorForUri(uri);

// Get existing connection for the editor
let dhService = await this._serverManager.getEditorConnection(editor);
let dhService = await this._serverManager.getEditorConnection(uri);

if (dhService != null) {
return dhService;
}

const supportingConnections = await getConnectionsForConsoleType(
this._serverManager.getConnections(),
editor.document.languageId as ConsoleType
languageId as ConsoleType
);

const availableServers = this._serverManager.getServers({
Expand All @@ -233,29 +241,29 @@ export class ConnectionController extends ControllerBase implements Disposable {
if (supportingConnections.length === 1 && availableServers.length === 0) {
// If we only have 1 supporting connection, and no available servers, use
// the available connection.
await this.connectEditor(supportingConnections[0], editor);
await this.connectEditor(supportingConnections[0], uri, languageId);
} else if (
// If there are no active connections that can support the editor, and we
// only have 1 available server, just connect to it instead of prompting the
// user to select an available server / connection.
supportingConnections.length === 0 &&
availableServers.length === 1
) {
await this.connectEditor(availableServers[0], editor);
await this.connectEditor(availableServers[0], uri, languageId);
} else {
// If there are multiple options to select, prompt the user to select one.
const isSelected = await this.onPromptUserToSelectConnection();
const isSelected = await this.onPromptUserToSelectConnection(languageId);

// User cancelled the selection or an error occurred
if (!isSelected) {
return null;
}
}

dhService = await this._serverManager.getEditorConnection(editor);
dhService = await this._serverManager.getEditorConnection(uri);

if (dhService == null) {
const logMsg = `No active connection found supporting '${editor.document.languageId}' console type.`;
const logMsg = `No active connection found supporting '${languageId}' console type.`;
logger.debug(logMsg);
this._outputChannel.appendLine(logMsg);
this._toaster.error(logMsg);
Expand Down Expand Up @@ -341,12 +349,20 @@ export class ConnectionController extends ControllerBase implements Disposable {
* 2. A list of running servers composed of:
* - DHC servers that don't yet have a connection
* - All running DHE servers
* @param languageId Optional language id to use for the connection. Defaults
* to the language id of the active editor.
*/
onPromptUserToSelectConnection = async (): Promise<boolean> => {
onPromptUserToSelectConnection = async (
languageId?: string
): Promise<boolean> => {
assertDefined(vscode.window.activeTextEditor, 'activeTextEditor');
assertDefined(this._serverManager, 'serverManager');

const editor = vscode.window.activeTextEditor;
const uri = editor.document.uri;
if (languageId == null) {
languageId = editor.document.languageId;
}

const updateStatusPromise = this._serverManager.updateStatus();

Expand Down Expand Up @@ -382,12 +398,11 @@ export class ConnectionController extends ControllerBase implements Disposable {
const connectionsForConsoleType: ConnectionState[] =
await getConnectionsForConsoleType(
this._serverManager.getConnections(),
editor.document.languageId as ConsoleType
languageId as ConsoleType
);

const editorActiveConnectionUrl = this._serverManager.getUriConnection(
editor.document.uri
)?.serverUrl;
const editorActiveConnectionUrl =
this._serverManager.getUriConnection(uri)?.serverUrl;

let selectedCnResult: ConnectionState | ServerState | null = null;

Expand All @@ -396,7 +411,7 @@ export class ConnectionController extends ControllerBase implements Disposable {
createConnectionQuickPickOptions(
[...runningDHCServersWithoutConnections, ...runningDHEServers],
connectionsForConsoleType,
editor.document.languageId,
languageId,
editorActiveConnectionUrl
)
);
Expand All @@ -405,7 +420,7 @@ export class ConnectionController extends ControllerBase implements Disposable {
return false;
}

await this.connectEditor(selectedCnResult, editor);
await this.connectEditor(selectedCnResult, uri, languageId);

return true;
} catch (err) {
Expand Down
63 changes: 54 additions & 9 deletions src/controllers/ExtensionController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
REFRESH_SERVER_CONNECTION_TREE_CMD,
REFRESH_SERVER_TREE_CMD,
RUN_CODE_COMMAND,
RUN_MARKDOWN_CODEBLOCK_CMD,
RUN_SELECTION_COMMAND,
SEARCH_CONNECTIONS_CMD,
SEARCH_PANELS_CMD,
Expand All @@ -34,6 +35,7 @@ import {
ServerConnectionTreeProvider,
ServerConnectionPanelTreeProvider,
runSelectedLinesHoverProvider,
RunMarkdownCodeBlockCodeLensProvider,
} from '../providers';
import {
DheJsApiCache,
Expand Down Expand Up @@ -160,11 +162,16 @@ export class ExtensionController implements Disposable {
*/
initializeCodeLenses = (): void => {
const codelensProvider = new RunCommandCodeLensProvider();
const markdownCodelensProvider = new RunMarkdownCodeBlockCodeLensProvider();

this._context.subscriptions.push(
codelensProvider,
vscode.languages.registerCodeLensProvider('groovy', codelensProvider),
vscode.languages.registerCodeLensProvider('python', codelensProvider)
vscode.languages.registerCodeLensProvider('python', codelensProvider),
vscode.languages.registerCodeLensProvider(
'markdown',
markdownCodelensProvider
)
);
};

Expand Down Expand Up @@ -468,6 +475,12 @@ export class ExtensionController implements Disposable {
/** Run all code in active editor */
this.registerCommand(RUN_CODE_COMMAND, this.onRunCode);

/** Run Markdown codeblock */
this.registerCommand(
RUN_MARKDOWN_CODEBLOCK_CMD,
this.onRunMarkdownCodeblock
);

/** Run selected code in active editor */
this.registerCommand(RUN_SELECTION_COMMAND, this.onRunSelectedCode);

Expand Down Expand Up @@ -626,7 +639,11 @@ export class ExtensionController implements Disposable {

const editor = await vscode.window.showTextDocument(doc);

this._serverManager?.setEditorConnection(editor, dhService);
this._serverManager?.setEditorConnection(
editor.document.uri,
editor.document.languageId,
dhService
);
};

/**
Expand Down Expand Up @@ -676,16 +693,37 @@ export class ExtensionController implements Disposable {
await this._serverManager?.updateStatus();
};

/**
* Run code block in markdown.
* @param uri The uri of the editor
* @param languageId The languageId of the code block
* @param range The range of the code block
*/
onRunMarkdownCodeblock = async (
uri: vscode.Uri,
languageId: string,
range: vscode.Range
): Promise<void> => {
this.onRunCode(uri, undefined, [range], languageId);
};

/**
* Run all code in editor for given uri.
* @param uri
* @param arg
* @param selectionOnly
* @param uri The uri of the editor
* @param _arg Unused 2nd argument
* @param constrainTo Optional arg to constrain the code to run.
* - If 'selection', run only selected code in the editor.
* - If `undefined`, run all code in the editor.
* - If an array of vscode.Range, run only the code in the ranges (Note that
* partial lines will be expanded to include the full line content).
* @param languageId Optional languageId to run the code as. If none provided,
* use the languageId of the editor.
*/
onRunCode = async (
uri?: vscode.Uri,
_arg?: { groupId: number },
selectionOnly?: boolean
constrainTo?: 'selection' | vscode.Range[],
languageId?: string
): Promise<void> => {
assertDefined(this._connectionController, 'connectionController');

Expand All @@ -696,11 +734,18 @@ export class ExtensionController implements Disposable {
assertDefined(uri, 'uri');

const editor = await getEditorForUri(uri);
if (languageId == null) {
languageId = editor.document.languageId;
}

const connectionState =
await this._connectionController.getOrCreateConnection(uri);
await this._connectionController.getOrCreateConnection(uri, languageId);

if (isInstanceOf(connectionState, DhcService)) {
await connectionState?.runEditorCode(editor, selectionOnly === true);
const ranges: readonly vscode.Range[] | undefined =
constrainTo === 'selection' ? editor.selections : constrainTo;

await connectionState?.runCode(editor.document, languageId, ranges);
}
};

Expand All @@ -713,7 +758,7 @@ export class ExtensionController implements Disposable {
uri?: vscode.Uri,
arg?: { groupId: number }
): Promise<void> => {
this.onRunCode(uri, arg, true);
this.onRunCode(uri, arg, 'selection');
};

/**
Expand Down
6 changes: 5 additions & 1 deletion src/controllers/ServerConnectionTreeDragAndDropController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,11 @@ export class ServerConnectionTreeDragAndDropController
const editor = await getEditorForUri(uri);

try {
await this.serverManager.setEditorConnection(editor, target);
await this.serverManager.setEditorConnection(
editor.document.uri,
editor.document.languageId,
target
);
} catch {}
};
}
11 changes: 2 additions & 9 deletions src/providers/RunCommandCodeLensProvider.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as vscode from 'vscode';
import { ICON_ID } from '../common';
import { ICON_ID, RUN_CODE_COMMAND } from '../common';
import type { Disposable } from '../types';

/**
Expand Down Expand Up @@ -35,18 +35,11 @@ export class RunCommandCodeLensProvider
const codeLenses: vscode.CodeLens[] = [
new vscode.CodeLens(new vscode.Range(0, 0, 0, 0), {
title: `$(${ICON_ID.runAll}) Run Deephaven File`,
command: 'vscode-deephaven.runCode',
command: RUN_CODE_COMMAND,
arguments: [document.uri],
}),
];

return codeLenses;
}

resolveCodeLens?(
codeLens: vscode.CodeLens,
_token: vscode.CancellationToken
): vscode.ProviderResult<vscode.CodeLens> {
return codeLens;
}
}
Loading

0 comments on commit 28f58b6

Please sign in to comment.