-
-
Notifications
You must be signed in to change notification settings - Fork 85
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
33 changed files
with
1,694 additions
and
0 deletions.
There are no files selected for viewing
126 changes: 126 additions & 0 deletions
126
cursorless-talon/src/cursorless_command_to_spoken_form.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
from typing import Callable | ||
|
||
from talon import registry | ||
|
||
from .actions.actions import ACTION_LIST_NAMES | ||
from .conventions import get_cursorless_list_name | ||
from .modifiers.containing_scope import SCOPE_LIST_NAMES | ||
|
||
|
||
def make_cursorless_list_reverse_look_up(*raw_list_names: str): | ||
return make_list_reverse_look_up( | ||
*[get_cursorless_list_name(raw_list_name) for raw_list_name in raw_list_names] | ||
) | ||
|
||
|
||
def make_list_reverse_look_up(*list_names: str): | ||
""" | ||
Given a list of talon list names, returns a function that does a reverse | ||
look-up in all lists to find the spoken form for its input. | ||
""" | ||
|
||
def return_func(argument: str): | ||
for list_name in list_names: | ||
for spoken_form, value in registry.lists[list_name][-1].items(): | ||
if value == argument: | ||
return list_name, spoken_form | ||
|
||
raise LookupError(f"Unknown identifier `{argument}`") | ||
|
||
return return_func | ||
|
||
|
||
lookup_action = make_cursorless_list_reverse_look_up(*ACTION_LIST_NAMES) | ||
lookup_scope_type = make_cursorless_list_reverse_look_up(*SCOPE_LIST_NAMES) | ||
|
||
|
||
def cursorless_command_to_spoken_form(command: dict): | ||
action_list_name, action_spoken_form = lookup_action(command["action"]) | ||
targets_spoken_form = targets_processor_map[action_list_name](command["targets"]) | ||
return f"{action_spoken_form} {targets_spoken_form}" | ||
|
||
|
||
def process_simple_action_targets(targets: list[dict]): | ||
return process_target(targets[0]) | ||
|
||
|
||
raw_targets_processor_map: dict[str, Callable[[list[dict]], str]] = { | ||
"simple_action": process_simple_action_targets, | ||
"positional_action": process_simple_action_targets, | ||
"callback_action": process_simple_action_targets, | ||
"makeshift_action": process_simple_action_targets, | ||
"custom_action": process_simple_action_targets, | ||
"swap_action": {"swap": "swapTargets"}, | ||
"move_bring_action": {"bring": "replaceWithTarget", "move": "moveToTarget"}, | ||
"wrap_action": {"wrap": "wrapWithPairedDelimiter", "repack": "rewrap"}, | ||
"insert_snippet_action": {"snippet": "insertSnippet"}, | ||
"reformat_action": {"format": "applyFormatter"}, | ||
} | ||
|
||
targets_processor_map = { | ||
get_cursorless_list_name(key): value | ||
for key, value in raw_targets_processor_map.items() | ||
} | ||
|
||
|
||
def process_target(target: dict): | ||
if target["type"] == "primitive": | ||
return process_primitive_target(target) | ||
elif target["type"] == "range": | ||
return process_range_target(target) | ||
elif target["type"] == "list": | ||
return process_list_target(target) | ||
else: | ||
raise Exception(f"Unknown target type {target['type']}") | ||
|
||
|
||
class MarkProcessor: | ||
field_name = "mark" | ||
|
||
def __init__(self): | ||
self.process_character = make_list_reverse_look_up( | ||
"user.letter", | ||
"user.number_key", | ||
"user.symbol_key", | ||
) | ||
|
||
def process_value(self, field_value: dict): | ||
mark_type = field_value["type"] | ||
if mark_type == "decoratedSymbol": | ||
return self.process_decorated_symbol(field_value) | ||
elif mark_type == "that": | ||
return self.process_that_mark(field_value) | ||
elif mark_type == "source": | ||
return self.process_source_mark(field_value) | ||
elif mark_type == "cursor": | ||
return self.process_cursor_mark(field_value) | ||
|
||
def process_decorated_symbol(self, field_value: dict): | ||
# TODO: Handle `symbolColor` | ||
return self.process_character(field_value["character"])[1] | ||
|
||
def process_that_mark(self, field_value: dict): | ||
# TODO: Handle this case properly using users custom term | ||
return "that" | ||
|
||
def process_source_mark(self, field_value: dict): | ||
# TODO: Handle this case properly using users custom term | ||
return "source" | ||
|
||
def process_cursor_mark(self, field_value: dict): | ||
# TODO: Handle this case properly using users custom term | ||
return "this" | ||
|
||
|
||
field_processors = [MarkProcessor()] | ||
|
||
|
||
def process_primitive_target(target: dict): | ||
field_spoken_forms = [ | ||
field_processor.process_value(target.get(field_processor.field_name, None)) | ||
for field_processor in field_processors | ||
] | ||
|
||
return " ".join( | ||
[spoken_form for spoken_form in field_spoken_forms if spoken_form is not None] | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
import json | ||
import re | ||
from pathlib import Path | ||
from typing import Callable | ||
|
||
import yaml | ||
from talon import actions, app | ||
|
||
from .cursorless_command_to_spoken_form import ( | ||
cursorless_command_to_spoken_form, | ||
lookup_action, | ||
lookup_scope_type, | ||
) | ||
|
||
regex = re.compile(r"\{(\w+):([^}]+)\}") | ||
tutorial_dir = Path( | ||
"/Users/pokey/src/cursorless-vscode/src/test/suite/fixtures/recorded/tutorial/unit-2-basic-coding" | ||
) | ||
|
||
|
||
def process_literal_step(argument: str): | ||
return f"<cmd@{argument}/>" | ||
|
||
|
||
def process_action(argument: str): | ||
_, spoken_form = lookup_action(argument) | ||
return f'<*"{spoken_form}"/>' | ||
|
||
|
||
def process_scope_type(argument: str): | ||
_, spoken_form = lookup_scope_type(argument) | ||
return f'<*"{spoken_form}"/>' | ||
|
||
|
||
def process_cursorless_command_step(argument: str): | ||
step_fixture = yaml.safe_load((tutorial_dir / argument).read_text()) | ||
return f"<cmd@{cursorless_command_to_spoken_form(step_fixture['command'])}/>" | ||
|
||
|
||
interpolation_processor_map: dict[str, Callable[[str], str]] = { | ||
"literalStep": process_literal_step, | ||
"action": process_action, | ||
"scopeType": process_scope_type, | ||
"step": process_cursorless_command_step, | ||
} | ||
|
||
|
||
def process_tutorial_step(raw: str): | ||
print(f"{raw=}") | ||
current_index = 0 | ||
content = "" | ||
for match in regex.finditer(raw): | ||
content += raw[current_index : match.start()] | ||
content += interpolation_processor_map[match.group(1)](match.group(2)) | ||
current_index = match.end() | ||
content += raw[current_index : len(raw)] | ||
print(f"{content=}") | ||
|
||
return { | ||
"content": content, | ||
"restore_callback": print, | ||
"modes": ["command"], | ||
"app": "Code", | ||
"context_hint": "Please open VSCode and enter command mode", | ||
} | ||
|
||
|
||
def get_basic_coding_walkthrough(): | ||
with open(tutorial_dir / "script.json") as f: | ||
script = json.load(f) | ||
|
||
return [ | ||
actions.user.hud_create_walkthrough_step(**process_tutorial_step(step)) | ||
for step in script | ||
] | ||
|
||
|
||
def on_ready(): | ||
actions.user.hud_add_lazy_walkthrough( | ||
"Cursorless basic coding", get_basic_coding_walkthrough | ||
) | ||
|
||
|
||
app.register("ready", on_ready) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
from talon import Context, Module | ||
|
||
mod = Module() | ||
ctx = Context() | ||
|
||
mod.list("cursorless_walkthrough_list", desc="My tutorial list") | ||
ctx.list["user.cursorless_walkthrough_list"] = { | ||
"spoken form": "whatever", | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
================================================== | ||
========== ========== | ||
========== Welcome to Cursorless! ========== | ||
========== ========== | ||
========== Let's start using marks ========== | ||
========== ========== | ||
========== so we can navigate around ========== | ||
========== ========== | ||
========== without lifting a finger! ========== | ||
========== ========== | ||
================================================== |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
def print_color(color, invert=False): | ||
if invert: | ||
print(invert_color(color)) | ||
else: | ||
print(color) | ||
|
||
|
||
def invert_color(color): | ||
if color == "black": | ||
return "white" | ||
|
||
|
||
print_color("black") |
55 changes: 55 additions & 0 deletions
55
src/test/suite/fixtures/recorded/tutorial/extra-cloning-a-talon-list/bringBlockMade.yml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
languageId: python | ||
command: | ||
version: 1 | ||
spokenForm: bring block made | ||
action: replaceWithTarget | ||
targets: | ||
- type: primitive | ||
selectionType: paragraph | ||
mark: {type: decoratedSymbol, symbolColor: default, character: m} | ||
- {type: primitive, isImplicit: true} | ||
initialState: | ||
documentContents: |+ | ||
from talon import Context, Module | ||
mod = Module() | ||
ctx = Context() | ||
mod.list("cursorless_walkthrough_list", desc="My tutorial list") | ||
ctx.list['user.cursorless_walkthrough_list'] = { | ||
"spoken form": "whatever", | ||
} | ||
selections: | ||
- anchor: {line: 10, character: 0} | ||
active: {line: 10, character: 0} | ||
marks: | ||
default.m: | ||
start: {line: 5, character: 0} | ||
end: {line: 5, character: 3} | ||
finalState: | ||
documentContents: |- | ||
from talon import Context, Module | ||
mod = Module() | ||
ctx = Context() | ||
mod.list("cursorless_walkthrough_list", desc="My tutorial list") | ||
ctx.list['user.cursorless_walkthrough_list'] = { | ||
"spoken form": "whatever", | ||
} | ||
mod.list("cursorless_walkthrough_list", desc="My tutorial list") | ||
ctx.list['user.cursorless_walkthrough_list'] = { | ||
"spoken form": "whatever", | ||
} | ||
selections: | ||
- anchor: {line: 13, character: 1} | ||
active: {line: 13, character: 1} | ||
thatMark: | ||
- anchor: {line: 10, character: 0} | ||
active: {line: 13, character: 1} | ||
sourceMark: | ||
- anchor: {line: 5, character: 0} | ||
active: {line: 8, character: 1} | ||
fullTargets: [{type: primitive, mark: {type: decoratedSymbol, symbolColor: default, character: m}, selectionType: paragraph, position: contents, insideOutsideType: null, modifier: {type: identity}}, {type: primitive, mark: {type: cursor}, selectionType: paragraph, position: contents, insideOutsideType: null, modifier: {type: identity}}] |
55 changes: 55 additions & 0 deletions
55
src/test/suite/fixtures/recorded/tutorial/extra-cloning-a-talon-list/clearCoreSun.yml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
languageId: python | ||
command: | ||
version: 1 | ||
spokenForm: clear core sun | ||
action: clearAndSetSelection | ||
targets: | ||
- type: primitive | ||
modifier: {type: surroundingPair, delimiter: any, delimiterInclusion: interiorOnly} | ||
mark: {type: decoratedSymbol, symbolColor: default, character: s} | ||
initialState: | ||
documentContents: |- | ||
from talon import Context, Module | ||
mod = Module() | ||
ctx = Context() | ||
mod.list("cursorless_walkthrough_list", desc="My tutorial list") | ||
ctx.list['user.cursorless_walkthrough_list'] = { | ||
"spoken form": "whatever", | ||
} | ||
mod.list("emoji", desc="Emojis") | ||
ctx.list['user.emoji'] = { | ||
"spoken form": "whatever", | ||
} | ||
selections: | ||
- anchor: {line: 10, character: 30} | ||
active: {line: 10, character: 30} | ||
marks: | ||
default.s: | ||
start: {line: 12, character: 5} | ||
end: {line: 12, character: 11} | ||
finalState: | ||
documentContents: |- | ||
from talon import Context, Module | ||
mod = Module() | ||
ctx = Context() | ||
mod.list("cursorless_walkthrough_list", desc="My tutorial list") | ||
ctx.list['user.cursorless_walkthrough_list'] = { | ||
"spoken form": "whatever", | ||
} | ||
mod.list("emoji", desc="Emojis") | ||
ctx.list['user.emoji'] = { | ||
"": "whatever", | ||
} | ||
selections: | ||
- anchor: {line: 12, character: 5} | ||
active: {line: 12, character: 5} | ||
thatMark: | ||
- anchor: {line: 12, character: 5} | ||
active: {line: 12, character: 5} | ||
fullTargets: [{type: primitive, mark: {type: decoratedSymbol, symbolColor: default, character: s}, selectionType: token, position: contents, insideOutsideType: inside, modifier: {type: surroundingPair, delimiter: any, delimiterInclusion: interiorOnly}}] |
Oops, something went wrong.