Skip to content
Open
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
10 changes: 8 additions & 2 deletions extensions/positron-r/src/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,9 +203,15 @@ export class RSession implements positron.LanguageRuntimeSession, vscode.Disposa
throw new Error(`Debugging is not supported in R sessions`);
}

execute(code: string, id: string, mode: positron.RuntimeCodeExecutionMode, errorBehavior: positron.RuntimeErrorBehavior): void {
execute(
code: string,
id: string,
mode: positron.RuntimeCodeExecutionMode,
errorBehavior: positron.RuntimeErrorBehavior,
codeLocation?: vscode.Location,
): void {
if (this._kernel) {
this._kernel.execute(code, id, mode, errorBehavior);
this._kernel.execute(code, id, mode, errorBehavior, codeLocation);
} else {
throw new Error(`Cannot execute '${code}'; kernel not started`);
}
Expand Down
12 changes: 10 additions & 2 deletions extensions/positron-supervisor/src/KallichoreSession.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import * as positron from 'positron';
import * as os from 'os';
import * as path from 'path';
import * as fs from 'fs';
import * as typesConverters from './jupyter/TypesConverters';
import { CommBackendMessage, JupyterKernelExtra, JupyterKernelSpec, JupyterLanguageRuntimeSession, JupyterSession, Comm } from './positron-supervisor';
import { ActiveSession, ConnectionInfo, DefaultApi, InterruptMode, NewSession, RestartSession, StartupEnvironment, Status, VarAction, VarActionType } from './kcclient/api';
import { JupyterMessage } from './jupyter/JupyterMessage';
Expand Down Expand Up @@ -693,10 +694,13 @@ export class KallichoreSession implements JupyterLanguageRuntimeSession {
* @param mode The execution mode
* @param errorBehavior What to do if an error occurs
*/
execute(code: string,
execute(
code: string,
id: string,
mode: positron.RuntimeCodeExecutionMode,
errorBehavior: positron.RuntimeErrorBehavior): void {
errorBehavior: positron.RuntimeErrorBehavior,
codeLocation?: vscode.Location,
): void {

// Translate the parameters into a Jupyter execute request
const request: JupyterExecuteRequest = {
Expand All @@ -708,6 +712,10 @@ export class KallichoreSession implements JupyterLanguageRuntimeSession {
stop_on_error: errorBehavior === positron.RuntimeErrorBehavior.Stop,
};

if (codeLocation) {
request.positron = { code_location: typesConverters.JupyterPositronLocation.from(codeLocation, code) };
}

// Create and send the execute request
const execute = new ExecuteRequest(id, request);
this.sendRequest(execute).then((reply) => {
Expand Down
9 changes: 9 additions & 0 deletions extensions/positron-supervisor/src/jupyter/ExecuteRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import { JupyterChannel } from './JupyterChannel';
import { JupyterDisplayData } from './JupyterDisplayData';
import { JupyterMessageType } from './JupyterMessageType.js';
import { JupyterPositronLocation, JupyterPositronRange } from './JupyterPositronTypes';
import { JupyterRequest } from './JupyterRequest';


Expand Down Expand Up @@ -41,6 +42,14 @@ export interface JupyterExecuteRequest {

/** Whether the kernel should stop the execution queue when an error occurs */
stop_on_error: boolean;

/** Positron extension */
positron?: JupyterPositronExecuteRequest;
}

export interface JupyterPositronExecuteRequest {
/** Location of `code`. Encoded in UTF-16 offsets. */
code_location?: JupyterPositronLocation;
}

/**
Expand Down
22 changes: 22 additions & 0 deletions extensions/positron-supervisor/src/jupyter/JupyterPositronTypes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*---------------------------------------------------------------------------------------------
* Copyright (C) 2025 Posit Software, PBC. All rights reserved.
* Licensed under the Elastic License 2.0. See LICENSE.txt for license information.
*--------------------------------------------------------------------------------------------*/

export interface JupyterPositronLocation {
uri: string;
range: JupyterPositronRange;
}

export interface JupyterPositronRange {
start: JupyterPositronPosition;
end: JupyterPositronPosition;
}

// See https://jupyter-client.readthedocs.io/en/stable/messaging.html#cursor-pos-unicode-note
// regarding choice of offset in unicode points
export interface JupyterPositronPosition {
line: number;
/** Column offset in unicode points */
character: number;
}
61 changes: 61 additions & 0 deletions extensions/positron-supervisor/src/jupyter/TypesConverters.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*---------------------------------------------------------------------------------------------
* Copyright (C) 2025 Posit Software, PBC. All rights reserved.
* Licensed under the Elastic License 2.0. See LICENSE.txt for license information.
*--------------------------------------------------------------------------------------------*/

import * as vscode from 'vscode';
import * as PositronTypes from './JupyterPositronTypes';

export namespace JupyterPositronLocation {
export function from(location: vscode.Location, text: string): PositronTypes.JupyterPositronLocation {
return {
uri: location.uri.toString(),
range: JupyterPositronRange.from(location.range, text),
};
}
}

export namespace JupyterPositronRange {
export function from(range: vscode.Range, text: string): PositronTypes.JupyterPositronRange {
return {
start: JupyterPositronPosition.from(range.start, text),
end: JupyterPositronPosition.from(range.end, text),
};
}
}

export namespace JupyterPositronPosition {
export function from(position: vscode.Position, text: string): PositronTypes.JupyterPositronPosition {
return {
line: position.line,
character: codePointOffsetFromUtf16Index(text, position.character),
};
}
}


export function codePointOffsetFromUtf16Index(text: string, utf16Index: number): number {
if (utf16Index <= 0) {
return 0;
}

let offset = 0;
let i = 0;

while (i < text.length && i < utf16Index) {
const codePoint = text.codePointAt(i);
if (codePoint === undefined) {
break;
}

// Advance by 2 for surrogate pairs (code points > 0xFFFF), 1 otherwise
i += codePoint > 0xFFFF ? 2 : 1;

// Only count this code point if we haven't passed the target index
if (i <= utf16Index) {
++offset;
}
}

return offset;
}
Loading
Loading