Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added glyph scope #2050

Merged
merged 22 commits into from
Dec 11, 2023
Merged
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
27 changes: 20 additions & 7 deletions cursorless-talon/src/cheatsheet/sections/scopes.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,25 @@
from ..get_list import get_lists
from ..get_list import get_lists, get_raw_list


def get_scopes():
return get_lists(
["scope_type"],
"scopeType",
complex_scopes = get_raw_list("glyph_scope_type")
return [
*get_lists(
["scope_type"],
"scopeType",
{
"argumentOrParameter": "Argument",
"boundedNonWhitespaceSequence": "Non whitespace sequence stopped by surrounding pair delimeters",
},
),
{
"argumentOrParameter": "Argument",
"boundedNonWhitespaceSequence": "Non whitespace sequence stopped by surrounding pair delimeters",
"id": "glyph",
"type": "scopeType",
"variations": [
{
"spokenForm": f"{complex_scopes['glyph']} <character>",
"description": "Instance of single character <character>",
},
],
},
)
]
30 changes: 30 additions & 0 deletions cursorless-talon/src/modifiers/glyph_scope.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from talon import Module

mod = Module()

mod.list(
"cursorless_glyph_scope_type",
desc="Cursorless glyph scope type",
)
mod.list(
"cursorless_glyph_scope_type_plural",
desc="Plural version of Cursorless glyph scope type",
)


@mod.capture(rule="{user.cursorless_glyph_scope_type} <user.any_alphanumeric_key>")
def cursorless_glyph_scope_type(m) -> dict[str, str]:
return {
"type": "glyph",
"character": m.any_alphanumeric_key,
}


@mod.capture(
rule="{user.cursorless_glyph_scope_type_plural} <user.any_alphanumeric_key>"
)
def cursorless_glyph_scope_type_plural(m) -> dict[str, str]:
return {
"type": "glyph",
"character": m.any_alphanumeric_key,
}
28 changes: 21 additions & 7 deletions cursorless-talon/src/modifiers/scopes.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,25 +15,39 @@


@mod.capture(
rule="{user.cursorless_scope_type} | {user.cursorless_custom_regex_scope_type}"
rule="{user.cursorless_scope_type} | <user.cursorless_glyph_scope_type> | {user.cursorless_custom_regex_scope_type}"
)
def cursorless_scope_type(m) -> dict[str, str]:
"""Cursorless scope type singular"""
try:
return {"type": m.cursorless_scope_type}
except AttributeError:
return {"type": "customRegex", "regex": m.cursorless_custom_regex_scope_type}
pass

try:
return m.cursorless_glyph_scope_type
except AttributeError:
pass

return {"type": "customRegex", "regex": m.cursorless_custom_regex_scope_type}


@mod.capture(
rule="{user.cursorless_scope_type_plural} | {user.cursorless_custom_regex_scope_type_plural}"
rule="{user.cursorless_scope_type_plural} | <user.cursorless_glyph_scope_type_plural> | {user.cursorless_custom_regex_scope_type_plural}"
)
def cursorless_scope_type_plural(m) -> dict[str, str]:
"""Cursorless scope type plural"""
try:
return {"type": m.cursorless_scope_type_plural}
except AttributeError:
return {
"type": "customRegex",
"regex": m.cursorless_custom_regex_scope_type_plural,
}
pass

try:
return m.cursorless_glyph_scope_type_plural
except AttributeError:
pass

return {
"type": "customRegex",
"regex": m.cursorless_custom_regex_scope_type_plural,
}
3 changes: 3 additions & 0 deletions cursorless-talon/src/spoken_forms.json
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,9 @@
},
"surrounding_pair_scope_type": {
"string": "string"
},
"glyph_scope_type": {
"glyph": "glyph"
}
},
"paired_delimiters.csv": {
Expand Down
3 changes: 2 additions & 1 deletion cursorless-talon/src/spoken_forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ def ret(filename: str, *args: P.args, **kwargs: P.kwargs) -> R:
"wrapper_only_paired_delimiter": "pairedDelimiter",
"surrounding_pair_scope_type": "pairedDelimiter",
"scope_type": "simpleScopeTypeType",
"glyph_scope_type": "complexScopeTypeType",
"custom_regex_scope_type": "customRegex",
}

