From d75738d753ab194df6661ccb106ab4c1c4083e5b Mon Sep 17 00:00:00 2001 From: Pokey Rule Date: Fri, 20 Aug 2021 17:58:25 +0100 Subject: [PATCH 1/5] Initial work --- src/actions/EditNewLine.ts | 12 ++++++++++-- src/checkCommandValidity.ts | 25 +++++++++++++++++++++++++ src/extension.ts | 3 +++ src/processTargets/processModifier.ts | 4 ++++ src/typings/Types.ts | 12 ++++++------ src/util/targetUtils.ts | 23 ++++++++++++++++++++++- 6 files changed, 70 insertions(+), 9 deletions(-) create mode 100644 src/checkCommandValidity.ts diff --git a/src/actions/EditNewLine.ts b/src/actions/EditNewLine.ts index 53c0fd1313..2505cdcad4 100644 --- a/src/actions/EditNewLine.ts +++ b/src/actions/EditNewLine.ts @@ -38,10 +38,18 @@ class EditNewLine implements Action { this.correctForParagraph(targets); if (this.isAbove) { await this.graph.actions.setSelectionBefore.run([targets]); - await commands.executeCommand("editor.action.insertLineBefore"); + await commands.executeCommand( + targets[0].selectionContext.isNotebookCell + ? "jupyter.insertCellAbove" + : "editor.action.insertLineBefore" + ); } else { await this.graph.actions.setSelectionAfter.run([targets]); - await commands.executeCommand("editor.action.insertLineAfter"); + await commands.executeCommand( + targets[0].selectionContext.isNotebookCell + ? "jupyter.insertCellBelow" + : "editor.action.insertLineAfter" + ); } return { diff --git a/src/checkCommandValidity.ts b/src/checkCommandValidity.ts new file mode 100644 index 0000000000..2cb62afb74 --- /dev/null +++ b/src/checkCommandValidity.ts @@ -0,0 +1,25 @@ +import { ActionType, PartialTarget, ScopeType } from "./typings/Types"; +import { getPrimitiveTargets } from "./util/targetUtils"; + +export function checkCommandValidity( + actionName: ActionType, + partialTargets: PartialTarget[], + extraArgs: any[] +) { + if ( + usesScopeType("notebookCell", partialTargets) && + !["editNewLineAbove", "editNewLineBelow"].includes(actionName) + ) { + throw new Error( + "The notebookCell scope type is currently only supported with the actions editNewLineAbove and editNewLineBelow" + ); + } +} + +function usesScopeType(scopeType: ScopeType, partialTargets: PartialTarget[]) { + return getPrimitiveTargets(partialTargets).some( + (partialTarget) => + partialTarget.modifier?.type === "containingScope" && + partialTarget.modifier.scopeType === scopeType + ); +} diff --git a/src/extension.ts b/src/extension.ts index edf78c17e1..0ddfb7afde 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -17,6 +17,7 @@ import { TestCase } from "./testUtil/TestCase"; import { ThatMark } from "./core/ThatMark"; import { TestCaseRecorder } from "./testUtil/TestCaseRecorder"; import { getParseTreeApi } from "./util/getExtensionApi"; +import { checkCommandValidity } from "./checkCommandValidity"; export async function activate(context: vscode.ExtensionContext) { const fontMeasurements = new FontMeasurements(context); @@ -113,6 +114,8 @@ export async function activate(context: vscode.ExtensionContext) { const action = graph.actions[actionName]; + checkCommandValidity(actionName, partialTargets, extraArgs); + const targets = inferFullTargets( partialTargets, action.targetPreferences diff --git a/src/processTargets/processModifier.ts b/src/processTargets/processModifier.ts index 6a10466f86..43f40d8ae9 100644 --- a/src/processTargets/processModifier.ts +++ b/src/processTargets/processModifier.ts @@ -67,6 +67,10 @@ function processScopeType( selection: SelectionWithEditor, modifier: ContainingScopeModifier ): SelectionWithContext[] | null { + if (modifier.scopeType === "notebookCell") { + return [{ selection, context: { isNotebookCell: true } }]; + } + const nodeMatcher = getNodeMatcher( selection.editor.document.languageId, modifier.scopeType, diff --git a/src/typings/Types.ts b/src/typings/Types.ts index cedcacf409..97fca8ef01 100644 --- a/src/typings/Types.ts +++ b/src/typings/Types.ts @@ -58,7 +58,7 @@ export type Mark = | CursorMarkToken | That | Source -// | LastCursorPosition Not implemented yet + // | LastCursorPosition Not implemented yet | DecoratedSymbol | LineNumber; export type Delimiter = @@ -86,6 +86,7 @@ export type ScopeType = | "list" | "name" | "namedFunction" + | "notebookCell" | "regex" | "statement" | "string" @@ -137,11 +138,8 @@ export type Modifier = | TailModifier; export type SelectionType = -// | "character" Not implemented - | "token" - | "line" - | "paragraph" - | "document"; + // | "character" Not implemented + "token" | "line" | "paragraph" | "document"; export type Position = "before" | "after" | "contents"; export type InsideOutsideType = "inside" | "outside" | null; @@ -228,6 +226,8 @@ export interface SelectionContext { * The range of the delimiter after the selection */ trailingDelimiterRange?: vscode.Range | null; + + isNotebookCell?: boolean; } export interface TypedSelection { diff --git a/src/util/targetUtils.ts b/src/util/targetUtils.ts index ba7c544c79..b4b6b444d0 100644 --- a/src/util/targetUtils.ts +++ b/src/util/targetUtils.ts @@ -1,6 +1,10 @@ import { TextEditor, Selection, Position } from "vscode"; import { groupBy } from "./itertools"; -import { TypedSelection } from "../typings/Types"; +import { + PartialPrimitiveTarget, + PartialTarget, + TypedSelection, +} from "../typings/Types"; export function ensureSingleEditor(targets: TypedSelection[]) { if (targets.length === 0) { @@ -86,3 +90,20 @@ function createTypeSelection( position: "contents", }; } + +export function getPrimitiveTargets(partialTargets: PartialTarget[]) { + return partialTargets.flatMap(getPrimitiveTargetsHelper); +} + +function getPrimitiveTargetsHelper( + partialTarget: PartialTarget +): PartialPrimitiveTarget[] { + switch (partialTarget.type) { + case "primitive": + return [partialTarget]; + case "list": + return partialTarget.elements.flatMap(getPrimitiveTargetsHelper); + case "range": + return [partialTarget.start, partialTarget.end]; + } +} From b3e2b0cdd76bc519dba836ffb2bd8c05e95acfea Mon Sep 17 00:00:00 2001 From: Pokey Rule Date: Fri, 20 Aug 2021 18:21:00 +0100 Subject: [PATCH 2/5] Add support for "drink" / "pour" cell --- src/checkCommandValidity.ts | 13 ++++--- src/processTargets/processModifier.ts | 4 -- src/processTargets/processSelectionType.ts | 17 +++++++++ src/test/runTest.ts | 10 +++-- .../recorded/selectionTypes/drinkCell.yml | 26 +++++++++++++ .../recorded/selectionTypes/drinkCellEach.yml | 37 +++++++++++++++++++ .../recorded/selectionTypes/pourCell.yml | 26 +++++++++++++ .../recorded/selectionTypes/pourCellEach.yml | 37 +++++++++++++++++++ src/typings/Types.ts | 3 +- 9 files changed, 158 insertions(+), 15 deletions(-) create mode 100644 src/test/suite/fixtures/recorded/selectionTypes/drinkCell.yml create mode 100644 src/test/suite/fixtures/recorded/selectionTypes/drinkCellEach.yml create mode 100644 src/test/suite/fixtures/recorded/selectionTypes/pourCell.yml create mode 100644 src/test/suite/fixtures/recorded/selectionTypes/pourCellEach.yml diff --git a/src/checkCommandValidity.ts b/src/checkCommandValidity.ts index 2cb62afb74..e9784932d5 100644 --- a/src/checkCommandValidity.ts +++ b/src/checkCommandValidity.ts @@ -1,4 +1,4 @@ -import { ActionType, PartialTarget, ScopeType } from "./typings/Types"; +import { ActionType, PartialTarget, SelectionType } from "./typings/Types"; import { getPrimitiveTargets } from "./util/targetUtils"; export function checkCommandValidity( @@ -7,7 +7,7 @@ export function checkCommandValidity( extraArgs: any[] ) { if ( - usesScopeType("notebookCell", partialTargets) && + usesSelectionType("notebookCell", partialTargets) && !["editNewLineAbove", "editNewLineBelow"].includes(actionName) ) { throw new Error( @@ -16,10 +16,11 @@ export function checkCommandValidity( } } -function usesScopeType(scopeType: ScopeType, partialTargets: PartialTarget[]) { +function usesSelectionType( + selectionType: SelectionType, + partialTargets: PartialTarget[] +) { return getPrimitiveTargets(partialTargets).some( - (partialTarget) => - partialTarget.modifier?.type === "containingScope" && - partialTarget.modifier.scopeType === scopeType + (partialTarget) => partialTarget.selectionType === selectionType ); } diff --git a/src/processTargets/processModifier.ts b/src/processTargets/processModifier.ts index 43f40d8ae9..6a10466f86 100644 --- a/src/processTargets/processModifier.ts +++ b/src/processTargets/processModifier.ts @@ -67,10 +67,6 @@ function processScopeType( selection: SelectionWithEditor, modifier: ContainingScopeModifier ): SelectionWithContext[] | null { - if (modifier.scopeType === "notebookCell") { - return [{ selection, context: { isNotebookCell: true } }]; - } - const nodeMatcher = getNodeMatcher( selection.editor.document.languageId, modifier.scopeType, diff --git a/src/processTargets/processSelectionType.ts b/src/processTargets/processSelectionType.ts index a8c53b11ca..bbfd5708b1 100644 --- a/src/processTargets/processSelectionType.ts +++ b/src/processTargets/processSelectionType.ts @@ -23,6 +23,8 @@ export default function ( switch (target.selectionType) { case "token": return processToken(target, selection, selectionContext); + case "notebookCell": + return processNotebookCell(target, selection, selectionContext); case "document": return processDocument(target, selection, selectionContext); case "line": @@ -32,6 +34,21 @@ export default function ( } } +function processNotebookCell( + target: PrimitiveTarget, + selection: SelectionWithEditor, + selectionContext: SelectionContext +): TypedSelection { + const { selectionType, insideOutsideType, position } = target; + return { + selection, + selectionType, + position, + insideOutsideType, + selectionContext: { ...selectionContext, isNotebookCell: true }, + }; +} + function processToken( target: PrimitiveTarget, selection: SelectionWithEditor, diff --git a/src/test/runTest.ts b/src/test/runTest.ts index f1d9bdaa48..9c36329ae9 100644 --- a/src/test/runTest.ts +++ b/src/test/runTest.ts @@ -7,6 +7,8 @@ import { downloadAndUnzipVSCode, } from "vscode-test"; +const extensionDependencies = ["pokey.parse-tree"]; + async function main() { try { // The folder containing the Extension Manifest package.json @@ -22,9 +24,11 @@ async function main() { resolveCliPathFromVSCodeExecutablePath(vscodeExecutablePath); // Install extension dependencies - cp.spawnSync(cliPath, ["--install-extension", "pokey.parse-tree"], { - encoding: "utf-8", - stdio: "inherit", + extensionDependencies.forEach((dependency) => { + cp.spawnSync(cliPath, ["--install-extension", dependency], { + encoding: "utf-8", + stdio: "inherit", + }); }); // Run the integration test diff --git a/src/test/suite/fixtures/recorded/selectionTypes/drinkCell.yml b/src/test/suite/fixtures/recorded/selectionTypes/drinkCell.yml new file mode 100644 index 0000000000..3f9981a8b4 --- /dev/null +++ b/src/test/suite/fixtures/recorded/selectionTypes/drinkCell.yml @@ -0,0 +1,26 @@ +spokenForm: drink cell +languageId: python +command: + actionName: editNewLineAbove + partialTargets: + - {type: primitive, selectionType: notebookCell} + extraArgs: [] +marks: {} +initialState: + documentContents: |- + # %% + print("hello") + selections: + - anchor: {line: 1, character: 12} + active: {line: 1, character: 12} +finalState: + documentContents: |- + # %% + print("hello") + selections: + - anchor: {line: 1, character: 0} + active: {line: 1, character: 0} + thatMark: + - anchor: {line: 1, character: 0} + active: {line: 1, character: 0} +fullTargets: [{type: primitive, mark: {type: cursor}, selectionType: notebookCell, position: contents, insideOutsideType: inside, modifier: {type: identity}}] diff --git a/src/test/suite/fixtures/recorded/selectionTypes/drinkCellEach.yml b/src/test/suite/fixtures/recorded/selectionTypes/drinkCellEach.yml new file mode 100644 index 0000000000..57c7306660 --- /dev/null +++ b/src/test/suite/fixtures/recorded/selectionTypes/drinkCellEach.yml @@ -0,0 +1,37 @@ +spokenForm: drink cell each +languageId: python +command: + actionName: editNewLineAbove + partialTargets: + - type: primitive + selectionType: notebookCell + mark: {type: decoratedSymbol, symbolColor: default, character: e} + extraArgs: [] +marks: + default.e: + start: {line: 1, character: 7} + end: {line: 1, character: 12} +initialState: + documentContents: |- + # %% + print("hello") + + # %% + print("hello") + selections: + - anchor: {line: 4, character: 12} + active: {line: 4, character: 12} +finalState: + documentContents: |- + # %% + print("hello") + + # %% + print("hello") + selections: + - anchor: {line: 1, character: 0} + active: {line: 1, character: 0} + thatMark: + - anchor: {line: 1, character: 0} + active: {line: 1, character: 0} +fullTargets: [{type: primitive, mark: {type: decoratedSymbol, symbolColor: default, character: e}, selectionType: notebookCell, position: contents, insideOutsideType: inside, modifier: {type: identity}}] diff --git a/src/test/suite/fixtures/recorded/selectionTypes/pourCell.yml b/src/test/suite/fixtures/recorded/selectionTypes/pourCell.yml new file mode 100644 index 0000000000..660a945be5 --- /dev/null +++ b/src/test/suite/fixtures/recorded/selectionTypes/pourCell.yml @@ -0,0 +1,26 @@ +spokenForm: pour cell +languageId: python +command: + actionName: editNewLineBelow + partialTargets: + - {type: primitive, selectionType: notebookCell} + extraArgs: [] +marks: {} +initialState: + documentContents: |- + # %% + print("hello") + selections: + - anchor: {line: 1, character: 12} + active: {line: 1, character: 12} +finalState: + documentContents: |- + # %% + print("hello") + selections: + - anchor: {line: 3, character: 0} + active: {line: 3, character: 0} + thatMark: + - anchor: {line: 3, character: 0} + active: {line: 3, character: 0} +fullTargets: [{type: primitive, mark: {type: cursor}, selectionType: notebookCell, position: contents, insideOutsideType: inside, modifier: {type: identity}}] diff --git a/src/test/suite/fixtures/recorded/selectionTypes/pourCellEach.yml b/src/test/suite/fixtures/recorded/selectionTypes/pourCellEach.yml new file mode 100644 index 0000000000..41741d6264 --- /dev/null +++ b/src/test/suite/fixtures/recorded/selectionTypes/pourCellEach.yml @@ -0,0 +1,37 @@ +spokenForm: pour cell each +languageId: python +command: + actionName: editNewLineBelow + partialTargets: + - type: primitive + selectionType: notebookCell + mark: {type: decoratedSymbol, symbolColor: default, character: e} + extraArgs: [] +marks: + default.e: + start: {line: 1, character: 7} + end: {line: 1, character: 12} +initialState: + documentContents: |- + # %% + print("hello") + + # %% + print("hello") + selections: + - anchor: {line: 4, character: 12} + active: {line: 4, character: 12} +finalState: + documentContents: |- + # %% + print("hello") + + # %% + print("hello") + selections: + - anchor: {line: 4, character: 0} + active: {line: 4, character: 0} + thatMark: + - anchor: {line: 4, character: 0} + active: {line: 4, character: 0} +fullTargets: [{type: primitive, mark: {type: decoratedSymbol, symbolColor: default, character: e}, selectionType: notebookCell, position: contents, insideOutsideType: inside, modifier: {type: identity}}] diff --git a/src/typings/Types.ts b/src/typings/Types.ts index 97fca8ef01..f41612e56b 100644 --- a/src/typings/Types.ts +++ b/src/typings/Types.ts @@ -86,7 +86,6 @@ export type ScopeType = | "list" | "name" | "namedFunction" - | "notebookCell" | "regex" | "statement" | "string" @@ -139,7 +138,7 @@ export type Modifier = export type SelectionType = // | "character" Not implemented - "token" | "line" | "paragraph" | "document"; + "token" | "line" | "notebookCell" | "paragraph" | "document"; export type Position = "before" | "after" | "contents"; export type InsideOutsideType = "inside" | "outside" | null; From 9c55d1faaaf9e39a555914a231991bc72859ddcf Mon Sep 17 00:00:00 2001 From: Pokey Rule Date: Fri, 20 Aug 2021 18:46:51 +0100 Subject: [PATCH 3/5] Add docstring --- src/util/targetUtils.ts | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/util/targetUtils.ts b/src/util/targetUtils.ts index b4b6b444d0..78f41e253d 100644 --- a/src/util/targetUtils.ts +++ b/src/util/targetUtils.ts @@ -91,19 +91,26 @@ function createTypeSelection( }; } -export function getPrimitiveTargets(partialTargets: PartialTarget[]) { - return partialTargets.flatMap(getPrimitiveTargetsHelper); +/** + * Given a list of targets, recursively descends all targets and returns every + * contained primitive target. + * + * @param targets The targets to extract from + * @returns A list of primitive targets + */ +export function getPrimitiveTargets(targets: PartialTarget[]) { + return targets.flatMap(getPrimitiveTargetsHelper); } function getPrimitiveTargetsHelper( - partialTarget: PartialTarget + target: PartialTarget ): PartialPrimitiveTarget[] { - switch (partialTarget.type) { + switch (target.type) { case "primitive": - return [partialTarget]; + return [target]; case "list": - return partialTarget.elements.flatMap(getPrimitiveTargetsHelper); + return target.elements.flatMap(getPrimitiveTargetsHelper); case "range": - return [partialTarget.start, partialTarget.end]; + return [target.start, target.end]; } } From 56027a79faeb350413db4ecbec16808c3a5279c4 Mon Sep 17 00:00:00 2001 From: Pokey Rule Date: Sat, 21 Aug 2021 16:45:41 +0100 Subject: [PATCH 4/5] Add jupyter dependency --- src/test/runTest.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/runTest.ts b/src/test/runTest.ts index 9c36329ae9..aac1f6427f 100644 --- a/src/test/runTest.ts +++ b/src/test/runTest.ts @@ -7,7 +7,7 @@ import { downloadAndUnzipVSCode, } from "vscode-test"; -const extensionDependencies = ["pokey.parse-tree"]; +const extensionDependencies = ["pokey.parse-tree", "ms-toolsai.jupyter"]; async function main() { try { From 8f1abdcfbafe1da2b8a74b39a4878d3e90f76dc3 Mon Sep 17 00:00:00 2001 From: Pokey Rule Date: Mon, 23 Aug 2021 16:20:21 +0100 Subject: [PATCH 5/5] Add comments --- src/test/suite/fixtures/recorded/selectionTypes/drinkCell.yml | 1 + .../suite/fixtures/recorded/selectionTypes/drinkCellEach.yml | 1 + src/test/suite/fixtures/recorded/selectionTypes/pourCell.yml | 1 + src/test/suite/fixtures/recorded/selectionTypes/pourCellEach.yml | 1 + 4 files changed, 4 insertions(+) diff --git a/src/test/suite/fixtures/recorded/selectionTypes/drinkCell.yml b/src/test/suite/fixtures/recorded/selectionTypes/drinkCell.yml index 3f9981a8b4..908a2ed3f7 100644 --- a/src/test/suite/fixtures/recorded/selectionTypes/drinkCell.yml +++ b/src/test/suite/fixtures/recorded/selectionTypes/drinkCell.yml @@ -1,3 +1,4 @@ +# Note that this is just checking that no errors are thrown spokenForm: drink cell languageId: python command: diff --git a/src/test/suite/fixtures/recorded/selectionTypes/drinkCellEach.yml b/src/test/suite/fixtures/recorded/selectionTypes/drinkCellEach.yml index 57c7306660..1c2379f7fe 100644 --- a/src/test/suite/fixtures/recorded/selectionTypes/drinkCellEach.yml +++ b/src/test/suite/fixtures/recorded/selectionTypes/drinkCellEach.yml @@ -1,3 +1,4 @@ +# Note that this is just checking that no errors are thrown spokenForm: drink cell each languageId: python command: diff --git a/src/test/suite/fixtures/recorded/selectionTypes/pourCell.yml b/src/test/suite/fixtures/recorded/selectionTypes/pourCell.yml index 660a945be5..f57d13fec8 100644 --- a/src/test/suite/fixtures/recorded/selectionTypes/pourCell.yml +++ b/src/test/suite/fixtures/recorded/selectionTypes/pourCell.yml @@ -1,3 +1,4 @@ +# Note that this is just checking that no errors are thrown spokenForm: pour cell languageId: python command: diff --git a/src/test/suite/fixtures/recorded/selectionTypes/pourCellEach.yml b/src/test/suite/fixtures/recorded/selectionTypes/pourCellEach.yml index 41741d6264..6596cf26bb 100644 --- a/src/test/suite/fixtures/recorded/selectionTypes/pourCellEach.yml +++ b/src/test/suite/fixtures/recorded/selectionTypes/pourCellEach.yml @@ -1,3 +1,4 @@ +# Note that this is just checking that no errors are thrown spokenForm: pour cell each languageId: python command: