cursorless-dev / cursorless

Don't let the cursor slow you down
https://www.cursorless.org/
MIT License
1.12k stars 78 forks source link

Filter / forgiving adverbs #1015

Open pokey opened 1 year ago

pokey commented 1 year ago

Implementation

We can implement this with a new compositional modifier:

interface ErrorSuppressorModifier {
   type: "errorSuppressor";
   modifier: Modifier;
   onSuccess: "useOriginal" | "useModified";
   onError: "useOriginal" | "drop";
}

The modifier stage would just apply modifier, surrounded by a try block. On success it would either use the original or modified depending on onSuccess. On error it would either use original or return [] depending on onError

Then Talon-side we could map spoken forms as follows:

{
    "can": {"onSuccess": "useOriginal", "onError": "drop"},
    "only": {"onSuccess": "useModified", "onError": "drop"},
    "soft": {"onSuccess": "useModified", "onError": "useOriginal"},
}

Note that these spoken forms can't be used on their own; they are of the form {user.cursorlessErrorSuppressor} <user.cursorlessModifier>, and construct a composed modifier

See also #1006

Might be helpful to have a look at extension and talon side of head and tail:

Update

@AndreasArvidsson had the idea to have the modifier function more like an "error boundary". It wouldn't be an adverb that tries to run modifiers itself. Instead, it would just indicate error behaviour:

interface ErrorBoundaryModifier {
   type: "errorBoundary";
   onSuccess: "useOriginal" | "useModified";
   onError: "useOriginal" | "drop";
}

Then the pipeline, when a modifier throws an error, will scan onwards in the pipeline looking for an error boundary, and use that to determine what to do

josharian commented 1 year ago

Cross-linking: This came up as useful in combination with https://github.com/cursorless-dev/cursorless/issues/1631.

pokey commented 9 months ago

I do wonder if the error boundary approach is too fancy. Having modifiers which have long-range impacts on other modifiers feels slightly strange. Also, how does the data flow work? Does it flow out of the original modifier or the error boundary? And how would it interact with https://github.com/cursorless-dev/cursorless/issues/756? and https://github.com/cursorless-dev/cursorless/issues/2000?

The advantage of the original proposal is that the semantics are very clear, and no special sauce needs to appear in the pipeline. In addition, for keyboard, the pipeline is not used at all, so they wouldn't be able to use these error boundaries

cc/ @AndreasArvidsson @josharian

AndreasArvidsson commented 9 months ago

I was just thinking that if one stage of the pipeline throws an error we check if the next stage can handle that. Nothing fancy. How this interact with future features and specifically the keyboard I have no idea.

pokey commented 9 months ago

I thought you were planning to walk multiple stages, as you suggested it was an alternative to having to think bout whether we swallow multiple modifiers

AndreasArvidsson commented 9 months ago

That is a possibility, but not necessary.

pokey commented 9 months ago

I'm leaning back towards the original, more explicit approach, but maybe @josharian has some thoughts here. Fwiw jq does something more like the original approach, and the original approach feels more traditional: surrounding something explicitly in a try-catch block.

If the only benefit is simplifying Talon grammar, then I don't think it's worth it. But if there are other tangible benefits then I could see it