Expand Down Expand Up @@ -125,7 +126,7 @@ def handle_new_values(csv_name: str, values: list[SpokenFormEntry]):
handle_csv("experimental/miscellaneous.csv"),
handle_csv(
"modifier_scope_types.csv",
pluralize_lists=["scope_type"],
pluralize_lists=["scope_type", "glyph_scope_type"],
extra_allowed_values=[
"private.fieldAccess",
"private.switchStatementSubject",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,16 @@
}
]
},
{
"id": "breakLine",
"type": "action",
"variations": [
{
"spokenForm": "break <target>",
"description": "Break line"
}
]
},
{
"id": "callAsFunction",
"type": "action",
Expand Down Expand Up @@ -228,6 +238,16 @@
}
]
},
{
"id": "joinLines",
"type": "action",
"variations": [
{
"spokenForm": "join <target>",
"description": "Join lines"
}
]
},
{
"id": "moveToTarget",
"type": "action",
Expand Down Expand Up @@ -905,6 +925,16 @@
"description": "Trailing delimiter range"
}
]
},
{
"id": "visible",
"type": "modifier",
"variations": [
{
"spokenForm": "visible",
"description": "Visible"
}
]
}
]
},
Expand Down Expand Up @@ -1058,6 +1088,16 @@
}
]
},
{
"id": "show_scope_sidebar",
"type": "command",
"variations": [
{
"spokenForm": "bar cursorless",
"description": "Show cursorless sidebar"
}
]
},
{
"id": "show_scope_visualizer",
"type": "command",
Expand Down Expand Up @@ -1272,6 +1312,16 @@
}
]
},
{
"id": "glyph",
"type": "scopeType",
"variations": [
{
"spokenForm": "glyph <character>",
"description": "Instance of single character <character>"
}
]
},
{
"id": "identifier",
"type": "scopeType",
Expand Down Expand Up @@ -1537,7 +1587,7 @@
"type": "scopeType",
"variations": [
{
"spokenForm": "word",
"spokenForm": "sub",
"description": "Word"
}
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -205,11 +205,17 @@ export interface OneOfScopeType {
scopeTypes: ScopeType[];
}

export interface GlyphScopeType {
type: "glyph";
character: string;
}

export type ScopeType =
| SimpleScopeType
| SurroundingPairScopeType
| CustomRegexScopeType
| OneOfScopeType;
| OneOfScopeType
| GlyphScopeType;

export interface ContainingSurroundingPairModifier
extends ContainingScopeModifier {
Expand Down
25 changes: 4 additions & 21 deletions packages/cursorless-engine/src/core/Cheatsheet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import produce from "immer";
import { sortBy } from "lodash";
import { ide } from "../singletons/ide.singleton";
import path from "path";
import { getCursorlessRepoRoot } from "@cursorless/common";

/**
* The argument expected by the cheatsheet command.
Expand Down Expand Up @@ -56,27 +57,9 @@ export async function showCheatsheet({
* @param spokenFormInfo The new value to use for default spoken forms.
*/
export async function updateDefaults(spokenFormInfo: CheatsheetInfo) {
const { runMode, assetsRoot, workspaceFolders } = ide();

const workspacePath =
runMode === "development"
? assetsRoot
: workspaceFolders?.[0].uri.path ?? null;

if (workspacePath == null) {
throw new Error(
"Please update defaults from Cursorless workspace or running in debug",
);
}

const defaultsPath = path.join(
workspacePath,
"packages",
"cheatsheet",
"src",
"lib",
"sampleSpokenFormInfos",
"defaults.json",
getCursorlessRepoRoot(),
"packages/cheatsheet/src/lib/sampleSpokenFormInfos/defaults.json",
);

const outputObject = produce(spokenFormInfo, (draft) => {
Expand All @@ -86,7 +69,7 @@ export async function updateDefaults(spokenFormInfo: CheatsheetInfo) {
});
});

await writeFile(defaultsPath, JSON.stringify(outputObject, null, "\t"));
await writeFile(defaultsPath, JSON.stringify(outputObject, null, 2) + "\n");
}

// FIXME: Stop duplicating these types once we have #945
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import assert from "node:assert";
import { CustomSpokenFormGeneratorImpl } from "./CustomSpokenFormGeneratorImpl";
import { asyncSafety } from "@cursorless/common";

suite("CustomSpokenFormGeneratorImpl", async function () {
test(
"glyph",
asyncSafety(async () => {
const generator = new CustomSpokenFormGeneratorImpl({
async getSpokenFormEntries() {
return [
{
type: "complexScopeTypeType",
id: "glyph",
spokenForms: ["foo"],
},
];
},
onDidChange: () => ({ dispose() {} }),
});

await generator.customSpokenFormsInitialized;

const spokenForm = generator.scopeTypeToSpokenForm({
type: "glyph",
character: "a",
});

assert.deepStrictEqual(spokenForm, {
type: "success",
spokenForms: ["foo air"],
});
}),
);
});
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,15 @@ export class CustomSpokenFormGeneratorImpl
private spokenFormGenerator: SpokenFormGenerator;
private disposable: Disposable;

/**
* A promise that resolves when the custom spoken forms have been loaded.
*/
public readonly customSpokenFormsInitialized: Promise<void>;

constructor(talonSpokenForms: TalonSpokenForms) {
this.customSpokenForms = new CustomSpokenForms(talonSpokenForms);
this.customSpokenFormsInitialized =
this.customSpokenForms.customSpokenFormsInitialized;
this.spokenFormGenerator = new SpokenFormGenerator(
this.customSpokenForms.spokenFormMap,
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,11 @@ export class PrimitiveTargetSpokenFormGenerator {
switch (scopeType.type) {
case "oneOf":
throw new NoSpokenFormError(`Scope type '${scopeType.type}'`);
case "glyph":
return [
this.spokenFormMap.complexScopeTypeType.glyph,
characterToSpokenForm(scopeType.character),
];
case "surroundingPair": {
const pair = this.spokenFormMap.pairedDelimiter[scopeType.delimiter];
if (scopeType.forceDirection != null) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { CustomRegexScopeType, Direction, ScopeType } from "@cursorless/common";
import {
CustomRegexScopeType,
Direction,
GlyphScopeType,
ScopeType,
} from "@cursorless/common";
import { imap } from "itertools";
import { escapeRegExp } from "lodash";
import { NestedScopeHandler } from "./NestedScopeHandler";
import { ScopeHandlerFactory } from "./ScopeHandlerFactory";
import { generateMatchesInRange } from "../../../util/getMatchesInRange";
Expand Down Expand Up @@ -62,3 +68,17 @@ export class CustomRegexScopeHandler extends RegexStageBase {
super(scopeHandlerFactory, scopeType, languageId);
}
}

export class GlyphScopeHandler extends RegexStageBase {
get regex() {
return new RegExp(escapeRegExp(this.scopeType.character), "gui");
}

constructor(
scopeHandlerFactory: ScopeHandlerFactory,
readonly scopeType: GlyphScopeType,
languageId: string,
) {
super(scopeHandlerFactory, scopeType, languageId);
}
}
Loading