Skip to content

Commit

Permalink
Added literal mark (#2593)
Browse files Browse the repository at this point in the history
I'm not sure this is a direction we want to go or if this is a good
implementation, but for editors where we don't have hats it would be
nice if you could just speak the literal text instead.
`"take hello world"`
`"chuck state get value"`

In this implementation I'm only looking in the viewport and I'm solving
ambiguity by selecting the match closest to a cursor selection.

## Checklist

- [x] I have added
[tests](https://www.cursorless.org/docs/contributing/test-case-recorder/)
- [/] I have updated the
[docs](https://github.com/cursorless-dev/cursorless/tree/main/docs) and
[cheatsheet](https://github.com/cursorless-dev/cursorless/tree/main/cursorless-talon/src/cheatsheet)
- [/] I have not broken the cheatsheet

- [x] Add list for literal prefix/keyword. Empty by default. test that
this behaves properly
- [x] Tag to remove prefix and have literal as a replacement for hats.
Check dfa compilation
- [x] Tag to default to preferred scope instead of containing scope

---------

Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
Co-authored-by: Phil Cohen <[email protected]>
  • Loading branch information
3 people authored Aug 4, 2024
1 parent 0e01381 commit 21715e4
Show file tree
Hide file tree
Showing 24 changed files with 516 additions and 7 deletions.
50 changes: 50 additions & 0 deletions cursorless-talon/src/marks/literal_mark.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
from talon import Context, Module

from .mark_types import LiteralMark

mod = Module()

mod.list("private_cursorless_literal_mark", desc="Cursorless literal mark")

# This is a private tag and should not be used by non Cursorless developers
mod.tag(
"private_cursorless_literal_mark_no_prefix",
desc="Tag for enabling literal mark without prefix",
)

ctx_no_prefix = Context()
ctx_no_prefix.matches = r"""
tag: user.private_cursorless_literal_mark_no_prefix
"""


# NB: <phrase> is used over <user.text> for DFA performance reasons
# (we intend to replace this with a dynamic list of document contents eventually)
@mod.capture(rule="{user.private_cursorless_literal_mark} <phrase>")
def cursorless_literal_mark(m) -> LiteralMark:
return construct_mark(str(m.phrase))


@ctx_no_prefix.capture("user.cursorless_literal_mark", rule="<phrase>")
def cursorless_literal_mark_no_prefix(m) -> LiteralMark:
return construct_mark(str(m.phrase))


def construct_mark(text: str) -> LiteralMark:
return {
"type": "literal",
"modifier": {
"type": "preferredScope",
"scopeType": {
"type": "customRegex",
"regex": construct_fuzzy_regex(text),
"flags": "gui",
},
},
}


def construct_fuzzy_regex(text: str) -> str:
parts = text.split(" ")
# Between each word there can be any number of non-alpha symbols (including escape characters: \t\r\n). No separator at all is also valid -- for example, when searching for a camelCase identifier.
return r"([^a-zA-Z]|\\[trn])*".join(parts)
1 change: 1 addition & 0 deletions cursorless-talon/src/marks/mark.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
@mod.capture(
rule=(
"<user.cursorless_decorated_symbol> | "
"<user.cursorless_literal_mark> | "
"<user.cursorless_simple_mark> |"
"<user.cursorless_line_number>" # row (ie absolute mod 100), up, down
)
Expand Down
7 changes: 6 additions & 1 deletion cursorless-talon/src/marks/mark_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ class DecoratedSymbol(TypedDict):
character: str


class LiteralMark(TypedDict):
type: Literal["literal"]
modifier: dict


SimpleMark = dict[Literal["type"], str]

LineNumberType = Literal["modulo100", "relative"]
Expand All @@ -28,4 +33,4 @@ class LineNumberRange(TypedDict):

LineNumber = Union[LineNumberMark, LineNumberRange]

Mark = Union[DecoratedSymbol, SimpleMark, LineNumber]
Mark = Union[DecoratedSymbol, LiteralMark, SimpleMark, LineNumber]
15 changes: 14 additions & 1 deletion cursorless-talon/src/modifiers/simple_scope_modifier.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from typing import Any

from talon import Module
from talon import Module, settings

mod = Module()

Expand All @@ -13,6 +13,13 @@
desc="Cursorless ancestor scope modifiers",
)

# This is a private setting and should not be used by non Cursorless developers
mod.setting(
"private_cursorless_use_preferred_scope",
bool,
desc="Use preferred scope instead of containing scope for all scopes by default (EXPERIMENTAL)",
)


@mod.capture(
rule=(
Expand All @@ -35,6 +42,12 @@ def cursorless_simple_scope_modifier(m) -> dict[str, Any]:
"ancestorIndex": 1,
}

if settings.get("user.private_cursorless_use_preferred_scope", False):
return {
"type": "preferredScope",
"scopeType": m.cursorless_scope_type,
}

return {
"type": "containingScope",
"scopeType": m.cursorless_scope_type,
Expand Down
16 changes: 12 additions & 4 deletions cursorless-talon/src/targets/primitive_target.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,15 @@
)
)
def cursorless_primitive_target(m) -> PrimitiveTarget:
return PrimitiveTarget(
getattr(m, "cursorless_mark", None),
getattr(m, "cursorless_modifier_list", None),
)
mark = getattr(m, "cursorless_mark", None)
modifiers = getattr(m, "cursorless_modifier_list", None)

# for grammar performance reasons, the literal modifier is exposed to Talon as a mark,
# but is converted to a modifier in the engine.
if mark is not None and mark["type"] == "literal":
if modifiers is None:
modifiers = []
modifiers.append(mark["modifier"])
mark = None

return PrimitiveTarget(mark, modifiers)
26 changes: 26 additions & 0 deletions data/fixtures/recorded/modifiers/preferredScope/changeGoodPair.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
languageId: plaintext
command:
version: 7
spokenForm: change good pair
action:
name: clearAndSetSelection
target:
type: primitive
modifiers:
- type: preferredScope
scopeType: {type: surroundingPair, delimiter: any}
usePrePhraseSnapshot: true
spokenFormError: Modifier 'preferredScope'
initialState:
documentContents: |
()
selections:
- anchor: {line: 1, character: 0}
active: {line: 1, character: 0}
marks: {}
finalState:
documentContents: |+
selections:
- anchor: {line: 0, character: 0}
active: {line: 0, character: 0}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
languageId: plaintext
command:
version: 7
spokenForm: change good pair
action:
name: clearAndSetSelection
target:
type: primitive
modifiers:
- type: preferredScope
scopeType: {type: surroundingPair, delimiter: any}
usePrePhraseSnapshot: true
spokenFormError: Modifier 'preferredScope'
initialState:
documentContents: |-
() ()
selections:
- anchor: {line: 1, character: 9}
active: {line: 1, character: 9}
marks: {}
finalState:
documentContents: |-
()
selections:
- anchor: {line: 0, character: 8}
active: {line: 0, character: 8}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
languageId: plaintext
command:
version: 7
spokenForm: change good pair
action:
name: clearAndSetSelection
target:
type: primitive
modifiers:
- type: preferredScope
scopeType: {type: surroundingPair, delimiter: any}
usePrePhraseSnapshot: true
spokenFormError: Modifier 'preferredScope'
initialState:
documentContents: |
() ()
selections:
- anchor: {line: 1, character: 0}
active: {line: 1, character: 0}
marks: {}
finalState:
documentContents: |2
()
selections:
- anchor: {line: 0, character: 0}
active: {line: 0, character: 0}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
languageId: plaintext
command:
version: 7
spokenForm: change good pair
action:
name: clearAndSetSelection
target:
type: primitive
modifiers:
- type: preferredScope
scopeType: {type: surroundingPair, delimiter: any}
usePrePhraseSnapshot: true
spokenFormError: Modifier 'preferredScope'
initialState:
documentContents: () ()
selections:
- anchor: {line: 0, character: 3}
active: {line: 0, character: 3}
marks: {}
finalState:
documentContents: "() "
selections:
- anchor: {line: 0, character: 4}
active: {line: 0, character: 4}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
languageId: plaintext
command:
version: 7
spokenForm: clear literal hello
action:
name: clearAndSetSelection
target:
type: primitive
modifiers:
- type: preferredScope
scopeType: {type: customRegex, regex: hello, flags: gui}
usePrePhraseSnapshot: true
spokenFormError: Modifier 'preferredScope'
initialState:
documentContents: |
hello
selections:
- anchor: {line: 1, character: 0}
active: {line: 1, character: 0}
marks: {}
finalState:
documentContents: |+
selections:
- anchor: {line: 0, character: 0}
active: {line: 0, character: 0}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
languageId: plaintext
command:
version: 7
spokenForm: clear literal hello
action:
name: clearAndSetSelection
target:
type: primitive
modifiers:
- type: preferredScope
scopeType: {type: customRegex, regex: hello, flags: gui}
usePrePhraseSnapshot: true
spokenFormError: Modifier 'preferredScope'
initialState:
documentContents: |-
hello
selections:
- anchor: {line: 0, character: 0}
active: {line: 0, character: 0}
marks: {}
finalState:
documentContents: |+
selections:
- anchor: {line: 1, character: 0}
active: {line: 1, character: 0}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
languageId: plaintext
command:
version: 7
spokenForm: clear literal hello there
action:
name: clearAndSetSelection
target:
type: primitive
modifiers:
- type: preferredScope
scopeType: {type: customRegex, regex: 'hello([^a-zA-Z]|\\[trn])*there', flags: gui}
usePrePhraseSnapshot: true
spokenFormError: Modifier 'preferredScope'
initialState:
documentContents: |
hello there
selections:
- anchor: {line: 1, character: 0}
active: {line: 1, character: 0}
marks: {}
finalState:
documentContents: |+
selections:
- anchor: {line: 0, character: 0}
active: {line: 0, character: 0}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
languageId: plaintext
command:
version: 7
spokenForm: clear literal hello there
action:
name: clearAndSetSelection
target:
type: primitive
modifiers:
- type: preferredScope
scopeType: {type: customRegex, regex: 'hello([^a-zA-Z]|\\[trn])*there', flags: gui}
usePrePhraseSnapshot: true
spokenFormError: Modifier 'preferredScope'
initialState:
documentContents: |
hello.there
selections:
- anchor: {line: 1, character: 0}
active: {line: 1, character: 0}
marks: {}
finalState:
documentContents: |+
selections:
- anchor: {line: 0, character: 0}
active: {line: 0, character: 0}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
languageId: plaintext
command:
version: 7
spokenForm: clear literal hello there
action:
name: clearAndSetSelection
target:
type: primitive
modifiers:
- type: preferredScope
scopeType: {type: customRegex, regex: 'hello([^a-zA-Z]|\\[trn])*there', flags: gui}
usePrePhraseSnapshot: true
spokenFormError: Modifier 'preferredScope'
initialState:
documentContents: |
hello\nthere
selections:
- anchor: {line: 1, character: 0}
active: {line: 1, character: 0}
marks: {}
finalState:
documentContents: |+
selections:
- anchor: {line: 0, character: 0}
active: {line: 0, character: 0}
Loading

0 comments on commit 21715e4

Please sign in to comment.