Skip to content
Draft
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
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,11 @@
"key": "ctrl+; c",
"when": "editorTextFocus && jupyter.hascodecells && !notebookEditorFocused"
},
{
"command": "jupyter.changeCellToRaw",
"key": "ctrl+; r",
"when": "editorTextFocus && jupyter.hascodecells && !notebookEditorFocused"
},
{
"command": "jupyter.gotoNextCellInFile",
"key": "ctrl+alt+]",
Expand Down
1 change: 1 addition & 0 deletions src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ export interface ICommandNameArgumentTypeMapping {
[DSCommands.MoveCellsDown]: [];
[DSCommands.ChangeCellToMarkdown]: [];
[DSCommands.ChangeCellToCode]: [];
[DSCommands.ChangeCellToRaw]: [];
[DSCommands.GotoNextCellInFile]: [];
[DSCommands.GotoPrevCellInFile]: [];
[DSCommands.ScrollToCell]: [Uri, string];
Expand Down
5 changes: 5 additions & 0 deletions src/interactive-window/commands/commandRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ export class CommandRegistry implements IDisposable, IExtensionSyncActivationSer
this.registerCommand(Commands.MoveCellsDown, this.moveCellsDown);
this.registerCommand(Commands.ChangeCellToMarkdown, this.changeCellToMarkdown);
this.registerCommand(Commands.ChangeCellToCode, this.changeCellToCode);
this.registerCommand(Commands.ChangeCellToRaw, this.changeCellToRaw);
this.registerCommand(Commands.GotoNextCellInFile, this.gotoNextCellInFile);
this.registerCommand(Commands.GotoPrevCellInFile, this.gotoPrevCellInFile);
this.registerCommand(Commands.AddCellBelow, this.addCellBelow);
Expand Down Expand Up @@ -436,6 +437,10 @@ export class CommandRegistry implements IDisposable, IExtensionSyncActivationSer
this.getCurrentCodeWatcher()?.changeCellToCode();
}

private async changeCellToRaw(): Promise<void> {
this.getCurrentCodeWatcher()?.changeCellToRaw();
}

private async gotoNextCellInFile(): Promise<void> {
this.getCurrentCodeWatcher()?.gotoNextCell();
}
Expand Down
10 changes: 9 additions & 1 deletion src/interactive-window/editor-integration/cellFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,17 @@ function generateMarkdownCell(code: string[]) {
return new NotebookCellData(NotebookCellKind.Markup, generateMarkdownFromCodeLines(code).join('\n'), 'markdown');
}

function generateRawCell(code: string[], matcher: CellMatcher) {
const lines = matcher.isCell(code[0]) && code.length > 1 ? code.slice(1) : code;
return new NotebookCellData(NotebookCellKind.Markup, lines.join('\n'), 'raw');
}

export function generateCells(
settings: IJupyterSettings | undefined,
code: string,
splitMarkdown: boolean
): NotebookCellData[] {
// Determine if we have a markdown cell/ markdown and code cell combined/ or just a code cell
// Determine if we have a markdown cell/ raw cell/ or code cell
const split = splitLines(code, { trim: false });
const firstLine = split[0];
const matcher = new CellMatcher(settings);
Expand Down Expand Up @@ -70,6 +75,9 @@ export function generateCells(
// Just a single markdown cell
return [generateMarkdownCell(split)];
}
} else if (matcher.isRaw(firstLine)) {
// Just a raw cell
return [generateRawCell(split, matcher)];
} else {
// Just code
return [generateCodeCell(split, matcher)];
Expand Down
21 changes: 19 additions & 2 deletions src/interactive-window/editor-integration/cellMatcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@ import { noop } from '../../platform/common/utils/misc';
export class CellMatcher {
public codeExecRegEx: RegExp;
public markdownExecRegEx: RegExp;
public rawExecRegEx: RegExp;

private codeMatchRegEx: RegExp;
private markdownMatchRegEx: RegExp;
private rawMatchRegEx: RegExp;
private defaultCellMarker: string;

constructor(settings?: IJupyterSettings) {
Expand All @@ -27,25 +29,40 @@ export class CellMatcher {
settings ? settings.markdownRegularExpression : undefined,
RegExpValues.PythonMarkdownCellMarker
);
this.rawMatchRegEx = this.createRegExp(
undefined, // No setting for raw cells yet
RegExpValues.PythonRawCellMarker
);
this.codeExecRegEx = new RegExp(`${this.codeMatchRegEx.source}(.*)`);
this.markdownExecRegEx = new RegExp(`${this.markdownMatchRegEx.source}(.*)`);
this.rawExecRegEx = new RegExp(`${this.rawMatchRegEx.source}(.*)`);
this.defaultCellMarker = settings?.defaultCellMarker ? settings.defaultCellMarker : '# %%';
}

public isCell(code: string): boolean {
return this.isCode(code) || this.isMarkdown(code);
return this.isCode(code) || this.isMarkdown(code) || this.isRaw(code);
}

public isMarkdown(code: string): boolean {
return this.markdownMatchRegEx.test(code.trim());
}

public isRaw(code: string): boolean {
return this.rawMatchRegEx.test(code.trim());
}

public isCode(code: string): boolean {
return this.codeMatchRegEx.test(code.trim()) || code.trim() === this.defaultCellMarker;
}

public getCellType(code: string): string {
return this.isMarkdown(code) ? 'markdown' : 'code';
if (this.isMarkdown(code)) {
return 'markdown';
} else if (this.isRaw(code)) {
return 'raw';
} else {
return 'code';
}
}

public isEmptyCell(code: string): boolean {
Expand Down
17 changes: 17 additions & 0 deletions src/interactive-window/editor-integration/cellMatcher.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ suite('CellMatcher', () => {
test('CellMatcher for valid code cell', () => {
assert.ok(defaultMatcher.isCell(cellMarker), `"${cellMarker}" should match as a cell marker`);
assert.ok(defaultMatcher.isCode(cellMarker), `"${cellMarker}" should match as a code cell marker`);
assert.equal(defaultMatcher.getCellType(cellMarker), 'code', `"${cellMarker}" should be detected as code cell type`);
});
});

Expand All @@ -41,6 +42,22 @@ suite('CellMatcher', () => {
test('CellMatcher for valid markdown cell', () => {
assert.ok(defaultMatcher.isCell(cellMarker), `"${cellMarker}" should match as a cell marker`);
assert.ok(defaultMatcher.isMarkdown(cellMarker), `"${cellMarker}" should match as a markdown cell marker`);
assert.equal(defaultMatcher.getCellType(cellMarker), 'markdown', `"${cellMarker}" should be detected as markdown cell type`);
});
});

const rawCellMarkers = [
'# %% [raw]',
'#%%[raw]',
'# %% [raw]',
'# %% [raw] extra stuff',
' # %% [raw] '
];
rawCellMarkers.forEach((cellMarker) => {
test('CellMatcher for valid raw cell', () => {
assert.ok(defaultMatcher.isCell(cellMarker), `"${cellMarker}" should match as a cell marker`);
assert.ok(defaultMatcher.isRaw(cellMarker), `"${cellMarker}" should match as a raw cell marker`);
assert.equal(defaultMatcher.getCellType(cellMarker), 'raw', `"${cellMarker}" should be detected as raw cell type`);
});
});

Expand Down
47 changes: 34 additions & 13 deletions src/interactive-window/editor-integration/codewatcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -694,6 +694,13 @@ export class CodeWatcher implements ICodeWatcher {
});
}

@capturePerfTelemetry(Telemetry.ChangeCellToRaw)
public changeCellToRaw() {
this.applyToCells((editor, cell, _) => {
return this.changeCellTo(editor, cell, 'raw');
});
}

@capturePerfTelemetry(Telemetry.GotoNextCellInFile)
public gotoNextCell() {
const editor = window.activeTextEditor;
Expand Down Expand Up @@ -743,11 +750,6 @@ export class CodeWatcher implements ICodeWatcher {
}

private changeCellTo(editor: TextEditor, cell: ICellRange, toCellType: nbformat.CellType) {
// change cell from code -> markdown or markdown -> code
if (toCellType === 'raw') {
throw Error('Cell Type raw not implemented');
}

// don't change cell type if already that type
if (cell.cell_type === toCellType) {
return;
Expand All @@ -758,18 +760,37 @@ export class CodeWatcher implements ICodeWatcher {

// new definition text
const cellMarker = this.getDefaultCellMarker(editor.document.uri);
const definitionMatch =
toCellType === 'markdown'
? cellMatcher.codeExecRegEx.exec(definitionText) // code -> markdown
: cellMatcher.markdownExecRegEx.exec(definitionText); // markdown -> code

// Determine which regex to use based on current cell type
let definitionMatch: RegExpExecArray | null = null;
if (cellMatcher.isMarkdown(definitionText)) {
definitionMatch = cellMatcher.markdownExecRegEx.exec(definitionText);
} else if (cellMatcher.isRaw(definitionText)) {
definitionMatch = cellMatcher.rawExecRegEx.exec(definitionText);
} else {
definitionMatch = cellMatcher.codeExecRegEx.exec(definitionText);
}

if (!definitionMatch) {
return;
}

const definitionExtra = definitionMatch[definitionMatch.length - 1];
const newDefinitionText =
toCellType === 'markdown'
? `${cellMarker} [markdown]${definitionExtra}` // code -> markdown
: `${cellMarker}${definitionExtra}`; // markdown -> code

// Create the new definition text based on target cell type
let newDefinitionText: string;
switch (toCellType) {
case 'markdown':
newDefinitionText = `${cellMarker} [markdown]${definitionExtra}`;
break;
case 'raw':
newDefinitionText = `${cellMarker} [raw]${definitionExtra}`;
break;
case 'code':
default:
newDefinitionText = `${cellMarker}${definitionExtra}`;
break;
}

editor
.edit(async (editBuilder) => {
Expand Down
1 change: 1 addition & 0 deletions src/interactive-window/editor-integration/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export interface ICodeWatcher extends IDisposable {
moveCellsDown(): Promise<void>;
changeCellToMarkdown(): void;
changeCellToCode(): void;
changeCellToRaw(): void;
debugCurrentCell(): Promise<void>;
gotoNextCell(): void;
gotoPreviousCell(): void;
Expand Down
3 changes: 3 additions & 0 deletions src/platform/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@ export namespace Commands {
export const MoveCellsDown = 'jupyter.moveCellsDown';
export const ChangeCellToMarkdown = 'jupyter.changeCellToMarkdown';
export const ChangeCellToCode = 'jupyter.changeCellToCode';
export const ChangeCellToRaw = 'jupyter.changeCellToRaw';
export const GotoNextCellInFile = 'jupyter.gotoNextCellInFile';
export const GotoPrevCellInFile = 'jupyter.gotoPrevCellInFile';
export const ScrollToCell = 'jupyter.scrolltocell';
Expand Down Expand Up @@ -286,6 +287,7 @@ export namespace EditorContexts {
export namespace RegExpValues {
export const PythonCellMarker = /^(#\s*%%|#\s*\<codecell\>|#\s*In\[\d*?\]|#\s*In\[ \])/;
export const PythonMarkdownCellMarker = /^(#\s*%%\s*\[markdown\]|#\s*\<markdowncell\>)/;
export const PythonRawCellMarker = /^(#\s*%%\s*\[raw\])/;
export const UrlPatternRegEx =
'(?<PREFIX>https?:\\/\\/)((\\(.+\\s+or\\s+(?<IP>.+)\\))|(?<LOCAL>[^\\s]+))(?<REST>:.+)';
export const HttpPattern = /https?:\/\//;
Expand Down Expand Up @@ -315,6 +317,7 @@ export enum Telemetry {
MoveCellsDown = 'DATASCIENCE.RUN_MOVE_CELLS_DOWN',
ChangeCellToMarkdown = 'DATASCIENCE.RUN_CHANGE_CELL_TO_MARKDOWN',
ChangeCellToCode = 'DATASCIENCE.RUN_CHANGE_CELL_TO_CODE',
ChangeCellToRaw = 'DATASCIENCE.RUN_CHANGE_CELL_TO_RAW',
GotoNextCellInFile = 'DATASCIENCE.GOTO_NEXT_CELL_IN_FILE',
GotoPrevCellInFile = 'DATASCIENCE.GOTO_PREV_CELL_IN_FILE',
RunSelectionOrLine = 'DATASCIENCE.RUN_SELECTION_OR_LINE',
Expand Down
9 changes: 9 additions & 0 deletions src/telemetry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1498,6 +1498,15 @@ export class IEventNamePropertyMapping {
source: 'N/A',
measures: commonClassificationForDurationProperties()
};
/**
* Cell Edit Command in Interactive Window
*/
[Telemetry.ChangeCellToRaw]: TelemetryEventInfo<DurationMeasurement> = {
owner: 'amunger',
feature: ['InteractiveWindow'],
source: 'N/A',
measures: commonClassificationForDurationProperties()
};
/**
* Cell Navigation Command in Interactive Window
*/
Expand Down