Skip to content
Closed
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
19 changes: 11 additions & 8 deletions extensions/positron-r/src/hyperlink.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,19 +48,22 @@ function handleNotRunnable(code: string) {
}

async function handleManuallyRunnable(_runtime: RSession, code: string) {
const console = await positron.window.getConsoleForLanguage('r');

if (!console) {
// Not an expected path, but technically possible,
// and we should still do something somewhat useful.
try {
// Note that the `runtime` doesn't know anything about its console, so
// we can't use that to paste the code into the console. Instead, we use
// `positron.runtime.pasteText()` to paste into the most recently active
// R session, which should align with the one the user clicked the
// hyperlink in.
await positron.runtime.pasteText('r', code);
} catch (error) {
// Not an expected path, but technically possible if a console can't be
// started for some reason, and we should still do something somewhat
// useful.
vscode.window.showInformationMessage(vscode.l10n.t(
`Failed to locate an R console. Code hyperlink written to clipboard instead: \`${code}\`.`
));
vscode.env.clipboard.writeText(code);
return;
}

console.pasteText(code);
}

function handleAutomaticallyRunnable(runtime: RSession, code: string) {
Expand Down
27 changes: 10 additions & 17 deletions src/positron-dts/positron.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1310,13 +1310,6 @@ declare module 'positron' {
token: vscode.CancellationToken): vscode.ProviderResult<string>;
}

export interface Console {
/**
* Pastes text into the console.
*/
pasteText(text: string): void;
}

/**
* ConnectionsInput interface defines the structure for connection inputs.
*/
Expand Down Expand Up @@ -1528,16 +1521,6 @@ declare module 'positron' {
message: string,
okButtonTitle?: string): Thenable<null>;

/**
* Get the `Console` for a runtime `languageId`
*
* @param languageId The runtime language id to retrieve a `Console` for, i.e. 'r' or 'python'.
*
* @returns A Thenable that resolves to a `Console` or `undefined` if no `Console` for
* that `languageId` exists.
*/
export function getConsoleForLanguage(languageId: string): Thenable<Console | 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 Expand Up @@ -1674,6 +1657,16 @@ declare module 'positron' {
errorBehavior?: RuntimeErrorBehavior,
observer?: ExecutionObserver): Thenable<Record<string, any>>;

/**
* Paste text into a language runtime's console without running it.
*
* @param languageId The language ID of the console to paste into.
* @param text The text to paste.
*
* @returns A Thenable that resolves when the code has been pasted into the console.
*/
export function pasteText(languageId: string, text: string): Thenable<void>;

/**
* Register a language runtime manager with Positron.
*
Expand Down
29 changes: 0 additions & 29 deletions src/vs/workbench/api/browser/positron/mainThreadConsole.ts

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,13 @@
import { DisposableStore } from '../../../../base/common/lifecycle.js';
import { ExtHostConsoleServiceShape, ExtHostPositronContext, MainPositronContext, MainThreadConsoleServiceShape } from '../../common/positron/extHost.positron.protocol.js';
import { extHostNamedCustomer, IExtHostContext } from '../../../services/extensions/common/extHostCustomers.js';
import { IPositronConsoleInstance, IPositronConsoleService } from '../../../services/positronConsole/browser/interfaces/positronConsoleService.js';
import { MainThreadConsole } from './mainThreadConsole.js';
import { IPositronConsoleService } from '../../../services/positronConsole/browser/interfaces/positronConsoleService.js';

@extHostNamedCustomer(MainPositronContext.MainThreadConsoleService)
export class MainThreadConsoleService implements MainThreadConsoleServiceShape {

private readonly _disposables = new DisposableStore();

/**
* A Map of session ids to the respective console.
* Each session id maps to a single console.
* Multiple sessions could map to the same console, this happens
* when a user power-cycles the session for a console instance
* (i.e. shutdown session for console instance, then start a session for console instance)
*
* Kept in sync with consoles in `ExtHostConsoleService`
*/
private readonly _mainThreadConsolesBySessionId = new Map<string, MainThreadConsole>();

private readonly _proxy: ExtHostConsoleServiceShape;

constructor(
Expand All @@ -41,96 +29,15 @@ export class MainThreadConsoleService implements MainThreadConsoleServiceShape {
this._positronConsoleService.onDidChangeConsoleWidth((newWidth) => {
this._proxy.$onDidChangeConsoleWidth(newWidth);
}));

// Forward new positron console session id to the extension host, and then register it
// in the main thread too
this._disposables.add(
this._positronConsoleService.onDidStartPositronConsoleInstance((console) => {
const sessionId = console.sessionMetadata.sessionId;

// First update ext host
this._proxy.$addConsole(sessionId);

// Then update main thread
this.addConsole(sessionId, console);
})
);

// TODO:
// As of right now, we never delete console instances from the maps in
// `MainThreadConsoleService` and `ExtHostConsoleService` because we don't have a hook to
// know when a console is stopped. In particular, we should really call the `ExtHostConsole`
// `dispose()` method, which will ensure that any API callers who use the corresponding
// `Console` object will get a warning / error when calling the API of a closed console.
//
// this._disposables.add(
// this._positronConsoleService.onDidRemovePositronConsoleInstance((console) => {
// const sessionId = console.session.sessionId;
//
// // First update ext host
// this._proxy.$removeConsole(sessionId);
//
// // Then update main thread
// this.removeConsole(sessionId);
// })
// )
}

dispose(): void {
this._disposables.dispose();
}

private addConsole(sessionId: string, console: IPositronConsoleInstance) {
const mainThreadConsole = new MainThreadConsole(console);
this._mainThreadConsolesBySessionId.set(sessionId, mainThreadConsole);
}

// TODO:
// See comment in constructor
//
// private removeConsole(id: string) {
// // No dispose() method to call
// this._mainThreadConsolesByLanguageId.delete(id);
// }

// --- from extension host process

$getConsoleWidth(): Promise<number> {
return Promise.resolve(this._positronConsoleService.getConsoleWidth());
}

/**
* Get the session id of the active console for a particular language id
*
* @param languageId The language id to find a session id for.
*/
$getSessionIdForLanguage(languageId: string): Promise<string | undefined> {
// TODO: This is wrong in a multi-session world. It finds the
// first matching `languageId` in the map, but we likely want the "most
// recently activated and still alive" one. Reprex to prove it is wrong,
// which should eventually become a test:
// - Start R console 1
// - Start R console 2
// - Run `cli::cli_alert("{.run revdepcheck::cloud_summary()}")` in R
// console 2 and click the hyperlink.
// - The pasted code will incorrectly end up in R console 1.

for (let [sessionId, console] of this._mainThreadConsolesBySessionId.entries()) {
if (console.getLanguageId() === languageId) {
return Promise.resolve(sessionId);
}
}

return Promise.resolve(undefined);
}

$tryPasteText(sessionId: string, text: string): void {
const mainThreadConsole = this._mainThreadConsolesBySessionId.get(sessionId);

if (!mainThreadConsole) {
return;
}

mainThreadConsole.pasteText(text);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1609,6 +1609,10 @@ export class MainThreadLanguageRuntime
languageId, code, attribution, focus, allowIncomplete, mode, errorBehavior, executionId);
}

