bugGNU nano - Bugs: bug #61692, [Wish] allow binding a key to a...

 
 

You are not allowed to post comments on this tracker with your current authentication level.

bug #61692: [Wish] allow binding a key to a sequence of functions and strings

Submitter:  Tasos Papastylianou <tpapastylianou>
Submitted:  Fri 17 Dec 2021 03:40:09 PM UTC
   
 
Severity:  1 - Wish Status:  Fixed
Assigned to:  bens Open/Closed:  Closed

Jump to the original submission

Wed 16 Nov 2022 08:12:03 AM UTC, comment #19: 

Released in nano-7.0.

Benno Schulenberg <bens>
Group administrator
Thu 18 Aug 2022 01:27:31 PM UTC, comment #18: 

I don't really want to encourage including control codes now that {function} things are possible.  Also, I would prefer to keep it a dry manual instead of making it more like a tutorial.  But... I'll think about it.

Anyway, if you have any other comments, please open a new issue -- the basic wish of this issue has been handled and fulfilled.

Benno Schulenberg <bens>
Group administrator
Thu 18 Aug 2022 10:03:57 AM UTC, comment #17: 

I would still mention the fact that control codes can be inserted verbatim into the string, since this is not mentioned anywhere else in the nanorc (or nano) man pages.

I might also mention that function and "{function}" are functionally equivalent, to clarify the syntax.

How about:


REBINDING KEYS
       Key bindings can be changed via the following three commands:

          bind key "string" menu
                 Makes the given key produce the given  string  in  the  given
                 menu (or in all menus where the key exists when all is used).

                 The string can consist of literal text, verbatim control codes,
                 function names between braces, or a mix of all three. To
                 include a literal opening brace, use {{}. To include control
                 codes 'verbatim', type *M-V* and then the desired keystroke.


          bind key function menu
                 Rebinds the given key to the given function in the given menu
                 (or in all menus where the function exists when all is used).

                 Note: A function macro is functionally equivalent to a string
                 macro containing a single function, except for one detail: If
                 a function does not exist in a particular menu, nano will
                 produce a helpful error message on stdout upon loading the
                 nanorc file, and then disregard the binding for that menu.
                 whereas in the case of a string macro, if a function does not
                 exist in the relevant menu, it will behave as if it was
                 triggered directly from the main menu instead.


Tasos Papastylianou <tpapastylianou>
Thu 18 Aug 2022 09:11:44 AM UTC, comment #16: 

Would the following change be good enough?

-Besides literal text,
+Besides literal text and/or control codes,


Benno Schulenberg <bens>
Group administrator
Thu 18 Aug 2022 08:47:14 AM UTC, comment #15: 

The syntax coloring and replacing existing macros with the new functional form was a nice touch :)

Small comment about this commit: https://git.savannah.gnu.org/cgit/nano.git/commit/?id=8b4bf2bb8ddec5929f66740b345c0cfd9c4a4235

While the added text is good, I wouldn't have removed this part


-The string can consist of text or commands or a mix of them.
-(To enter a command into the string, precede its keystroke
-with M-V.)


entirely.

Like I said below, there are some things that can be done with control sequences that still can't be done with functions. It would be good for the fact that control sequences are allowed in such strings to still be documented there. (perhaps it would be clearer if 'commands' was replaced with 'control-sequences' however, and text with 'literal text').

Tasos Papastylianou <tpapastylianou>
Thu 18 Aug 2022 08:26:10 AM UTC, comment #14: 

Thanks Benno!

I can confirm that the escaping works, and more importantly that it also works with the Alt-{ keystroke. :)

Tasos Papastylianou <tpapastylianou>
Thu 18 Aug 2022 07:47:19 AM UTC, comment #13: 

Anyway, fixed in git, commit 958ec294, plus some subsequent commits for syntax coloring and documentation.

Benno Schulenberg <bens>
Group administrator
Thu 18 Aug 2022 07:39:44 AM UTC, comment #12: 

Hi Tasos -- Oh, I've missed your email.  Savannah has been behaving strangely for some days, so maybe it failed to send the email?

