Skip to content

Commit 1c4ce34

Browse files
committed
Emit code locations to kernels if supplied
1 parent c662af0 commit 1c4ce34

File tree

5 files changed

+110
-4
lines changed

5 files changed

+110
-4
lines changed

extensions/positron-r/src/session.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -203,9 +203,15 @@ export class RSession implements positron.LanguageRuntimeSession, vscode.Disposa
203203
throw new Error(`Debugging is not supported in R sessions`);
204204
}
205205

206-
execute(code: string, id: string, mode: positron.RuntimeCodeExecutionMode, errorBehavior: positron.RuntimeErrorBehavior): void {
206+
execute(
207+
code: string,
208+
id: string,
209+
mode: positron.RuntimeCodeExecutionMode,
210+
errorBehavior: positron.RuntimeErrorBehavior,
211+
codeLocation?: vscode.Location,
212+
): void {
207213
if (this._kernel) {
208-
this._kernel.execute(code, id, mode, errorBehavior);
214+
this._kernel.execute(code, id, mode, errorBehavior, codeLocation);
209215
} else {
210216
throw new Error(`Cannot execute '${code}'; kernel not started`);
211217
}

extensions/positron-supervisor/src/KallichoreSession.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import * as positron from 'positron';
88
import * as os from 'os';
99
import * as path from 'path';
1010
import * as fs from 'fs';
11+
import * as typesConverters from './jupyter/TypesConverters';
1112
import { CommBackendMessage, JupyterKernelExtra, JupyterKernelSpec, JupyterLanguageRuntimeSession, JupyterSession, Comm } from './positron-supervisor';
1213
import { ActiveSession, ConnectionInfo, DefaultApi, InterruptMode, NewSession, RestartSession, StartupEnvironment, Status, VarAction, VarActionType } from './kcclient/api';
1314
import { JupyterMessage } from './jupyter/JupyterMessage';
@@ -693,10 +694,13 @@ export class KallichoreSession implements JupyterLanguageRuntimeSession {
693694
* @param mode The execution mode
694695
* @param errorBehavior What to do if an error occurs
695696
*/
696-
execute(code: string,
697+
execute(
698+
code: string,
697699
id: string,
698700
mode: positron.RuntimeCodeExecutionMode,
699-
errorBehavior: positron.RuntimeErrorBehavior): void {
701+
errorBehavior: positron.RuntimeErrorBehavior,
702+
codeLocation?: vscode.Location,
703+
): void {
700704

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

715+
if (codeLocation) {
716+
request.positron = { code_location: typesConverters.JupyterPositronLocation.from(codeLocation, code) };
717+
}
718+
711719
// Create and send the execute request
712720
const execute = new ExecuteRequest(id, request);
713721
this.sendRequest(execute).then((reply) => {

extensions/positron-supervisor/src/jupyter/ExecuteRequest.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import { JupyterChannel } from './JupyterChannel';
77
import { JupyterDisplayData } from './JupyterDisplayData';
88
import { JupyterMessageType } from './JupyterMessageType.js';
9+
import { JupyterPositronLocation, JupyterPositronRange } from './JupyterPositronTypes';
910
import { JupyterRequest } from './JupyterRequest';
1011

1112

@@ -41,6 +42,14 @@ export interface JupyterExecuteRequest {
4142

4243
/** Whether the kernel should stop the execution queue when an error occurs */
4344
stop_on_error: boolean;
45+
46+
/** Positron extension */
47+
positron?: JupyterPositronExecuteRequest;
48+
}
49+
50+
export interface JupyterPositronExecuteRequest {
51+
/** Location of `code`. Encoded in UTF-16 offsets. */
52+
code_location?: JupyterPositronLocation;
4453
}
4554

4655
/**
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (C) 2025 Posit Software, PBC. All rights reserved.
3+
* Licensed under the Elastic License 2.0. See LICENSE.txt for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
export interface JupyterPositronLocation {
7+
uri: string;
8+
range: JupyterPositronRange;
9+
}
10+
11+
export interface JupyterPositronRange {
12+
start: JupyterPositronPosition;
13+
end: JupyterPositronPosition;
14+
}
15+
16+
// See https://jupyter-client.readthedocs.io/en/stable/messaging.html#cursor-pos-unicode-note
17+
// regarding choice of offset in unicode points
18+
export interface JupyterPositronPosition {
19+
line: number;
20+
/** Column offset in unicode points */
21+
character: number;
22+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (C) 2025 Posit Software, PBC. All rights reserved.
3+
* Licensed under the Elastic License 2.0. See LICENSE.txt for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import * as vscode from 'vscode';
7+
import * as PositronTypes from './JupyterPositronTypes';
8+
9+
export namespace JupyterPositronLocation {
10+
export function from(location: vscode.Location, text: string): PositronTypes.JupyterPositronLocation {
11+
return {
12+
uri: location.uri.toString(),
13+
range: JupyterPositronRange.from(location.range, text),
14+
};
15+
}
16+
}
17+
18+
export namespace JupyterPositronRange {
19+
export function from(range: vscode.Range, text: string): PositronTypes.JupyterPositronRange {
20+
return {
21+
start: JupyterPositronPosition.from(range.start, text),
22+
end: JupyterPositronPosition.from(range.end, text),
23+
};
24+
}
25+
}
26+
27+
export namespace JupyterPositronPosition {
28+
export function from(position: vscode.Position, text: string): PositronTypes.JupyterPositronPosition {
29+
return {
30+
line: position.line,
31+
character: codePointOffsetFromUtf16Index(text, position.character),
32+
};
33+
}
34+
}
35+
36+
37+
export function codePointOffsetFromUtf16Index(text: string, utf16Index: number): number {
38+
if (utf16Index <= 0) {
39+
return 0;
40+
}
41+
42+
let offset = 0;
43+
let i = 0;
44+
45+
while (i < text.length && i < utf16Index) {
46+
const codePoint = text.codePointAt(i);
47+
if (codePoint === undefined) {
48+
break;
49+
}
50+
51+
// Advance by 2 for surrogate pairs (code points > 0xFFFF), 1 otherwise
52+
i += codePoint > 0xFFFF ? 2 : 1;
53+
54+
// Only count this code point if we haven't passed the target index
55+
if (i <= utf16Index) {
56+
++offset;
57+
}
58+
}
59+
60+
return offset;
61+
}

0 commit comments

Comments
 (0)