Skip to content

Commit 5eb1e58

Browse files
committed
handle code blocks when executing jupyter line
1 parent 3c25b67 commit 5eb1e58

9 files changed

Lines changed: 474 additions & 151 deletions

File tree

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
import { Cell } from '../contracts';
2+
import { TextDocument, Range } from 'vscode';
3+
import { JupyterCodeLensProvider } from '../editorIntegration/codeLensProvider';
4+
import * as vscode from 'vscode';
5+
6+
const CellIdentifier = /^(# %%|#%%|# \<codecell\>|# In\[\d?\]|# In\[ \])(.*)/i;
7+
8+
export class CellHelper {
9+
constructor(private cellCodeLenses: JupyterCodeLensProvider) {
10+
}
11+
12+
public getActiveCell(): Thenable<{ cell: vscode.Range, nextCell?: vscode.Range, previousCell?: vscode.Range }> {
13+
const activeEditor = vscode.window.activeTextEditor;
14+
if (!activeEditor || !activeEditor.document) {
15+
return Promise.resolve(null);
16+
}
17+
18+
return this.cellCodeLenses.provideCodeLenses(activeEditor.document, null).then(lenses => {
19+
if (lenses.length === 0) {
20+
return null;
21+
}
22+
let currentCellRange: vscode.Range;
23+
let nextCellRange: vscode.Range;
24+
let previousCellRange: vscode.Range;
25+
lenses.forEach((lens, index) => {
26+
if (lens.range.contains(activeEditor.selection.start)) {
27+
currentCellRange = lens.range;
28+
if (index < (lenses.length - 1)) {
29+
nextCellRange = lenses[index + 1].range;
30+
}
31+
if (index > 0) {
32+
previousCellRange = lenses[index - 1].range;
33+
}
34+
}
35+
});
36+
if (!currentCellRange) {
37+
return null;
38+
}
39+
return { cell: currentCellRange, nextCell: nextCellRange, previousCell: previousCellRange };
40+
});
41+
}
42+
public goToPreviousCell(): Thenable<any> {
43+
const activeEditor = vscode.window.activeTextEditor;
44+
if (!activeEditor || !activeEditor.document) {
45+
return Promise.resolve();
46+
}
47+
return this.getActiveCell().then(cellInfo => {
48+
if (!cellInfo || !cellInfo.previousCell) {
49+
return;
50+
}
51+
return this.advanceToCell(activeEditor.document, cellInfo.previousCell);
52+
});
53+
}
54+
public goToNextCell(): Thenable<any> {
55+
const activeEditor = vscode.window.activeTextEditor;
56+
if (!activeEditor || !activeEditor.document) {
57+
return Promise.resolve();
58+
}
59+
return this.getActiveCell().then(cellInfo => {
60+
if (!cellInfo || !cellInfo.nextCell) {
61+
return;
62+
}
63+
return this.advanceToCell(activeEditor.document, cellInfo.nextCell);
64+
});
65+
}
66+
public advanceToCell(document: vscode.TextDocument, range: vscode.Range): Promise<any> {
67+
if (!range || !document) {
68+
return;
69+
}
70+
const textEditor = vscode.window.visibleTextEditors.find(editor => editor.document && editor.document.fileName === document.fileName);
71+
if (!textEditor) {
72+
return;
73+
}
74+
75+
// Remember, we use comments to identify cells
76+
// Setting the cursor to the comment doesn't make sense
77+
// Quirk 1: Besides the document highlighter doesn't kick in (event' not fired), when you have placed the cursor on a comment
78+
// Quirk 2: If the first character starts with a %, then for some reason the highlighter doesn't kick in (event' not fired)
79+
let firstLineOfCellRange = range;
80+
if (range.start.line < range.end.line) {
81+
// let line = textEditor.document.lineAt(range.start.line + 1);
82+
// let start = new vscode.Position(range.start.line + 1, range.start.character);
83+
// firstLineOfCellRange = new vscode.Range(start, range.end);
84+
const start = CellHelper.findStartPositionWithCode(document, range.start.line + 1, range.end.line);
85+
firstLineOfCellRange = new vscode.Range(start, range.end);
86+
}
87+
textEditor.selections = [];
88+
textEditor.selection = new vscode.Selection(firstLineOfCellRange.start, firstLineOfCellRange.start);
89+
textEditor.revealRange(range);
90+
vscode.window.showTextDocument(textEditor.document);
91+
}
92+
93+
private static findStartPositionWithCode(document: vscode.TextDocument, startLine: number, endLine: number): vscode.Position {
94+
for (let lineNumber = startLine; lineNumber < endLine; lineNumber++) {
95+
let line = document.lineAt(startLine);
96+
if (line.isEmptyOrWhitespace) {
97+
continue;
98+
}
99+
const lineText = line.text;
100+
const trimmedLine = lineText.trim();
101+
if (trimmedLine.startsWith('#')) {
102+
continue;
103+
}
104+
// Yay we have a line
105+
// Remember, we need to set the cursor to a character other than white space
106+
// Highlighting doesn't kick in for comments or white space
107+
return new vscode.Position(lineNumber, lineText.indexOf(trimmedLine));
108+
}
109+
110+
// give up
111+
return new vscode.Position(startLine, 0);
112+
}
113+
public static getCells(document: TextDocument): Cell[] {
114+
const cells: Cell[] = [];
115+
for (let index = 0; index < document.lineCount; index++) {
116+
const line = document.lineAt(index);
117+
if (CellIdentifier.test(line.text)) {
118+
const results = CellIdentifier.exec(line.text);
119+
if (cells.length > 0) {
120+
const previousCell = cells[cells.length - 1];
121+
previousCell.range = new Range(previousCell.range.start, document.lineAt(index - 1).range.end);
122+
}
123+
cells.push({
124+
range: line.range,
125+
title: results.length > 1 ? results[2].trim() : ''
126+
});
127+
}
128+
129+
}
130+
131+
if (cells.length >= 1) {
132+
const line = document.lineAt(document.lineCount - 1);
133+
const previousCell = cells[cells.length - 1];
134+
previousCell.range = new Range(previousCell.range.start, line.range.end);
135+
}
136+
return cells;
137+
}
138+
}

src/client/jupyter/common/cells.ts

Lines changed: 0 additions & 36 deletions
This file was deleted.
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import * as vscode from 'vscode';
2+
import { JupyterCodeLensProvider } from '../editorIntegration/codeLensProvider';
3+
import { CellHelper } from './cellHelper';
4+
5+
export class CodeHelper {
6+
private cellHelper: CellHelper;
7+
constructor(private cellCodeLenses: JupyterCodeLensProvider) {
8+
this.cellHelper = new CellHelper(cellCodeLenses);
9+
}
10+
11+
public getSelectedCode(): Promise<string> {
12+
const activeEditor = vscode.window.activeTextEditor;
13+
if (!activeEditor || !activeEditor.document) {
14+
return Promise.resolve('');
15+
}
16+
if (activeEditor.selection.isEmpty) {
17+
const lineText = activeEditor.document.lineAt(activeEditor.selection.start.line).text;
18+
if (!CodeHelper.isCodeBlock(lineText)) {
19+
return Promise.resolve(lineText);
20+
}
21+
22+
// ok we're in a block, look for the end of the block untill the last line in the cell (if there are any cells)
23+
return new Promise<string>((resolve, reject) => {
24+
this.cellHelper.getActiveCell().then(activeCell => {
25+
const endLineNumber = activeCell ? activeCell.cell.end.line : activeEditor.document.lineCount - 1;
26+
const startIndent = lineText.indexOf(lineText.trim());
27+
const nextStartLine = activeEditor.selection.start.line + 1;
28+
29+
for (let lineNumber = nextStartLine; lineNumber <= endLineNumber; lineNumber++) {
30+
const line = activeEditor.document.lineAt(lineNumber);
31+
const nextLine = line.text;
32+
const nextLineIndent = nextLine.indexOf(nextLine.trim());
33+
if (nextLine.trim().indexOf('#') === 0) {
34+
continue;
35+
}
36+
if (nextLineIndent === startIndent) {
37+
// Return code untill previous line
38+
const endRange = activeEditor.document.lineAt(lineNumber - 1).range.end;
39+
resolve(activeEditor.document.getText(new vscode.Range(activeEditor.selection.start, endRange)));
40+
}
41+
}
42+
43+
resolve(activeEditor.document.getText(activeCell.cell));
44+
}, reject);
45+
});
46+
//return activeEditor.document.getText(new vscode.Range(activeEditor.selection.start, activeEditor.selection.))
47+
}
48+
else {
49+
return Promise.resolve(activeEditor.document.getText(activeEditor.selection));
50+
}
51+
}
52+
53+
private static isCodeBlock(code: string): boolean {
54+
return code.trim().endsWith(':') && code.indexOf('#') === -1;
55+
}
56+
}
Lines changed: 10 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
import * as vscode from 'vscode';
2-
import {Commands} from '../../common/constants';
3-
import {JupyterCodeLensProvider} from '../editorIntegration/codeLensProvider';
2+
import { Commands } from '../../common/constants';
3+
import { JupyterCodeLensProvider } from '../editorIntegration/codeLensProvider';
4+
import { CellHelper } from '../common/cellHelper';
45

56
export class CellOptions extends vscode.Disposable {
67
private disposables: vscode.Disposable[];
8+
private cellHelper: CellHelper;
79
constructor(private cellCodeLenses: JupyterCodeLensProvider) {
810
super(() => { });
11+
this.cellHelper = new CellHelper(this.cellCodeLenses);
912
this.disposables = [];
1013
this.registerCommands();
1114
}
@@ -15,107 +18,31 @@ export class CellOptions extends vscode.Disposable {
1518

1619
private registerCommands() {
1720
this.disposables.push(vscode.commands.registerCommand(Commands.Jupyter.Cell.DisplayCellMenu, this.displayCellOptions.bind(this)));
18-
this.disposables.push(vscode.commands.registerCommand(Commands.Jupyter.Cell.AdcanceToCell, this.advanceToCell.bind(this)));
21+
this.disposables.push(vscode.commands.registerCommand(Commands.Jupyter.Cell.AdcanceToCell, this.cellHelper.advanceToCell.bind(this.cellHelper)));
1922
this.disposables.push(vscode.commands.registerCommand(Commands.Jupyter.Cell.ExecuteCurrentCell, this.executeCell.bind(this, false)));
2023
this.disposables.push(vscode.commands.registerCommand(Commands.Jupyter.Cell.ExecuteCurrentCellAndAdvance, this.executeCell.bind(this, true)));
21-
this.disposables.push(vscode.commands.registerCommand(Commands.Jupyter.Cell.GoToNextCell, this.goToNextCell.bind(this)));
22-
this.disposables.push(vscode.commands.registerCommand(Commands.Jupyter.Cell.GoToPreviousCell, this.goToPreviousCell.bind(this)));
24+
this.disposables.push(vscode.commands.registerCommand(Commands.Jupyter.Cell.GoToNextCell, this.cellHelper.goToNextCell.bind(this.cellHelper)));
25+
this.disposables.push(vscode.commands.registerCommand(Commands.Jupyter.Cell.GoToPreviousCell, this.cellHelper.goToPreviousCell.bind(this.cellHelper)));
2326
}
24-
private getActiveCell(): Thenable<{ cell: vscode.Range, nextCell?: vscode.Range, previousCell?: vscode.Range }> {
25-
const activeEditor = vscode.window.activeTextEditor;
26-
if (!activeEditor || !activeEditor.document) {
27-
return Promise.resolve(null);
28-
}
2927

30-
return this.cellCodeLenses.provideCodeLenses(activeEditor.document, null).then(lenses => {
31-
let currentCellRange: vscode.Range;
32-
let nextCellRange: vscode.Range;
33-
let previousCellRange: vscode.Range;
34-
lenses.forEach((lens, index) => {
35-
if (lens.range.contains(activeEditor.selection.start)) {
36-
currentCellRange = lens.range;
37-
if (index < (lenses.length - 1)) {
38-
nextCellRange = lenses[index + 1].range;
39-
}
40-
if (index > 0) {
41-
previousCellRange = lenses[index - 1].range;
42-
}
43-
}
44-
});
45-
if (!currentCellRange) {
46-
return null;
47-
}
48-
return { cell: currentCellRange, nextCell: nextCellRange, previousCell: previousCellRange };
49-
});
50-
}
51-
private goToPreviousCell(): Thenable<any> {
52-
const activeEditor = vscode.window.activeTextEditor;
53-
if (!activeEditor || !activeEditor.document) {
54-
return Promise.resolve();
55-
}
56-
return this.getActiveCell().then(cellInfo => {
57-
if (!cellInfo || !cellInfo.previousCell) {
58-
return;
59-
}
60-
return this.advanceToCell(activeEditor.document, cellInfo.previousCell);
61-
});
62-
}
63-
private goToNextCell(): Thenable<any> {
64-
const activeEditor = vscode.window.activeTextEditor;
65-
if (!activeEditor || !activeEditor.document) {
66-
return Promise.resolve();
67-
}
68-
return this.getActiveCell().then(cellInfo => {
69-
if (!cellInfo || !cellInfo.nextCell) {
70-
return;
71-
}
72-
return this.advanceToCell(activeEditor.document, cellInfo.nextCell);
73-
});
74-
}
7528
private executeCell(advanceToNext: boolean): Thenable<any> {
7629
const activeEditor = vscode.window.activeTextEditor;
7730
if (!activeEditor || !activeEditor.document) {
7831
return Promise.resolve();
7932
}
8033

81-
return this.getActiveCell().then(cellInfo => {
34+
return this.cellHelper.getActiveCell().then(cellInfo => {
8235
if (!cellInfo || !cellInfo.cell) {
8336
return;
8437
}
8538
return vscode.commands.executeCommand(Commands.Jupyter.ExecuteRangeInKernel, activeEditor.document, cellInfo.cell).then(() => {
8639
if (!advanceToNext) {
8740
return;
8841
}
89-
return this.advanceToCell(activeEditor.document, cellInfo.nextCell);
42+
return this.cellHelper.advanceToCell(activeEditor.document, cellInfo.nextCell);
9043
});
9144
});
9245
}
93-
private advanceToCell(document: vscode.TextDocument, range: vscode.Range): Promise<any> {
94-
if (!range || !document) {
95-
return;
96-
}
97-
const textEditor = vscode.window.visibleTextEditors.find(editor => editor.document && editor.document.fileName === document.fileName);
98-
if (!textEditor) {
99-
return;
100-
}
101-
102-
// Remember, we use comments to identify cells
103-
// Setting the cursor to the comment doesn't make sense
104-
// Quirk 1: Besides the document highlighter doesn't kick in (event' not fired), when you have placed the cursor on a comment
105-
// Quirk 2: If the first character starts with a %, then for some reason the highlighter doesn't kick in (event' not fired)
106-
let firstLineOfCellRange = range;
107-
if (range.start.line < range.end.line) {
108-
// let line = textEditor.document.lineAt(range.start.line + 1);
109-
// let start = new vscode.Position(range.start.line + 1, range.start.character);
110-
// firstLineOfCellRange = new vscode.Range(start, range.end);
111-
const start = this.findStartPositionWithCode(document, range.start.line + 1, range.end.line);
112-
firstLineOfCellRange = new vscode.Range(start, range.end);
113-
}
114-
textEditor.selections = [];
115-
textEditor.selection = new vscode.Selection(firstLineOfCellRange.start, firstLineOfCellRange.start);
116-
textEditor.revealRange(range);
117-
vscode.window.showTextDocument(textEditor.document);
118-
}
11946
private displayCellOptions(document: vscode.TextDocument, range: vscode.Range, nextCellRange?: vscode.Range) {
12047
interface Option extends vscode.QuickPickItem {
12148
command: string;
@@ -154,24 +81,4 @@ export class CellOptions extends vscode.Disposable {
15481
});
15582
}
15683

157-
private findStartPositionWithCode(document: vscode.TextDocument, startLine: number, endLine: number): vscode.Position {
158-
for (let lineNumber = startLine; lineNumber < endLine; lineNumber++) {
159-
let line = document.lineAt(startLine);
160-
if (line.isEmptyOrWhitespace) {
161-
continue;
162-
}
163-
const lineText = line.text;
164-
const trimmedLine = lineText.trim();
165-
if (trimmedLine.startsWith('#')) {
166-
continue;
167-
}
168-
// Yay we have a line
169-
// Remember, we need to set the cursor to a character other than white space
170-
// Highlighting doesn't kick in for comments or white space
171-
return new vscode.Position(lineNumber, lineText.indexOf(trimmedLine));
172-
}
173-
174-
// give up
175-
return new vscode.Position(startLine, 0);
176-
}
17784
}

0 commit comments

Comments
 (0)