Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 117 additions & 0 deletions extensions/positron-zed/src/positronZedLanguageRuntime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,13 @@ const HelpLines = [
'busy X Y - Simulates an interuptible busy state for X seconds that takes Y seconds to interrupt (default X = 5, Y = 1)',
'cd X - Changes the current working directory to X, or to a random directory if X is not specified',
'clock - Show a plot containing a clock, using the notebook renderer API',
'console active - Show the current active console editor URI, language, and text (tests positron.window.activeConsoleEditor)',
'console watch - Subscribe to onDidChangeActiveConsoleEditor and print each change',
'console watch stop - Stop watching for active console changes',
'console edit X - Replace the console input with X via TextEditor.edit() (tests the Editor interface)',
'console append X - Insert X at the end of the console input via TextEditor.edit()',
'console insert X - Insert X at the cursor via TextEditor.insertSnippet()',
'console select - Select the entire console input and report TextEditor.selection',
'connection X - Create a database connection, optionally named X',
'connection close - Close a random database connection',
'code X Y - Simulates a successful X line input with Y lines of output (where X >= 1 and Y >= 0)',
Expand Down Expand Up @@ -191,6 +198,11 @@ export class PositronZedRuntimeSession implements positron.LanguageRuntimeSessio
*/
private _busyInterruptSeconds: number;

/**
* Disposable for the active `console watch` subscription; undefined when not watching.
*/
private _consoleWatchDisposable: vscode.Disposable | undefined;

/**
* The number of seconds by which a shutdown should be delayed. This is used
* to help simulate a state in which a runtime locks up during shutdown.
Expand Down Expand Up @@ -470,6 +482,13 @@ export class PositronZedRuntimeSession implements positron.LanguageRuntimeSessio
this.simulateConnection(id, code, title);
}
return;
} else if (match = code.match(/^console (edit|insert|append|select)(?: ([\s\S]*))?$/)) {
// Exercise the editing side of positron.window.activeConsoleEditor's
// vscode.TextEditor interface (edit(), insertSnippet(), selection).
const subcommand = match[1];
const arg = (match.length > 2 && match[2] !== undefined) ? match[2] : '';
this.simulateConsoleEditorCommand(id, code, subcommand, arg);
return;
}

// Process the "code".
Expand Down Expand Up @@ -938,6 +957,43 @@ export class PositronZedRuntimeSession implements positron.LanguageRuntimeSessio
break;
}

case 'console active': {
const editor = positron.window.activeConsoleEditor;
const msg = editor
? `Active console editor: ${editor.document.uri.toString()}\nLanguage: ${editor.document.languageId}\nText: ${JSON.stringify(editor.document.getText())}`
: `Active console editor: none (no console is currently active)`;
this.simulateSuccessfulCodeExecution(id, code, msg);
break;
}

case 'console watch': {
if (this._consoleWatchDisposable) {
this.simulateSuccessfulCodeExecution(id, code, `Already watching. Use 'console watch stop' to stop.`);
break;
}
const watchId = id;
this._consoleWatchDisposable = positron.window.onDidChangeActiveConsoleEditor((editor) => {
const msg = editor
? `[console watch] Active console changed: ${editor.document.uri.toString()} (${editor.document.languageId})`
: `[console watch] Active console changed: none`;
this.simulateOutputMessage(watchId, msg + '\n');
});
this.context.subscriptions.push(this._consoleWatchDisposable);
this.simulateSuccessfulCodeExecution(id, code, `Watching for active console changes. Switch consoles to see events. Use 'console watch stop' to stop.`);
break;
}

case 'console watch stop': {
if (this._consoleWatchDisposable) {
this._consoleWatchDisposable.dispose();
this._consoleWatchDisposable = undefined;
this.simulateSuccessfulCodeExecution(id, code, `Stopped watching for active console changes.`);
} else {
this.simulateSuccessfulCodeExecution(id, code, `Not currently watching. Use 'console watch' to start.`);
}
break;
}

default: {
this.simulateUnsuccessfulCodeExecution(id, code, 'Unknown Command', `Error. '${code}' not recognized.\n`, []);
break;
Expand Down Expand Up @@ -1985,6 +2041,67 @@ export class PositronZedRuntimeSession implements positron.LanguageRuntimeSessio
this.simulateIdleState(parentId);
}

/**
* Exercises the mutating side of the `vscode.TextEditor` interface exposed by
* `positron.window.activeConsoleEditor`. This complements the read-only `console active`
* and `console watch` commands by verifying that `edit()`, `insertSnippet()`, and
* `selection` operate on the live console input.
* @param parentId The parent identifier.
* @param code The originating command text.
* @param subcommand The editor operation to perform: `edit`, `append`, `insert`, or `select`.
* @param arg The text argument for `edit`, `append`, and `insert` (ignored for `select`).
*/
private simulateConsoleEditorCommand(parentId: string, code: string, subcommand: string, arg: string) {
const editor = positron.window.activeConsoleEditor;
if (!editor) {
this.simulateUnsuccessfulCodeExecution(parentId, code, 'No Active Console',
`No console editor is currently active.\n`, []);
return;
}

// The full range of the current console input, used for whole-document operations.
const fullRange = () => new vscode.Range(
editor.document.positionAt(0),
editor.document.positionAt(editor.document.getText().length));

switch (subcommand) {
case 'edit': {
// Replace the entire input with `arg`.
editor.edit(editBuilder => editBuilder.replace(fullRange(), arg)).then((applied) => {
this.simulateSuccessfulCodeExecution(parentId, code,
`edit() replace applied: ${applied}\nText: ${JSON.stringify(editor.document.getText())}`);
});
break;
}
case 'append': {
// Insert `arg` at the end of the input.
editor.edit(editBuilder => editBuilder.insert(fullRange().end, arg)).then((applied) => {
this.simulateSuccessfulCodeExecution(parentId, code,
`edit() insert applied: ${applied}\nText: ${JSON.stringify(editor.document.getText())}`);
});
break;
}
case 'insert': {
// Insert `arg` as a snippet at the current cursor position.
editor.insertSnippet(new vscode.SnippetString(arg)).then((applied) => {
this.simulateSuccessfulCodeExecution(parentId, code,
`insertSnippet() applied: ${applied}\nText: ${JSON.stringify(editor.document.getText())}`);
});
break;
}
case 'select': {
// Set the selection to cover the whole input, then read it back.
editor.selection = new vscode.Selection(fullRange().start, fullRange().end);
const selection = editor.selection;
this.simulateSuccessfulCodeExecution(parentId, code,
`Selection: [${selection.start.line}:${selection.start.character} - ` +
`${selection.end.line}:${selection.end.character}]\n` +
`Selected text: ${JSON.stringify(editor.document.getText(selection))}`);
break;
}
}
}

/**
* Simulates unsuccessful code execution.
* @param parentId The parent ID.
Expand Down
14 changes: 14 additions & 0 deletions src/positron-dts/positron.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2584,6 +2584,20 @@ declare module 'positron' {
*/
export function getConsoleForLanguage(languageId: string): Thenable<Console | undefined>;

/**
* The currently active console editor, or `undefined` if no console is active.
* Provides the full `vscode.TextEditor` API for the console input, including
* `document`, `selection`, `edit()`, and `insertSnippet()`.
*
* Note: this editor is intentionally NOT `vscode.window.activeTextEditor`.
*/
export const activeConsoleEditor: vscode.TextEditor | undefined;

/**
* An event that fires when the active console editor changes.
*/
export const onDidChangeActiveConsoleEditor: vscode.Event<vscode.TextEditor | undefined>;

/**
* Fires when the width of the console input changes. The new width is passed as
* a number, which represents the number of characters that can fit in the
Expand Down
73 changes: 71 additions & 2 deletions src/vs/workbench/api/browser/mainThreadDocumentsAndEditors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/

import { Event } from '../../../base/common/event.js';
import { combinedDisposable, DisposableStore, DisposableMap } from '../../../base/common/lifecycle.js';
import { combinedDisposable, DisposableStore, DisposableMap, IDisposable, toDisposable } from '../../../base/common/lifecycle.js';
import { ICodeEditor, isCodeEditor, isDiffEditor, IActiveCodeEditor } from '../../../editor/browser/editorBrowser.js';
import { ICodeEditorService } from '../../../editor/browser/services/codeEditorService.js';
import { IEditor } from '../../../editor/common/editorCommon.js';
Expand Down Expand Up @@ -33,6 +33,9 @@ import { IPaneCompositePartService } from '../../services/panecomposite/browser/
import { ViewContainerLocation } from '../../common/views.js';
import { IConfigurationService } from '../../../platform/configuration/common/configuration.js';
import { IQuickDiffModelService } from '../../contrib/scm/browser/quickDiffModel.js';
// --- Start Positron ---
import { IMainThreadConsoleEditorManager, MainPositronContext } from '../common/positron/extHost.positron.protocol.js';
// --- End Positron ---


class TextEditorSnapshot {
Expand Down Expand Up @@ -274,7 +277,9 @@ class MainThreadDocumentAndEditorStateComputer {
}

@extHostCustomer
export class MainThreadDocumentsAndEditors implements IMainThreadEditorLocator {
// --- Start Positron ---
export class MainThreadDocumentsAndEditors implements IMainThreadEditorLocator, IMainThreadConsoleEditorManager {
// --- End Positron ---

private readonly _toDispose = new DisposableStore();
private readonly _proxy: ExtHostDocumentsAndEditorsShape;
Expand Down Expand Up @@ -310,6 +315,11 @@ export class MainThreadDocumentsAndEditors implements IMainThreadEditorLocator {

// It is expected that the ctor of the state computer calls our `_onDelta`.
this._toDispose.add(new MainThreadDocumentAndEditorStateComputer(delta => this._onDelta(delta), _modelService, codeEditorService, this._editorService, paneCompositeService));

// --- Start Positron ---
// Register this instance so MainThreadConsoleService can reach it via getRaw.
extHostContext.set(MainPositronContext.MainThreadConsoleEditorManager, this);
// --- End Positron ---
}

dispose(): void {
Expand Down Expand Up @@ -433,4 +443,63 @@ export class MainThreadDocumentsAndEditors implements IMainThreadEditorLocator {
getEditor(id: string): MainThreadTextEditor | undefined {
return this._textEditors.get(id);
}

// --- Start Positron ---
/**
* Registers a console input editor so it is accessible as a `vscode.TextEditor` via
* `positron.window.activeConsoleEditor`. The editor is NOT set as `vscode.window.activeTextEditor`.
*
* @param id A stable id for this editor (e.g. `console-<sessionId>`)
* @param codeEditor The Monaco editor backing the console input
* @returns A disposable that removes the editor from the ext host when disposed
*/
registerConsoleEditor(id: string, codeEditor: ICodeEditor): IDisposable {
const store = new DisposableStore();

const doRegister = (model: ITextModel) => {
const editor = new MainThreadTextEditor(
id,
model,
codeEditor,
{ onGainedFocus() { }, onLostFocus() { } },
this._mainThreadDocuments,
this._modelService,
this._clipboardService,
);

this._textEditors.set(id, editor);
this._mainThreadEditors.handleTextEditorAdded(editor);
this._proxy.$acceptDocumentsAndEditorsDelta({
addedEditors: [this._toTextEditorAddData(editor)],
});

store.add(toDisposable(() => {
this._textEditors.delete(id);
editor.dispose();
this._mainThreadEditors.handleTextEditorRemoved(id);
this._proxy.$acceptDocumentsAndEditorsDelta({ removedEditors: [id] });
}));
};

const model = codeEditor.getModel();
if (model) {
doRegister(model);
} else {
// The console input assigns its code editor before attaching the text model, so the
// model may not be present yet. Wait for it rather than silently skipping registration,
// otherwise `positron.window.activeConsoleEditor` would never resolve this editor.
const sub = store.add(codeEditor.onDidChangeModel(e => {
if (e.newModelUrl) {
const newModel = codeEditor.getModel();
if (newModel) {
sub.dispose();
doRegister(newModel);
}
}
}));
}

return store;
}
// --- End Positron ---
}
Loading
Loading