From b388490430c93c42489ceda9954ce8e4df1105c5 Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Wed, 13 Dec 2023 11:45:32 +0100 Subject: [PATCH 1/8] Added grand scope --- .../src/modifiers/simple_scope_modifier.py | 18 ++++++++++++- cursorless-talon/src/spoken_forms.json | 2 +- .../primitiveTargetToSpokenForm.ts | 11 ++++++++ .../modifiers/getContainingScopeTarget.ts | 4 +-- .../src/spokenForms/SpokenFormType.ts | 3 ++- .../spokenForms/defaultSpokenFormMapCore.ts | 1 + .../containingScope/changeGrandState.yml | 27 +++++++++++++++++++ .../containingScope/changeGrandState2.yml | 26 ++++++++++++++++++ 8 files changed, 87 insertions(+), 5 deletions(-) create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/containingScope/changeGrandState.yml create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/containingScope/changeGrandState2.yml diff --git a/cursorless-talon/src/modifiers/simple_scope_modifier.py b/cursorless-talon/src/modifiers/simple_scope_modifier.py index 7725623304..4c8241e5f6 100644 --- a/cursorless-talon/src/modifiers/simple_scope_modifier.py +++ b/cursorless-talon/src/modifiers/simple_scope_modifier.py @@ -15,7 +15,23 @@ ) def cursorless_simple_scope_modifier(m) -> dict[str, Any]: """Containing scope, every scope, etc""" + if hasattr(m, "cursorless_simple_scope_modifier"): + modifier = m.cursorless_simple_scope_modifier + + if modifier == "every": + return { + "type": "everyScope", + "scopeType": m.cursorless_scope_type, + } + + if modifier == "grand": + return { + "type": "containingScope", + "scopeType": m.cursorless_scope_type, + "ancestorIndex": 1, + } + return { - "type": "everyScope" if m[0] == "every" else "containingScope", + "type": "containingScope", "scopeType": m.cursorless_scope_type, } diff --git a/cursorless-talon/src/spoken_forms.json b/cursorless-talon/src/spoken_forms.json index eec6aac6e2..c25fa58fdc 100644 --- a/cursorless-talon/src/spoken_forms.json +++ b/cursorless-talon/src/spoken_forms.json @@ -81,7 +81,7 @@ "its": "inferPreviousMark", "visible": "visible" }, - "simple_scope_modifier": { "every": "every" }, + "simple_scope_modifier": { "every": "every", "grand": "grand" }, "interior_modifier": { "inside": "interiorOnly" }, diff --git a/packages/cursorless-engine/src/generateSpokenForm/primitiveTargetToSpokenForm.ts b/packages/cursorless-engine/src/generateSpokenForm/primitiveTargetToSpokenForm.ts index 6dba6a8f65..307ed6cc44 100644 --- a/packages/cursorless-engine/src/generateSpokenForm/primitiveTargetToSpokenForm.ts +++ b/packages/cursorless-engine/src/generateSpokenForm/primitiveTargetToSpokenForm.ts @@ -49,6 +49,17 @@ export class PrimitiveTargetSpokenFormGenerator { throw new NoSpokenFormError(`Modifier '${modifier.type}'`); case "containingScope": + if (modifier.ancestorIndex != null) { + if (modifier.ancestorIndex === 1) { + return [ + this.spokenFormMap.modifierExtra.grandScope, + this.handleScopeType(modifier.scopeType), + ]; + } + throw new NoSpokenFormError( + `Modifier '${modifier.type}' with ancestor index != 1`, + ); + } return [this.handleScopeType(modifier.scopeType)]; case "everyScope": diff --git a/packages/cursorless-engine/src/processTargets/modifiers/getContainingScopeTarget.ts b/packages/cursorless-engine/src/processTargets/modifiers/getContainingScopeTarget.ts index 24aa3f6c6a..9d1bc257df 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/getContainingScopeTarget.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/getContainingScopeTarget.ts @@ -36,8 +36,8 @@ export function getContainingScopeTarget( scope = expandFromPosition( scopeHandler, editor, - scope.domain.end, - "forward", + scope.domain.start, + "backward", ancestorIndex - 1, ); } diff --git a/packages/cursorless-engine/src/spokenForms/SpokenFormType.ts b/packages/cursorless-engine/src/spokenForms/SpokenFormType.ts index e1d604fce3..9de080bdc5 100644 --- a/packages/cursorless-engine/src/spokenForms/SpokenFormType.ts +++ b/packages/cursorless-engine/src/spokenForms/SpokenFormType.ts @@ -60,6 +60,7 @@ type ModifierExtra = | "previous" | "next" | "forward" - | "backward"; + | "backward" + | "grandScope"; export type SpokenFormType = keyof SpokenFormMapKeyTypes; diff --git a/packages/cursorless-engine/src/spokenForms/defaultSpokenFormMapCore.ts b/packages/cursorless-engine/src/spokenForms/defaultSpokenFormMapCore.ts index 6b94ec67ba..47f9e436a0 100644 --- a/packages/cursorless-engine/src/spokenForms/defaultSpokenFormMapCore.ts +++ b/packages/cursorless-engine/src/spokenForms/defaultSpokenFormMapCore.ts @@ -133,6 +133,7 @@ export const defaultSpokenFormMapCore: DefaultSpokenFormMapDefinition = { next: "next", forward: "forward", backward: "backward", + grandScope: "grand", }, customRegex: {}, diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/containingScope/changeGrandState.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/containingScope/changeGrandState.yml new file mode 100644 index 0000000000..dd890307c3 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/containingScope/changeGrandState.yml @@ -0,0 +1,27 @@ +languageId: javascript +command: + version: 6 + spokenForm: change grand state + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - type: containingScope + scopeType: {type: statement} + ancestorIndex: 1 + usePrePhraseSnapshot: true +initialState: + documentContents: |- + function myFunk() { + const value = 2 + } + selections: + - anchor: {line: 1, character: 4} + active: {line: 1, character: 4} + marks: {} +finalState: + documentContents: "" + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/containingScope/changeGrandState2.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/containingScope/changeGrandState2.yml new file mode 100644 index 0000000000..4a13a62f9f --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/containingScope/changeGrandState2.yml @@ -0,0 +1,26 @@ +languageId: python +command: + version: 6 + spokenForm: change grand state + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - type: containingScope + scopeType: {type: statement} + ancestorIndex: 1 + usePrePhraseSnapshot: true +initialState: + documentContents: |- + def my_funk(): + value = 2 + selections: + - anchor: {line: 1, character: 4} + active: {line: 1, character: 4} + marks: {} +finalState: + documentContents: "" + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} From f8acb3faf17b469b4a6964551b4edfe32d0ab545 Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Wed, 13 Dec 2023 11:54:17 +0100 Subject: [PATCH 2/8] Updated documentation --- changelog/2023-12.addedGrandScopeModifier.md | 6 ++++++ cursorless-talon/src/cheatsheet/sections/modifiers.py | 11 +++++++++++ docs/user/README.md | 9 +++++++++ 3 files changed, 26 insertions(+) create mode 100644 changelog/2023-12.addedGrandScopeModifier.md diff --git a/changelog/2023-12.addedGrandScopeModifier.md b/changelog/2023-12.addedGrandScopeModifier.md new file mode 100644 index 0000000000..394d516509 --- /dev/null +++ b/changelog/2023-12.addedGrandScopeModifier.md @@ -0,0 +1,6 @@ +--- +tags: [enhancement] +pullRequest: 2130 +--- + +- Added `grand` modifier. This modifier will take the containing grandparent scope. eg `"take grand statement air"` diff --git a/cursorless-talon/src/cheatsheet/sections/modifiers.py b/cursorless-talon/src/cheatsheet/sections/modifiers.py index 576edc4721..5da983f90d 100644 --- a/cursorless-talon/src/cheatsheet/sections/modifiers.py +++ b/cursorless-talon/src/cheatsheet/sections/modifiers.py @@ -22,6 +22,7 @@ def get_modifiers(): "extendThroughStartOf", "extendThroughEndOf", "every", + "grand", "first", "last", "previous", @@ -101,6 +102,16 @@ def get_modifiers(): }, ], }, + { + "id": "grand", + "type": "modifier", + "variations": [ + { + "spokenForm": f"{complex_modifiers['grand']} ", + "description": "Grandparent containing instance of ", + }, + ], + }, { "id": "relativeScope", "type": "modifier", diff --git a/docs/user/README.md b/docs/user/README.md index 1b6f1674c3..897534a1d9 100644 --- a/docs/user/README.md +++ b/docs/user/README.md @@ -215,6 +215,15 @@ The command `"every"` can be used to select a syntactic element and all of its m For example, the command `take every key [blue] air` will select every key in the map/object/dict including the token with a blue hat over the letter 'a'. +##### `"grand"` + +The command `"grand"` can be used to select the grand parent of the containing syntactic element. + +- `"take grand statement air"` +- `"take grand funk air"` + +For example, the command `take grand statement [blue] air` will select that parent statement of the statement containing the token with a blue hat over the letter 'a'. + ##### Sub-token modifiers ###### `"sub"` From 0b0ad9b93892e111ba6c13b2b626a5d4730ed900 Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Wed, 13 Dec 2023 14:21:36 +0100 Subject: [PATCH 3/8] Spoken form for zero ancestor --- .../primitiveTargetToSpokenForm.ts | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/cursorless-engine/src/generateSpokenForm/primitiveTargetToSpokenForm.ts b/packages/cursorless-engine/src/generateSpokenForm/primitiveTargetToSpokenForm.ts index 307ed6cc44..f7c431fe8d 100644 --- a/packages/cursorless-engine/src/generateSpokenForm/primitiveTargetToSpokenForm.ts +++ b/packages/cursorless-engine/src/generateSpokenForm/primitiveTargetToSpokenForm.ts @@ -49,18 +49,18 @@ export class PrimitiveTargetSpokenFormGenerator { throw new NoSpokenFormError(`Modifier '${modifier.type}'`); case "containingScope": - if (modifier.ancestorIndex != null) { - if (modifier.ancestorIndex === 1) { - return [ - this.spokenFormMap.modifierExtra.grandScope, - this.handleScopeType(modifier.scopeType), - ]; - } - throw new NoSpokenFormError( - `Modifier '${modifier.type}' with ancestor index != 1`, - ); + if (modifier.ancestorIndex == null || modifier.ancestorIndex === 0) { + return this.handleScopeType(modifier.scopeType); } - return [this.handleScopeType(modifier.scopeType)]; + if (modifier.ancestorIndex === 1) { + return [ + this.spokenFormMap.modifierExtra.grandScope, + this.handleScopeType(modifier.scopeType), + ]; + } + throw new NoSpokenFormError( + `Modifier '${modifier.type}' with ancestor index != 1`, + ); case "everyScope": return [ From f39ae87f60a08730325b2efecee094aaf14e9ef1 Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Wed, 13 Dec 2023 14:22:15 +0100 Subject: [PATCH 4/8] Updated error message --- .../src/generateSpokenForm/primitiveTargetToSpokenForm.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cursorless-engine/src/generateSpokenForm/primitiveTargetToSpokenForm.ts b/packages/cursorless-engine/src/generateSpokenForm/primitiveTargetToSpokenForm.ts index f7c431fe8d..113c569c35 100644 --- a/packages/cursorless-engine/src/generateSpokenForm/primitiveTargetToSpokenForm.ts +++ b/packages/cursorless-engine/src/generateSpokenForm/primitiveTargetToSpokenForm.ts @@ -59,7 +59,7 @@ export class PrimitiveTargetSpokenFormGenerator { ]; } throw new NoSpokenFormError( - `Modifier '${modifier.type}' with ancestor index != 1`, + `Modifier '${modifier.type}' with ancestor index > 1`, ); case "everyScope": From 984555b7cbbe42e33d1cbab75ebd76cb0e8079ef Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Wed, 13 Dec 2023 14:22:28 +0100 Subject: [PATCH 5/8] Update message --- .../src/generateSpokenForm/primitiveTargetToSpokenForm.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cursorless-engine/src/generateSpokenForm/primitiveTargetToSpokenForm.ts b/packages/cursorless-engine/src/generateSpokenForm/primitiveTargetToSpokenForm.ts index 113c569c35..f54ed8c922 100644 --- a/packages/cursorless-engine/src/generateSpokenForm/primitiveTargetToSpokenForm.ts +++ b/packages/cursorless-engine/src/generateSpokenForm/primitiveTargetToSpokenForm.ts @@ -59,7 +59,7 @@ export class PrimitiveTargetSpokenFormGenerator { ]; } throw new NoSpokenFormError( - `Modifier '${modifier.type}' with ancestor index > 1`, + `Modifier '${modifier.type}' with ancestor index > 0`, ); case "everyScope": From ca48a44408b08682400b524431f37e1c7fe376dd Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Wed, 13 Dec 2023 15:07:25 +0100 Subject: [PATCH 6/8] update logic --- .../modifiers/getContainingScopeTarget.ts | 9 ++++-- .../containingScope/changeGrandState3.yml | 29 +++++++++++++++++++ 2 files changed, 35 insertions(+), 3 deletions(-) create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/containingScope/changeGrandState3.yml diff --git a/packages/cursorless-engine/src/processTargets/modifiers/getContainingScopeTarget.ts b/packages/cursorless-engine/src/processTargets/modifiers/getContainingScopeTarget.ts index 9d1bc257df..c1b38b610d 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/getContainingScopeTarget.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/getContainingScopeTarget.ts @@ -36,9 +36,10 @@ export function getContainingScopeTarget( scope = expandFromPosition( scopeHandler, editor, - scope.domain.start, - "backward", - ancestorIndex - 1, + scope.domain.end, + "forward", + ancestorIndex, + true, ); } @@ -86,10 +87,12 @@ function expandFromPosition( position: Position, direction: Direction, ancestorIndex: number, + allowAdjacentScopes: boolean = false, ): TargetScope | undefined { let nextAncestorIndex = 0; for (const scope of scopeHandler.generateScopes(editor, position, direction, { containment: "required", + allowAdjacentScopes, })) { if (nextAncestorIndex === ancestorIndex) { return scope; diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/containingScope/changeGrandState3.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/containingScope/changeGrandState3.yml new file mode 100644 index 0000000000..59240fd15e --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/containingScope/changeGrandState3.yml @@ -0,0 +1,29 @@ +languageId: javascript +command: + version: 6 + spokenForm: change grand state + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - type: containingScope + scopeType: {type: statement} + ancestorIndex: 1 + usePrePhraseSnapshot: true +initialState: + documentContents: |- + class MyClass { + myFunk() { + + } + } + selections: + - anchor: {line: 2, character: 8} + active: {line: 2, character: 8} + marks: {} +finalState: + documentContents: "" + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} From 97ba14b901bf42dbfcb55c262ce236ba960e4e43 Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Sat, 16 Dec 2023 09:13:41 +0100 Subject: [PATCH 7/8] update error message --- .../src/generateSpokenForm/primitiveTargetToSpokenForm.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cursorless-engine/src/generateSpokenForm/primitiveTargetToSpokenForm.ts b/packages/cursorless-engine/src/generateSpokenForm/primitiveTargetToSpokenForm.ts index f54ed8c922..256ef472ae 100644 --- a/packages/cursorless-engine/src/generateSpokenForm/primitiveTargetToSpokenForm.ts +++ b/packages/cursorless-engine/src/generateSpokenForm/primitiveTargetToSpokenForm.ts @@ -59,7 +59,7 @@ export class PrimitiveTargetSpokenFormGenerator { ]; } throw new NoSpokenFormError( - `Modifier '${modifier.type}' with ancestor index > 0`, + `Modifier '${modifier.type}' with ancestor index ${modifier.ancestorIndex}`, ); case "everyScope": From 1f128f4e0c0b854e04094ffd0dda8326d244bfdc Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Sat, 16 Dec 2023 11:33:27 +0100 Subject: [PATCH 8/8] Rename grand to ancestor --- cursorless-talon/src/cheatsheet/sections/modifiers.py | 6 +++--- cursorless-talon/src/modifiers/simple_scope_modifier.py | 2 +- cursorless-talon/src/spoken_forms.json | 2 +- .../src/generateSpokenForm/primitiveTargetToSpokenForm.ts | 2 +- .../cursorless-engine/src/spokenForms/SpokenFormType.ts | 2 +- .../src/spokenForms/defaultSpokenFormMapCore.ts | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/cursorless-talon/src/cheatsheet/sections/modifiers.py b/cursorless-talon/src/cheatsheet/sections/modifiers.py index 5da983f90d..55d3f396a3 100644 --- a/cursorless-talon/src/cheatsheet/sections/modifiers.py +++ b/cursorless-talon/src/cheatsheet/sections/modifiers.py @@ -22,7 +22,7 @@ def get_modifiers(): "extendThroughStartOf", "extendThroughEndOf", "every", - "grand", + "ancestor", "first", "last", "previous", @@ -103,11 +103,11 @@ def get_modifiers(): ], }, { - "id": "grand", + "id": "ancestor", "type": "modifier", "variations": [ { - "spokenForm": f"{complex_modifiers['grand']} ", + "spokenForm": f"{complex_modifiers['ancestor']} ", "description": "Grandparent containing instance of ", }, ], diff --git a/cursorless-talon/src/modifiers/simple_scope_modifier.py b/cursorless-talon/src/modifiers/simple_scope_modifier.py index 4c8241e5f6..5d51ba8a4e 100644 --- a/cursorless-talon/src/modifiers/simple_scope_modifier.py +++ b/cursorless-talon/src/modifiers/simple_scope_modifier.py @@ -24,7 +24,7 @@ def cursorless_simple_scope_modifier(m) -> dict[str, Any]: "scopeType": m.cursorless_scope_type, } - if modifier == "grand": + if modifier == "ancestor": return { "type": "containingScope", "scopeType": m.cursorless_scope_type, diff --git a/cursorless-talon/src/spoken_forms.json b/cursorless-talon/src/spoken_forms.json index c25fa58fdc..20fe7b1260 100644 --- a/cursorless-talon/src/spoken_forms.json +++ b/cursorless-talon/src/spoken_forms.json @@ -81,7 +81,7 @@ "its": "inferPreviousMark", "visible": "visible" }, - "simple_scope_modifier": { "every": "every", "grand": "grand" }, + "simple_scope_modifier": { "every": "every", "grand": "ancestor" }, "interior_modifier": { "inside": "interiorOnly" }, diff --git a/packages/cursorless-engine/src/generateSpokenForm/primitiveTargetToSpokenForm.ts b/packages/cursorless-engine/src/generateSpokenForm/primitiveTargetToSpokenForm.ts index 256ef472ae..28c92afb5f 100644 --- a/packages/cursorless-engine/src/generateSpokenForm/primitiveTargetToSpokenForm.ts +++ b/packages/cursorless-engine/src/generateSpokenForm/primitiveTargetToSpokenForm.ts @@ -54,7 +54,7 @@ export class PrimitiveTargetSpokenFormGenerator { } if (modifier.ancestorIndex === 1) { return [ - this.spokenFormMap.modifierExtra.grandScope, + this.spokenFormMap.modifierExtra.ancestor, this.handleScopeType(modifier.scopeType), ]; } diff --git a/packages/cursorless-engine/src/spokenForms/SpokenFormType.ts b/packages/cursorless-engine/src/spokenForms/SpokenFormType.ts index 9de080bdc5..3665eb5cd9 100644 --- a/packages/cursorless-engine/src/spokenForms/SpokenFormType.ts +++ b/packages/cursorless-engine/src/spokenForms/SpokenFormType.ts @@ -61,6 +61,6 @@ type ModifierExtra = | "next" | "forward" | "backward" - | "grandScope"; + | "ancestor"; export type SpokenFormType = keyof SpokenFormMapKeyTypes; diff --git a/packages/cursorless-engine/src/spokenForms/defaultSpokenFormMapCore.ts b/packages/cursorless-engine/src/spokenForms/defaultSpokenFormMapCore.ts index 47f9e436a0..300c2cacab 100644 --- a/packages/cursorless-engine/src/spokenForms/defaultSpokenFormMapCore.ts +++ b/packages/cursorless-engine/src/spokenForms/defaultSpokenFormMapCore.ts @@ -133,7 +133,7 @@ export const defaultSpokenFormMapCore: DefaultSpokenFormMapDefinition = { next: "next", forward: "forward", backward: "backward", - grandScope: "grand", + ancestor: "grand", }, customRegex: {},