As to escaping { and }, only { needs to be escaped, and it is escaped as {{}, as the commit message now says.

Yes, functions between braces will be executed anywhere, the menu of origin is irrelevant.  This was not the intended behavior, but it is hard to prevent -- many menus do not list all the keystrokes (functions) that are valid in them, so if nano were to check for presence of the function in the current menu before executing it, it would prevent the execution of many valid functions, which is not good.  So, when composing a string bind with {function} things, the user should take care that things make sense, nano does no such checks.

Benno Schulenberg <bens>
Group administrator
Tue 16 Aug 2022 12:35:39 AM UTC, comment #11: 

Hi Benno, thanks for putting so much thought into this!

A couple of comments/issues. The second is the more important one, so getting the first out of the way: I couldn't spot if you had a way to escape the '{' and '}' in a macro. The obvious '\{' and '{{' didn't work.

Now, to the important bit. By way of introduction, let me say that I've have been experimenting quite a bit with macros lately, exploring clever ways to get nano to do some very nice things for me. I'm hoping to make some of them public soon in a nice reproducible format for your enjoyment :)   E.g., there are some clever ways to introduce effective 'conditional' macros by exploiting the fact that 'yes/no' menus sometimes appear and sometimes do not, based on whether a replacement exists or not. You can exploit this fact to type lots of keystrokes that are effectively 'dead' in a yesno menu, but valid keystrokes in other menus, to create a 'conditional' macro that does different things when different menus are triggered. E.g. I have used this successfully to detect/save/restore regex/multibuffer states, enabling me to create macros that work identically regardless of whether regex or multibuffer is on or off.

Cleverness of my macros aside, however, one thing that became clear is that, with a string macro, you're not really limited to staying within the original menu you assign the macro to. E.g. you could assign a macro to the 'search' menu, that cancels the search, and starts a process that involves a completely different menu. If you utilise a 'conditional' macro like I mention above, occasionally the same key sequence will achieve two different things, depending on the menus invoked in the process. As a simple example, within a yes/no menu, ^C cancels, whereas within a main menu, it updates the location on the prompt (assuming default bindings for cancel:yesno and location:main).

So, when you do this with a normal string macro, the same keystrokes mean different things in the different menus. If you were to do the same with function names, would you simply expect that the macro keeps track of the 'menu' you're in and check for function validity accordingly at the point of calling the function? Or should it expect everything to take place in the menu of origin, and only check function name validity there?

I have checked your patch. I haven't managed to follow all of it at a glance, but from testing it out, I noticed that creating a macro like `bind M-" "Hello{writeout}world!" search` causes nano to escape the search menu completely, leaving the task unfinished, and trigger 'writeout' as if it was issued in the 'main' menu. This is unlikely to be the desired effect, even if an interesting side-effect.

Not sure where I'm going with this. It's almost as if I'm convincing myself against function calls in string macros, since keystrokes are actually more versatile (especially in conditional situations), and the ability of macros to traverse menus is not trivial, so I wouldn't want to assume that one can only use functions that are only valid in the menu of origin. At the same time, nobody is forcing one to use functions instead of keystrokes, and all the points at the top of this discussion about nicer syntax and not wasting keybindings just to achieve intermediate steps etc still apply, so ... I really don't know. :)

Tasos Papastylianou <tpapastylianou>
Fri 12 Aug 2022 12:50:51 PM UTC, comment #10: 

Monday I started coding for the format:

action;"text";action;"text"

But in the afternoon I had already fifty lines of code and was still just parsing the rcfile, and wasn't even halfway...  The advantages of the single-string format then became quite clear -- the advantages of this format:

"{action}text{action}text"

It requires zero extra code for parsing the rcfile, while the parsing during the execution phase is dead-simple.  So I wrote down in words how this execution part should work.  The crucial idea was: to not want to interpret the whole expansion string in one go and store the entire result in the keyboard buffer (as I thought to do in a previous comment), but to interpret one fragment at a time, either a command cartouche or a piece of literal text outside of those cartouches, and after each fragment let the keyboard routine come back for more.  Quite simple, really.  On Tuesday I started to code this up (starting from the "outside", with the variables and the special keycodes and the two hooks into the input routines), and at the end of the afternoon I had it working.  It worked on the first run, too.  :)  Wednesday morning I fixed one bug, and after that just applied some polishing.  So here it is.

(file #53565)

Benno Schulenberg <bens>
Group administrator
Tue 18 Jan 2022 03:05:18 PM UTC, comment #9: 

One argument against introducing the escaping of a double-quote (in whatever way) is that it will break any existing string bind that contains a double-quote.  But I don't see how each and every breakage can be avoided: even if the whole sequence of function names and strings has to be inside double-quotes, we would need to be able to tell which things are meant as function names, so some character or character sequence needs to be special, which will break some string binds -- maybe exceedingly few, but still.  So... I prefer the more elegant way and have it be obvious what are function names and what are literal strings.

Benno Schulenberg <bens>
Group administrator
Mon 17 Jan 2022 04:10:01 PM UTC, comment #8: 

Hi Benno. Yes, you're right. Sorry, I see what you were saying now.

I thought it might cause problems with things like """ in python, or quotes in comments etc, but obviously it doesn't because no such parsing is needed, as long as the quotes are 'escaped' appropriately; and as long as the number of quotes per token before processing the escapes is an odd number, the string is considered 'still open' and the semicolon is treated as part of the string. You're right.

Agreed, this is more elegant. :)

Tasos Papastylianou <tpapastylianou>
Wed 12 Jan 2022 03:19:14 PM UTC, comment #7: 

There is no necessity for a double-character delimiter.  I see no problem with parsing something like:

bind ^K "echo ""hello"";";enter main

Here a doubled double-quote means one escaped double-quote, and a semicolon within a string is not special.  Of course, this requires a new string parsing routine for the key binding code, but I don't see a problem with that, as the code has to change anyway, to allow for the concatenating of several function names and strings.

Benno Schulenberg <bens>
Group administrator
Sat 08 Jan 2022 05:35:49 PM UTC, comment #6: 

Hi Benno, sorry for the delay, I did not spot your response on my email.

The surrounding spaces aren't necessary, obviously. I just generally prefer spacing over cramping in my own code, but obviously that's only a personal preference and I'd be equally happy with a syntax that does not enforce surrounding spaces.

The main thing I'm pointing out here isn't the spacing, but the necessity for a "double-character" delimiter over a single one. The main advantage of this is that you can then trivially disambiguate between two symbols intended as a delimiter vs two symbols intended as actual keystrokes, since the latter can be trivially split into a "symbol" + delimiter + "symbol" sequence, to obtain the equivalent of "symbolsymbol" without sacrificing tokenization or "end quote closes whole quote" semantics within tokens.

I.e. if the delimiter is ; then you can have something like


bind ^C "case NAME in";;enter;;tab;;"1) echo "hello" ;";;";";;enter;;"esac";;enter main


This would result in


case NAME in
  1) echo "hello" ;;
esac


printed on screen. I.e. it allows us to print ";;" by 'joining' two separate strings as separate commands, and it also allows preserves the whole "end quote closes whole quote" behaviour within ;; delimited tokens. (note how this is used in the example above to do the 'echo "hello"' bit).

Tasos Papastylianou <tpapastylianou>
Wed 05 Jan 2022 09:58:09 AM UTC, comment #5: 

Using a double colon and two spaces to separate function names is horribly verbose.  Remember what I liked about your first suggested syntax?  That is was lean and clean.  Also, the semicolon is a well-known command separator, and using no whitespace glues the function names nicely together, showing that they are single unit.  (Of course there could still be spaces inside strings, but that is the nature of strings: they end only at an unescaped closing quote.  And of course a semicolon inside a string is not special.)

Yes, currently string binds that contain commands are cumbersome to type.  And that is exactly what this feature request should be trying to get away from: make the long binds readable, typable, pastable.

Benno Schulenberg <bens>
Group administrator
Sat 01 Jan 2022 08:40:33 PM UTC, comment #4: 


> So, if binding to a sequence of things is going to happen, it will have to be a syntax without control codes: human-readable, copy-pastable, and easy to type.  It will require some changes to the bind-parsing code of rcfile.c (to stop also at a semicolon and at an unescaped double quote), but that looks feasible.

If you still think this is a good idea, perhaps there is a better delimiter that avoids potential bugs with plain semicolons, and can still respect the 'final double quote closes the quote' after tokenization. Perhaps ' :: ' (i.e. two colons with spaces on either side)? E.g.:


bind function1 :: "String with a single double quote at the end"" :: function2 :: "Another " string" main


This way, in the worst case scenario that one really needs the character sequence ' :: ' in a string, they could still work around it by splitting it in two,  e.g.


bind prevword :: "Classname" :: " :" :: ": " :: nextword main   # prepend c++ classname to method name



> Creating an 'evaluate' function would avoid having to make changes to the parsing routines in rcfile.c, true, but the resulting string-bind commands would still suffer from most of your original objections against the current state of things.

I don't necessarily disagree, but I would like to 'defend' the points nonetheless for the sake of the completeness:


> They are ugly / visually obfuscated.

Perhaps, yes. But, I think it would still be sufficiently friendlier than using only prebound keystrokes. Compare:


(clean)     : firstline :: mark :: lastline :: writeout :: "draft" :: enter :: mark
(obfuscated): "^[\^^^[/^Odraft^M^^"
(okayish?)  : "^[!firstline^M^[!mark^M^[!lastline^M^[!writeout^Mdraft^[!enter^M^[!mark^M"



> They are a nuisance to type: M-V M-! functionname M-V <Enter> ...

Even without the involvement of functionnames, string macros containing control codes are already a bit cumbersome to type anyway, but their usefulness more than makes up for it. While I agree that the 'clean' syntax above would be preferable, I don't necessarily think the 'okayish' syntax would be more of a 'nuisance' than current string macros, where things need to be bound to non-clashing keys first before use.


> They cannot be copy-pasted from a web page.

Perhaps not, but they would still be shareable as files. The recent uppercasing/lowercasing macros you included in the sample.nanorc are a good example of this.
I don't think lack of web-copying is a serious issue in this case.


> An 'evaluate' prompt is what I hate about editors like vim or emacs or micro or dte.

I whole-heartedly agree with you on this! Though, in vim, this is partly because the alternative to typing commands sucks: there's no menu, the shorcuts are not particularly intuitive or memorable, and discovering the right keyboard shortcut is an exercise in frustration. In nano's case, however, an 'evaluate' menu wouldn't abolish existing functionality or replace nano's menu-based approach; it would simply be extra functionality for whoever needs it, and invisible to whoever doesn't. But, if present, it becomes very useful (e.g. for macros); and if this menu were to accept 'set' and 'bind' commands as well, then it becomes very powerful indeed.

Of course, an 'evaluate' menu, and a clean syntax for command sequences are not necessarily mutually exclusive features!

Tasos Papastylianou <tpapastylianou>
Sat 01 Jan 2022 02:56:02 PM UTC, comment #3: 

Creating an 'evaluate' function would avoid having to make changes to the parsing routines in rcfile.c, true, but the resulting string-bind commands would still suffer from most of your original objections against the current state of things.

  1. They are ugly (or visually obfuscated, as you say).
  2. They are a nuisance to type: M-V M-! functionname M-V <Enter> ...
  3. They cannot be copy-pasted from a web page.


Besides, an 'evaluate' prompt is what I hate about editors like vim or emacs or micro or dte.  When the cursor is blinking at that prompt, I think: Arrw... Now I have to know the names of the functions... Grrr! Just give me a fakking menu!

So, if binding to a sequence of things is going to happen, it will have to be a syntax without control codes: human-readable, copy-pastable, and easy to type.  It will require some changes to the bind-parsing code of rcfile.c (to stop also at a semicolon and at an unescaped double quote), but that looks feasible.

Benno Schulenberg <bens>
Group administrator
Thu 30 Dec 2021 05:21:59 PM UTC, comment #2: 

My proposed syntax was simply a visual example. I don't know if I would suggest that explicitly as the actual syntax for this; in fact, I can see one big problem with it, which relates to how nanorc treats quoted values specifically, by treating the last double quote as the closing quote, and interpreting all other in-between quotes as literal quotes.

This makes something like


bind ^F function;"string";function;"string";function main


rather problematic.

If I may change the context slightly, there is a way to avoid having to introduce brand new (and possibly problematic) syntax for this, but it would require a different change to nano: the ability to execute a function directly, via a bespoke menu serving that specific purpose. This is not too far from what you propose, but implies an explicit (sub)menu for running functions, rather than performing 'under the hood expansions' instead as you propose. I can see how that may be more work than can be justified though (though it would greatly expand nano's versatility).

E.g. assume a new menu (and corresponding menu-launching function) called, say, 'evaluate', whose purpose is to execute internal functions.

If you then do:


bind M-! evaluate main


then a sequence of functions/strings can be placed in a macro simply as:


bind "^[!function1^M^[!function2^MMyStringContent^![function3^M" main


without having to bind a key to functions 1, 2, and 3 in advance, just for the sake of using them in such a sequential macro. To me such an approach would be straightforward and visually acceptable.

Furthermore, if such an 'evaluate' menu allows a user to call not just internal functions, but also to set options live, maybe even rebind keys live, etc (i.e. whatever line is valid in a nanorc file should be valid input in this menu), then this would be a very powerful feature indeed. E.g. I note that it would make lots of tickets (including bug #61703) obsolete, since one could, e.g. do "set fill 80" from such an evaluate menu to change the editor's state live (rather than 'edit and then reload .nanorc' ).

Sorry for the verbiage, this is obviously extremely low priority and more for brainstorming purposes. :)

Tasos Papastylianou <tpapastylianou>
Tue 28 Dec 2021 03:34:18 PM UTC, comment #1: 

Yes, being able to bind a key to a sequence of functions and strings would be cool.  It has been proposed before (see [1]; continued in [2]), but your proposed syntax is much leaner and cleaner:

bind key function;function;"string";function... menu

I can imagine how this could work: when such a sequence of functions and/or strings is detected while parsing an rc file, then for each function a special keycode is added to the 'expansion' string followed by the function name.  This rcfile routine should check that it is an existing function name, but not whether it is available in the specified menu, as preceding function names could have entered a different menu.  When the bound key is pressed, the whole expansion string is implanted in the keyboard buffer, as is already done.  When then parse_kbinput() encounters the special keycode, it will read the function name, check whether it exists in the current menu, and if so, execute it, and when not, report an error on the status bar and discard the entire rest of the keyboard buffer.  Anything I'm overlooking?

[1] https://lists.gnu.org/archive/html/nano-devel/2018-08/msg00101.html
[2] https://lists.gnu.org/archive/html/nano-devel/2018-09/msg00000.html

Benno Schulenberg <bens>
Group administrator
Fri 17 Dec 2021 03:40:09 PM UTC, original submission:  

At the moment (nano 6.0), the 'bind' command can:

  • bind a single function to a particular key, in the context of a particular menu
  • bind a "string" to a particular key, in the context of a particular menu.


For the sake of this discussion, we define the following terms:

  • Let functions and strings be collectively known as actions
  • Given an action which has been bound to a specific key, let that keystroke be known as a command which produces that action. (this is effectively how this term is used in 'man nanorc').
  • Strings are a special kind of action, because they can intermix normal text with commands; the latter are introduced into the string by typing "verbatim keystrokes" (entered via Alt-V). This creates a handy way of creating pre-recorded macros. (related: #52931, #58991). To disambiguate from simple, text-only strings, let us refer to such complex, command-containing strings as commandstrings


At the moment, commandstrings are the only way to bind a sequence of actions to a single keystroke. This has the unfortunate side-effect that, in order to bind a sequence of actions, all actions involved in the sequence must have their own keybindings. This is not ideal, for two reasons:

  1. It wastes keybindings unnecessarily
  2. It makes the sharing of macros between users problematic, since one cannot guarantee that temporary bindings created by one user for the explicit purpose of creating a suitable commandstring, won't result in the overriding of another user's custom key bindings.
  3. It generally results in visually obfuscated commandstrings, whose underlying functionality is hard to decipher by eye.


It would be useful if bind could accept a sequence of actions instead of only a single action.

E.g., this could take the following form:

bind key function;function;"commandstring";function menu

Tasos Papastylianou <tpapastylianou>

 

(Note: upload size limit is set to 16384 kB, after insertion of the required escape characters.)

 

Depends on the following items: None found

Items that depend on this one: None found

 

Carbon-Copy List
  • -email is unavailable- added by bens (Posted a comment)
  • -email is unavailable- added by tpapastylianou (Submitted the item)
  •  

    There are 0 votes so far. Votes easily highlight which items people would like to see resolved in priority, independently of the priority of the item set by tracker managers.

     

    Follow 8 latest changes.

    Date Changed by Updated Field Previous Value => Replaced by
    2022-11-16 bens Open/ClosedOpen Closed
    2022-08-18 bens StatusReady For Test Fixed
    2022-08-12 bens Attached File- Added 0001-input-interpret-commands-of-the-form-functionname-in.patch, #53565
        StatusIn Progress Ready For Test
    2022-08-10 bens StatusNone In Progress
        Assigned toNone bens
    2021-12-28 bens Severity3 - Normal 1 - Wish
        Summary[Wish] Enable bind to perform a sequence of functions/commandstrings rather than just a single function or commandstring [Wish] allow binding a key to a sequence of functions and strings

    Back to the top

    Powered by Savane 3.13-f8d8.
    Corresponding source code