$pasteText(languageId: string, text: string): Promise<void> {
return this._positronConsoleService.pasteText(languageId, text);
}

public dispose(): void {
// Check each session that is still running and emit an exit event for it
// so we can clean it up properly on the front end.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ export function createPositronApiFactoryAndRegisterActors(accessor: ServicesAcce
const extHostPreviewPanels = rpcProtocol.set(ExtHostPositronContext.ExtHostPreviewPanel, new ExtHostPreviewPanels(rpcProtocol, extHostWebviews, extHostWorkspace));
const extHostModalDialogs = rpcProtocol.set(ExtHostPositronContext.ExtHostModalDialogs, new ExtHostModalDialogs(rpcProtocol));
const extHostContextKeyService = rpcProtocol.set(ExtHostPositronContext.ExtHostContextKeyService, new ExtHostContextKeyService(rpcProtocol));
const extHostConsoleService = rpcProtocol.set(ExtHostPositronContext.ExtHostConsoleService, new ExtHostConsoleService(rpcProtocol, extHostLogService));
const extHostConsoleService = rpcProtocol.set(ExtHostPositronContext.ExtHostConsoleService, new ExtHostConsoleService(rpcProtocol));
const extHostPlotsService = rpcProtocol.set(ExtHostPositronContext.ExtHostPlotsService, new ExtHostPlotsService(rpcProtocol));
const extHostMethods = rpcProtocol.set(ExtHostPositronContext.ExtHostMethods,
new ExtHostMethods(rpcProtocol, extHostEditors, extHostDocuments, extHostModalDialogs,
Expand All @@ -93,6 +93,9 @@ export function createPositronApiFactoryAndRegisterActors(accessor: ServicesAcce
const extensionId = extension.identifier.value;
return extHostLanguageRuntime.executeCode(languageId, code, extensionId, focus, allowIncomplete, mode, errorBehavior, observer);
},
pasteText(languageId, text): Thenable<void> {
return extHostLanguageRuntime.pasteText(languageId, text);
},
registerLanguageRuntimeManager(
languageId: string,
manager: positron.LanguageRuntimeManager): vscode.Disposable {
Expand Down Expand Up @@ -183,9 +186,6 @@ export function createPositronApiFactoryAndRegisterActors(accessor: ServicesAcce
showSimpleModalDialogMessage(title: string, message: string, okButtonTitle?: string): Thenable<null> {
return extHostModalDialogs.showSimpleModalDialogMessage(title, message, okButtonTitle);
},
getConsoleForLanguage(languageId: string) {
return extHostConsoleService.getConsoleForLanguage(languageId);
},
get onDidChangeConsoleWidth() {
return extHostConsoleService.onDidChangeConsoleWidth;
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export interface MainThreadLanguageRuntimeShape extends IDisposable {
$completeLanguageRuntimeDiscovery(): void;
$unregisterLanguageRuntime(handle: number): void;
$executeCode(languageId: string, extensionId: string, code: string, focus: boolean, allowIncomplete?: boolean, mode?: RuntimeCodeExecutionMode, errorBehavior?: RuntimeErrorBehavior, executionId?: string): Promise<string>;
$pasteText(languageId: string, text: string): Promise<void>;
$getPreferredRuntime(languageId: string): Promise<ILanguageRuntimeMetadata | undefined>;
$getActiveSessions(): Promise<RuntimeSessionMetadata[]>;
$getForegroundSession(): Promise<string | undefined>;
Expand Down Expand Up @@ -108,14 +109,10 @@ export interface ExtHostContextKeyServiceShape { }

export interface MainThreadConsoleServiceShape {
$getConsoleWidth(): Promise<number>;
$getSessionIdForLanguage(languageId: string): Promise<string | undefined>;
$tryPasteText(sessionId: string, text: string): void;
}

export interface ExtHostConsoleServiceShape {
$onDidChangeConsoleWidth(newWidth: number): void;
$addConsole(sessionId: string): void;
$removeConsole(sessionId: string): void;
}

export interface MainThreadMethodsShape { }
Expand Down
57 changes: 0 additions & 57 deletions src/vs/workbench/api/common/positron/extHostConsole.ts

This file was deleted.

Loading