Skip to content

Commit b0c98ac

Browse files
committed
code lenses and symbols for cells
1 parent 4761736 commit b0c98ac

6 files changed

Lines changed: 110 additions & 18 deletions

File tree

src/client/common/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export namespace Commands {
2424
export const Get_All_KernelSpecs = 'jupyter:getAllKernelSpecs';
2525
export const Select_Kernel = 'jupyter:selectKernel';
2626
export const Kernel_Options = 'jupyter:kernelOptions';
27+
export const ExecuteRangeInKernel = 'jupyter:execRangeInKernel';
2728
export namespace Kernel {
2829
export const Kernel_Interrupt = 'jupyter:kernelInterrupt';
2930
export const Kernel_Restart = 'jupyter:kernelRestart';

src/client/jupyter/common/cells.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import {Cell} from '../contracts';
2+
import {TextDocument, Range} from 'vscode';
3+
4+
const CellIdentifier = /^(# %%|#%%|# \<codecell\>|# In\[\d?\]|# In\[ \])(.*)/i;
5+
6+
export class CellHelper {
7+
constructor() {
8+
9+
}
10+
11+
public static getCells(document: TextDocument): Cell[] {
12+
const cells: Cell[] = [];
13+
for (let index = 0; index < document.lineCount; index++) {
14+
const line = document.lineAt(index);
15+
if (CellIdentifier.test(line.text)) {
16+
const results = CellIdentifier.exec(line.text);
17+
if (cells.length > 0) {
18+
const previousCell = cells[cells.length - 1];
19+
previousCell.range = new Range(previousCell.range.start, document.lineAt(index - 1).range.end);
20+
}
21+
cells.push({
22+
range: line.range,
23+
title: results.length > 1 ? results[2].trim() : ''
24+
});
25+
}
26+
27+
if (index === document.lineCount - 1) {
28+
const previousCell = cells[cells.length - 1];
29+
previousCell.range = new Range(previousCell.range.start, line.range.end);
30+
}
31+
}
32+
return cells;
33+
}
34+
}

src/client/jupyter/contracts.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,3 +203,8 @@ export interface JupyterMessage {
203203
version: '5.0'
204204
};
205205
}
206+
207+
export interface Cell {
208+
range: vscode.Range;
209+
title: string;
210+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
'use strict';
2+
3+
import {CodeLensProvider, TextDocument, CancellationToken, CodeLens, Command} from 'vscode';
4+
import * as telemetryContracts from '../../common/telemetryContracts';
5+
import {Commands} from '../../common/constants';
6+
import {CellHelper} from '../common/cells';
7+
8+
export class JupyterCodeLensProvider implements CodeLensProvider {
9+
public provideCodeLenses(document: TextDocument, token: CancellationToken): Thenable<CodeLens[]> {
10+
const cells = CellHelper.getCells(document);
11+
if (cells.length === 0) {
12+
return Promise.resolve([]);
13+
}
14+
15+
const lenses = cells.map(cell => {
16+
const cmd: Command = {
17+
arguments: [cell.range],
18+
title: 'Run Cell',
19+
command: Commands.Jupyter.ExecuteRangeInKernel
20+
};
21+
return new CodeLens(cell.range, cmd);
22+
});
23+
24+
return Promise.resolve(lenses);
25+
}
26+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
'use strict';
2+
import {DocumentSymbolProvider, TextDocument, CancellationToken, SymbolInformation, SymbolKind} from 'vscode';
3+
import * as telemetryContracts from '../../common/telemetryContracts';
4+
import {CellHelper} from '../common/cells';
5+
6+
export class JupyterSymbolProvider implements DocumentSymbolProvider {
7+
public provideDocumentSymbols(document: TextDocument, token: CancellationToken): Thenable<SymbolInformation[]> {
8+
const cells = CellHelper.getCells(document);
9+
if (cells.length === 0) {
10+
return Promise.resolve([]);
11+
}
12+
13+
const symbols = cells.map(cell => {
14+
const title = 'Jupyter Cell' + (cell.title.length === 0 ? '' : ': ' + cell.title);
15+
return new SymbolInformation(title, SymbolKind.Namespace, cell.range);
16+
});
17+
18+
return Promise.resolve(symbols);
19+
}
20+
}

src/client/jupyter/main.ts

Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import * as vscode from 'vscode';
44
import {JupyterDisplay} from './display/main';
55
import {KernelStatus} from './display/kernelStatus';
66
import {Commands} from '../common/constants';
7+
import {JupyterCodeLensProvider} from './editorIntegration/codeLensProvider';
8+
import {JupyterSymbolProvider} from './editorIntegration/symbolProvider';
79

810
export class Jupyter extends vscode.Disposable {
911
public kernelManager: KernelManagerImpl;
@@ -15,11 +17,15 @@ export class Jupyter extends vscode.Disposable {
1517
constructor(private outputChannel: vscode.OutputChannel) {
1618
super(() => { });
1719
this.disposables = [];
20+
this.registerCommands();
21+
this.registerKernelCommands();
1822
}
1923
activate(state) {
2024
this.kernelManager = new KernelManagerImpl(this.outputChannel);
2125
this.disposables.push(this.kernelManager);
2226
this.disposables.push(vscode.window.onDidChangeActiveTextEditor(this.onEditorChanged.bind(this)));
27+
this.disposables.push(vscode.languages.registerCodeLensProvider('python', new JupyterCodeLensProvider()));
28+
this.disposables.push(vscode.languages.registerDocumentSymbolProvider('python', new JupyterSymbolProvider()));
2329
this.status = new KernelStatus();
2430
this.disposables.push(this.status);
2531
this.display = new JupyterDisplay();
@@ -74,27 +80,17 @@ export class Jupyter extends vscode.Disposable {
7480
let htmlResponse = '';
7581
let responses = [];
7682
return kernel.execute(code, (result: { type: string, stream: string, data: { [key: string]: string } | string }) => {
77-
if ((result.type === 'text' && result.stream === 'stdout' && typeof result.data['text/plain'] === 'string') ||
78-
(result.type === 'text' && result.stream === 'pyout' && typeof result.data['text/plain'] === 'string') ||
79-
(result.type === 'text' && result.stream === 'error' && typeof result.data['text/plain'] === 'string')) {
80-
responses.push(result.data);
81-
if (result.stream === 'error') {
82-
return resolve([htmlResponse, responses]);
83-
}
84-
}
85-
if (result.type === 'text/html' && result.stream === 'pyout' && typeof result.data['text/html'] === 'string') {
86-
result.data['text/html'] = result.data['text/html'].replace(/<\/script>/g, '</scripts>');
87-
responses.push(result.data);
88-
}
89-
if (result.type === 'application/javascript' && result.stream === 'pyout' && typeof result.data['application/javascript'] === 'string') {
90-
responses.push(result.data);
83+
if (result.data === 'ok' && result.stream === 'status' && result.type === 'text') {
84+
return resolve([htmlResponse, responses]);
9185
}
92-
if (result.type.startsWith('image/') && result.stream === 'pyout' && typeof result.data[result.type] === 'string') {
86+
if (result.stream === 'error' && result.type === 'text') {
9387
responses.push(result.data);
88+
return resolve([htmlResponse, responses]);
9489
}
95-
if (result.data === 'ok' && result.stream === 'status' && result.type === 'text') {
96-
resolve([htmlResponse, responses]);
90+
if (typeof result.data['text/html'] === 'string') {
91+
result.data['text/html'] = result.data['text/html'].replace(/<\/script>/g, '</scripts>');
9792
}
93+
responses.push(result.data);
9894
});
9995
});
10096
}
@@ -106,6 +102,16 @@ export class Jupyter extends vscode.Disposable {
106102
const code = activeEditor.document.getText(vscode.window.activeTextEditor.selection);
107103
this.executeCode(code, activeEditor.document.languageId);
108104
}
105+
private registerCommands() {
106+
this.disposables.push(vscode.commands.registerCommand(Commands.Jupyter.ExecuteRangeInKernel, (range: vscode.Range) => {
107+
const activeEditor = vscode.window.activeTextEditor;
108+
if (!activeEditor || !activeEditor.document || !range || range.isEmpty) {
109+
return;
110+
}
111+
const code = activeEditor.document.getText(range);
112+
this.executeCode(code, activeEditor.document.languageId);
113+
}));
114+
}
109115
private registerKernelCommands() {
110116
this.disposables.push(vscode.commands.registerCommand(Commands.Jupyter.Kernel.Kernel_Interrupt, () => {
111117
this.kernel.interrupt();
@@ -115,7 +121,7 @@ export class Jupyter extends vscode.Disposable {
115121
this.onKernelChanged(kernel);
116122
});
117123
}));
118-
this.disposables.push(vscode.commands.registerCommand(Commands.Jupyter.Kernel.Kernel_Interrupt, () => {
124+
this.disposables.push(vscode.commands.registerCommand(Commands.Jupyter.Kernel.Kernel_Shut_Down, () => {
119125
this.kernel.shutdown();
120126
this.onKernelChanged();
121127
}));

0 commit comments

Comments
 (0)