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

Protocol extension: multiple cursors #720

Closed
maximbaz opened this issue Jul 11, 2018 · 81 comments
Closed

Protocol extension: multiple cursors #720

maximbaz opened this issue Jul 11, 2018 · 81 comments

Comments

@maximbaz
Copy link
Contributor

More and more text editors are adopting the concept of multiple cursors, I'm playing with one of them called kakoune.

In order to draw multiple cursors consistently, kakoune has to hide the terminal cursor completely and "draw" its own cursors by manipulating background color of a cell. This is ugly hack that prevents people from using the much superior native cursor with all of its features (different shape, blinking, proper color).

In vim you only see one cursor, even if you are editing multiple lines, but there you are not editing multiple lines simultaneously anyway (vim waits for ESC to be pressed, then applies the same edit on the rest of the lines).

In kakoune, multiple lines are being edited at the same time, potentially in different positions, so it's really beneficial to show multiple cursors.

kak-multiple-cursors


What I was thinking about is to propose you to think about multiple cursors as an extension over xterm protocol.

I asked for how this works in layman terms, I was told applications like kakoune or vim take care of handling keypresses and rendering the text on the screen, and then they tell the terminal to put a cursor in a certain place, kind of like move (x,y). What is missing is the ability to draw more cursors, e.g. like draw (x1,y2). These "secondary" cursors should basically be a copy of the main cursor in terms of color, shape and blinking. And then there should be a way to remove all "secondary" cursors from the screen.

I brought this to you because you understand a lot more about the terminals, and I want to hear your thoughts. I hope I managed to at least explain the need for this feature: multiple cursors is a thing that is becoming more and more popular and demanded by users (here's just one example in neovim), unless terminal allows drawing them, software has to completely take over drawing cursors, and of course software-rendered cursors cannot ever have the features like shapes and colors and blinking that native cursors easily provide.

@kovidgoyal
Copy link
Owner

kovidgoyal commented Jul 12, 2018

Personally, I have never needed multiple cursors, but sure, I can see how they might be useful. It isn't particularly hard to design a protocol to do this in kitty, but it should ideally have involvement from somebody who is implementing this feature in some terminal editor, so that the protocol can be designed to meet their needs.

If you can get somebody like that to join the discussion, I will be happy to work with them on designing and implementing such a protocol in kitty.

I'll leave this issue open for a while, lets see if there is buy-in from editor developers.

@maximbaz
Copy link
Contributor Author

Good news! Let me ping a few kakoune's contributors directly, to see if anyone is interested in getting the native support for multiple cursors from terminal emulators: @mawww, @lenormf, @Delapouite, @alexherbo2, @casimir, @danr, @ekie, @occivink.

@erf
Copy link

erf commented Jul 13, 2018

Would this be relevant for the vis editor? @martanne

@mawww
Copy link

mawww commented Jul 17, 2018

Hello,

For Kakoune, I believe an easy approach for that would be an attribute, similar to underline/bold/itallic... This attribute would just mean "highlight each character as-if they had the cursor on top of them". This would stay separate from the "real" hardware cursor, which would remain at the hardware cursor position and would be displayed according to the current cursor display mode. The cursor attributes would not respect the current cursor display mode, as it could make sense to hide the real cursor while still display various characters with the cursor attribute, we might even want multiple cursor style attributes (block/vertical line), combined with existing blinking attribute.

The tricky part for Kakoune is that we use ncurses for output, which unfortunately does not give us a way to use arbitrary attributes, so before being able to support this, we would need to rewrite the terminal UI code to replace ncurses (at least for the display parts).

Those are my quick thought on that, but to be clear, I dont consider this a priority in Kakoune at the moment, and it depends on replacing (parts of) ncurses, which is a significant amount of work.

@kovidgoyal
Copy link
Owner

That has a few concerns:

  1. What happens when you erase the character at a location with a cursor attribute set? Does the cursor remain? Does it get deleted? Similarly what happens when you write a new character into that location?
    What happens when you insert a character somewhere before in the line? Does the cursor move?

  2. How does it work? Character attributes in terminals typically work by setting the attributes on the cursor, and those attributes are copied into the cell when you output new characters. So to render your cursor you would need to move the hardware cursor to each location and output the character at that location again.

  3. From an implementation perspective, it is not ideal, because while it works well for block cursors which are basically rendered by changing the fg/bg colors during normal character rendering, for other types of cursors, it would mean an whole extra scan of every cell to check if it contains a special cursor, in every rendering pass.

I think a better design is to have a dedicated escape code to create/style/position/delete extra cursors. This has the additional advantage that you dont need to change our ncurses based code at all. Simply write the extra escape codes to stdout in between calls to ncurses' API.

Offtopic and a bit ranty: ncurses is a horrible library, I strongly urge you to get rid of it anyway for your own future sanity.

@mawww
Copy link

mawww commented Jul 17, 2018

Regarding your concerns, I was thinking just do exactly as the terminal does for any other attributes, my idea was that displaying characters as-if a cursor was there would work exactly like displaying say an underline attribute on a single character.

In Kakoune display code, we only know of the cursor position in order to tell the terminal at the end of redraw where to place the hardware cursor so that it can use that as a hint for its eventual widgets (such as displaying an emoji picker, or placing the input method window OnTheSpot...). The rendering code itself just paints the full window with ncurses, and cursors have no special semantics, they are drawn exactly as the rest (Kakoune display structure is basically a list of display lines, themselves being a list of display atoms themselves being a tuple of a string, fg/bg colors and attributes).

For the implementation side, I am not sure how kitty paints its cursor, but couldn't it reuse the cursor painting logic and apply it if the cursor attribute is on when it paints a cell ? No need for a second pass.

@kovidgoyal
Copy link
Owner

kovidgoyal commented Jul 17, 2018

The problem is that different attributes are treated differently in various situations. For example, reverse video has no effect, background may or may not change depending on the bce terminfo property, underline may or may not take effect depending on the character drawn, etc. So the semantics of this "cursor attribute" would need to be defined carefully keeping in mind interactions with all other terminal escape codes.

As for implementation, kitty does not draw individual cells, it is GPU based, which means it draws all cells at once, in parallel on the GPU. So while this is fine for block cursors, which as I said are drawn as normal cells, just with colors changed, for non block cursor there is a separate draw call needed. This separate draw call currently draws only a single cell where the hardware cursor is. With your proposal it would need to draw all cells, since it cannot know which cells have special cursors in them. And worse, it would have to do that all the time, even if no special cursors are being used, which will be the case the vast majority of the time.

With my proposal, just as you tell the terminal where to put the hardware cursor after drawing the lines, you tell it where to put the extra cursors. This is low overhead for you (you dont have to maintain an extra attribute per character which is used only rarely) and low overhead for the terminal, for the same reason. Of course, it does mean that it works differently from how you do special cursors now, which I guess, is why you dont like it.

@kovidgoyal
Copy link
Owner

And I would like to add, semantically, a cursor is not a text attribute. A cursor is instead an element of the UI that is independent of the text under it. Treating it as a text attribute seems wrong to me.

@mawww
Copy link

mawww commented Jul 17, 2018

I still dont quite understand why drawing a cursor on top of a cell cannot be done while the cell is drawn, even when done on a GPU, how is that fundamentally different from being able to draw a colored wavy underline on that cell ?

Placing cursors after the fact seems pretty complex to me, we now have some additional state to track between the application and the terminal emulator, which is more potential bugs, and we need a new protocol to control those (whats the lifetime of those cursors ? do they disappear if I repaint the cell ? Does the escape sequence that creates them replaces all the existing ones, or add new ones ? etc...)

Using an attribute is pretty easy, and avoid inventing something new (although it seems there is still a few questions to answer, I was not aware that terminals have different behaviours on cell erase depending on the attributes the cell contains). The alternate implementation would not be difficult to write though.

And finally, the semantics of cursors in a fullscreen terminal application really only concerns the application and the user, I dont really see what the terminal could be doing besides displaying those, which is why I though attributes would make sense (the hardware cursor though has semantic meaning for the terminal, it tells it where to draw, and can act as a hint for where user attention is located, which is why I proposed to keep it as-is).

All that said, I do not care that much, as I already said this is not a priority for Kakoune at the moment.

Cheers.

@kovidgoyal
Copy link
Owner

kovidgoyal commented Jul 17, 2018 via email

@mawww
Copy link

mawww commented Jul 17, 2018

I would like a solution that means the CPU/GPU are doing no significant extra work when there are
no extra cursors.

Aren't we already paying that price to support wavy underlines ? I assume you disable that if there are no wavy underlines to display, which you could do as well if there was no extra cursor to display (the real hardware cursor does not need to go through this path, it could still be handled as it is right now).

Any text attribute is also state that needs to be tracked between the application and the emulator, there is no difference there. The lifetime of the cursor is simple: It exists until deleted by the application or on transition to/from alternate screen mode or on reset.

Text attributes is state we already know how to track both on application and terminal side, tracking additional cursors is more involved, imagine an app with multi-cursor crashes and hence does not remove them, the user now has a shell with multiple cursors displayed on the screen, only one of which is relevant.

Repainting the cell has no effect on the cursors, just as it has no effect on the hardware cursor. The extra cursors are semantically indistinguishable from hardware cursors. This seems like the most natural behavior.

Do you mean that outputting a character to the terminal would insert it at every cursor position, and move those cursors by one cell ? I dont think it would be manageable, I think only the hardware cursor should have those semantic properties, the other cursors are strictly for display (which is why in my view attributes makes a lot of sense, it just means "display those cells as-if there were cursors on them").

@kovidgoyal
Copy link
Owner

kovidgoyal commented Jul 17, 2018 via email

@egmontkob
Copy link

egmontkob commented Jul 17, 2018

Hi guys,

The issue starts by saying "More and more text editors", yet, surprisingly to me, only one terminal emulator is involved in the discussion. Do you guys intend this to be a Kitty-only feature, or one hopefully adopted by other notable terminal emulators as well? In the former case, let me know and I'm outta here leaving the rest up for you to discuss :-) In the latter case, my (vte/gnome-terminal developer) thoughts so far:

On one hand this sounds like a bit of an overkill to me, on the other hand I can also imagine other useful situations. E.g. even a relatively simple text editor such as "joe" can show multiple viewports to the same file at once, it would be nice if the cursor was shown in all of them (or would it be distracting, since the user couldn't be sure which one was the main one, i.e. the one which will start scrolling to keep the cursor inside the viewport, and where special operation (such as Find) will work?). Tmux-like apps could have synchronized (multiplexed) input sent to all panes, and show the cursor in all.

Saving a few CPU/GPU cycles in one particular terminal emulator should be the very last factor in designing this feature. With all respect, I just cannot care at all which approach saves a few row scans or whatever operatoins in Kitty (or in VTE, or any other emulator). We should go for the design that's simple, clean, builds on top of what we already have, makes it relatively easy for apps to adapt this new feature.

I don't really care about ncurses either, and I don't care about hacks where an app uses a mixture of ncurses and off-ncurses output. Ncurses already has quite a few limitations that make it unsuitable for most "big" projects. E.g. due to its palette approach, it's impossible to display arbitrary (not predefined) colors as they arise (e.g. "mcview" cannot display ANSI colors like "less -R" does, "watch" cannot support 256 colors etc.), and then we haven't even talked about true colors. Then there's the colored and wavy underline, the explicit hyperlinks (VTE and iTerm2; not yet in Kitty), and presumably more. Any application that wishes to use multiple cursors probably sooner or later will also wish to use some of these as well, so will have to switch to its own screen drawing (or some other library) anyway.

I think you guys have already agreed that there would still be only one "main" cursor (where new incoming data is printed etc.), the rest are "extra" cursors. I concur. Usually the user probably wouldn't be able to tell which is the main one, but there might be exceptions. E.g. when you press Ctrl+Shift+U to enter a Unicode character, Kitty brings up its own stuff at the top of the window, so this would be unaffected; however, VTE presents an inline box at the cursor, I think it would still go to the "main" one. Other emulators have visual aids helping you locate the cursor (all the time, or upon a keypress), they might decide to highlight the main one only, not all of them. In VTE we experimented with hiding the cursor on focus out as pretty much every graphical app does; even though we dropped this idea, it might make sense (I'm not sure, we'd need to try it out) to hide the "extra" cursors on focus out.

With this hidden concept of "main" vs. "extra" cursors, the extra cursors carry some semantics in the applications (text editors) towards the users, but they don't have any semantics for the terminal emulator. For the terminal emulator it's just a visual attribute.

As for the overall design (just another attribute, or a totally different kind of escape sequence updating and maintaining the extra cursors' location) I'm firmly in favor of the "just another attribute", that is, mawww's approach.

[kovidgoyal] 1. What happens when you erase the character at a location with a cursor attribute set?

With this approach, this question is already answered. The same should happen that to any other attribute, there's absolutely no point in treating this attribute differently. The background color wrt. bce is a bit special so it deserves a sentence: I don't see any point in applying the new "make it look like as if it was a cursor" attribute to the rest of the line in case of a bce, so still the background color should be the only special one here, the new attribute should behave just like the existing ones (e.g. underline, strikethrough) do and not get applied here.

[kovidgoyal] 2. How does it work? [...] So to render your cursor you would need to move the hardware cursor to each location and output the character at that location again.

That's exactly what happens now with any attribute that an app needs to modify, so I don't see any problem here.

[kovidgoyal] The problem is that different attributes are treated differently in various situations. For example, reverse video has no effect [...], underline may or may not take effect depending on the character drawn, etc. [...]

In VTE there's no special handling of reverse or underline anywhere. Why is there in Kitty? Do we miss anything? I believe all our non-background attributes have the very same handling.

[kovidgoyal] With my proposal, just as you tell the terminal where to put the hardware cursor after drawing the lines, you tell it where to put the extra cursors. This is low overhead for you (you dont have to maintain an extra attribute per character which is used only rarely) and low overhead for the terminal, for the same reason. Of course, it does mean that it works differently from how you do special cursors now, which I guess, is why you dont like it.

It seems to me that for you, "low overhead" means saving a bit, or some CPU cycles. For me, "low overhead" primarly means cleaner code, saving developer time, and hence reusing existing pieces wherever appropriate rather than coming up with something brand new.

[kovidgoyal] The lifetime of the cursor is simple: It exists until deleted by the application or on transition to/from alternate screen mode or on reset.

So we'd need two brand new escape sequences: Placing an extra cursor at a specific position, and deleting. We'd even need a "delete all", for when the application repaints its screen, e.g. to repair a potentially damaged one. Then maybe we don't even need a "delete one". Plus, figure out how these positions are affected by text being output or deleted, by scrolling (and scroll regions), by clear-to-eol, by window resize and so on and so forth. Sounds magnitudes more complex and error-prone (e.g. there's not even a "standard" terminal emulator to follow; what if two emulators implement some corner case differently and it causes troubles later on?) to me than assigning let's say ANSI 66 and 67 to enable/disable "extra cursor" mode and just doing everything the same way we do for other attributes.

A few more points:

What would happen on the normal screen? Would extra cursor be supported at all? Could it scroll out to the scrollback buffer? What would happen to it on rewrap-on-resize?

Repainting the screen after a text editor operation (e.g. page down) can take a few kilobytes of traffic, which might get stuck over ssh. With off-channel communication for the cursor positions, this might easily result in intermittent visual glitches, the extra cursor being shown at the wrong place. With in-channel communication I believe the visual experience would also be better.

[mawww] For Kakoune, I believe an easy approach for that would be an attribute, similar to underline/bold/itallic...

I think it's not just for Kakoune, but for most apps out there. If they have an existing infrastructure of tracking where underline/bold/italic are present, it's way easier to just add yet another attribute to this infrastructure, rather than coming up with a new one for the sake of the cursors. It feels to me that this approach is not only cleaner, but is also easier to implement (replacing ncurses put aside) in most applications, and most terminal emulators (sorry to hear if Kitty is not one of these).

@maximbaz
Copy link
Contributor Author

The issue starts by saying "More and more text editors", yet, surprisingly to me, only one terminal emulator is involved in the discussion. Do you guys intend this to be a Kitty-only feature, or one hopefully adopted by other notable terminal emulators as well? In the former case, let me know and I'm outta here leaving the rest up for you to discuss :-) In the latter case, my (vte/gnome-terminal developer) thoughts so far:

My bad, I simply didn't know where to open such discussion, and since I'm using kitty and I saw it already implement a few protocol extensions, I decided to propose here yet another one.

Of course this is intended to be adopted by other terminals as well, so your input is very valuable 👍

@egmontkob
Copy link

I simply didn't know where to open such discussion, and since I'm using kitty and I saw it already implement a few protocol extensions, I decided to propose here yet another one.

Fair enough, actually there's no right place (other than cross-posting to a few). Kitty is probably the "bravest" emulator now, experimenting with new features, so it's a great place to start. I've notified VTE's main developer, as well as iTerm2's developer. Thanks for posting this idea!

@kovidgoyal
Copy link
Owner

kovidgoyal commented Jul 17, 2018 via email

@kovidgoyal
Copy link
Owner

And let me add, that if there is some use case I missed in my bytestream demonstration above, feel free to point it out, and I will attempt to show how it is easier to do using dedicated escape codes rather than overloading text attributes.

And just to reinforce the point, @mawww my proposal allows you to continue storing extra cursor information as text attributes in you application, if that is what you want to do. The terminal emulator does not care what internal data strucutre you use.

This discussion should be about the protocol between the application and the terminal emulator and the interactions of the extra cursors with other terminal commands such as erase, scroll, print characters, etc.

@egmontkob
Copy link

egmontkob commented Jul 17, 2018

Sticking to the "just another attribute" philosophy for a while, which I'll get back to later:

  1. Using a text attribute [...] So, in this case my proposal is actually simpler to implement for an application developer.

No, it's not easier to implement. It involves fewer bytes of traffic. It would be easier to implement if it was the very first attribute to handle. But assuming that other attributes are already handled the same way, it's easier to stick to that same method, rather than coming up with something new. You miss the fact that the first variant is already implemented and hence doesn't need to be implemented again, while the second isn't yet.

[...] Again, my proposal is much simpler.

Again, it would be simpler if either approach would need to be implemented from scratch. That isn't the case. One is already implemented, while the other one isn't yet.

Finally, suppose that the editor wants to change the set of cursors displayed, hide some, move some, etc. 1) Using text attribute Redraw entire screen [...]

For some mysterious reason here you forgot about the possibility of redrawing only the affected characters. Nevermind.


And finally, let me re-iterate my main concern, that no one seems to want to address: Cursors are not text attributes.

I have to admit that I missed this bit, or more precisely, missed the strong emphasis you put on this approach.

Given that block cursors are a legacy thing, the cursor in all modern apps is a vertical bar between characters, which is reasonably well emulated using the I-beam cursor shape in terminal emulators, I have to stop here for a while and let this approach sink in. I think you might have convinced me (making our previous debate pointless).

While now I agree that this is the right philosophy, I still believe it makes implementation harder (something new needs to be designed and implemented, rather than reusing something that already exists), and hence it's not necessarily the most practical choice, but probably the correct one. I'm not yet explicitly in favor of this approach (give me a few days to ponder about this), but I'm okay with it.

So to move forward from here, I guess we'd need escape sequences to some of these (which ones exactly?) and answers to the questions:

  • Leave a "trail", place an extra cursor where the real cursor currently is.

  • Place an extra cursor at an arbitrary (x,y) position.

  • Would printing data over an extra cursor (or if thinking in I-beam shape: after the extra cursor) leave it there, or would it wipe it out?

  • Remove the extra cursor from the real cursor's current location.

  • Remove the extra cursor from an arbitrary (x,y) position.

  • Remove all extra cursors.

  • How would an extra cursor be affected if the content underneath moves in any of the four directions? (Vertical scroll, or a previous character in the line inserted/deleted.) Would the cursor follow them, or stay at the same absolute place, or get wiped out?

  • Would the (x,y) positions affected (and bound) by the scrolling region?

  • What to do on resize? Is it okay to wipe them out all (as e.g. the scroll region is reset), expecting the app to do a full repaint anyway?

  • Nit: How would a cursor positioned to the middle of a wide character be handled?

  • Nit: With I-beam shape in mind, would it be possible to place a cursor after the last column?

  • Nit: Would all extra cursors automatically share the main one's properties (shape, color, blinking)? Or could they each have their own ones?

  • Anything that I'm missing or overcomplicating? :)

@kovidgoyal
Copy link
Owner

kovidgoyal commented Jul 17, 2018 via email

@kovidgoyal
Copy link
Owner

kovidgoyal commented Jul 17, 2018

So for the moment I propose we focus on Stage 1, which is answers to the
following questions:

  1. Editor developers: Is this something you care enough that you are
    likely to implement this in the reasonably near future if some terminal
    emulators add support for it?

  2. Terminal emulator developers: Is this something you are willing to
    implement in principle? I say yes for kitty.

  3. Everybody: Do we want to use a new text attribute or a new escape
    code to implement

@justinmk
Copy link

justinmk commented Jul 17, 2018

Though multicursor support is in the works for Nvim, I don't yet see a need for special terminal features.

kakoune has to hide the terminal cursor completely and "draw" its own cursors by manipulating background color of a cell. This is ugly hack that prevents people from using the much superior native cursor with all of its features (different shape, blinking, proper color).

The way I envision multicursor is a "primary" cursor plus other "marker" (secondary) cursors. The secondary cursors don't need to look identical to the primary cursor, they are just visual clues. I would guess that a human is not greatly aided by all of the cursors looking like the primary cursor, rather it may increase confusion.

Actually, I'd be more interested in new text attributes as @mawww hinted. E.g., ability to display (1) a hollow "box" around a cell, (2) a bar on the left side, and (3) bar on the right side. Entirely orthogonal to cursor(s).

@gnachman
Copy link

iTerm2 author here: I'm fine with adding this, but I don't want it to be an attribute. I only have a few bits of attributes left before my struct needs to grow. That will increase memory usage and decrease cache efficiency for an attribute that would almost never be set. That's an implementation detail, but it's an important one.

More generally, it's easy to draw an analogy between the "real" cursor and the list of "virtual" cursors. It's a thing that has a coordinate that gets drawn on top of the text and there are some rules for how it's drawn that are configurable both by the user's preferences (e.g., color, shape) and by escape sequences (shape).

Provided we go with a list of cursors, I would be happy to implement it. I think we'd need escape codes to:

  1. Set the number of virtual cursors
  2. Position virtual cursor i at x,y
  3. Set the style (box, line, bar) of virtual cursor i

I would also propose that virtual cursors change position only when told to and have no automatic behavior. This pushes the complexity into the app that wants them, but makes it much more predictable in the face of scrolling regions and double-width characters. However, I'm not quite sure how this would work with an ncurses-based app. Someone who has more experience can perhaps comment here.

@maximbaz
Copy link
Contributor Author

The secondary cursors don't need to look identical to the primary cursor, they are just visual clues. I would guess that a human is not greatly aided by all of the cursors looking like the primary cursor, rather it may increase confusion.

I'm not so sure, I would say it is quite rare when I actually need to know which of the cursors is the primary one. Look in the gif in the first post, I'm manipulating a few lines and I want the same manipulation to happen in all lines, regardless of where is the primary cursor position. I would much prefer to see all cursors turn into identical vertical bar when I'm about to prepend the word "private" on every line, because this would indicate to me that I'm about to insert some text on all of these lines.

Sometimes it is helpful to know where the primary selection is, not the cursor (at least in kakoune), because there are some extra actions you can do with the primary selection (search for the word "main" here), but no actions to do with the primary cursor — and the primary selection is highlighted by using the white text color.

However the above is based on my experience with kakoune, I realize that the situation might be different in other editors.

@egmontkob
Copy link

[justinmk] E.g., ability to display (1) a hollow "box" around a cell, (2) a bar on the left side, and (3) bar on the right side. Entirely orthogonal to cursor(s).

SGR 51, 60, 62 might be relevant here.

[justinmk] The secondary cursors don't need to look identical to the primary cursor, they are just visual clues.

[maximbaz] I'm not so sure, I would say it is quite rare when I actually need to know which of the cursors is the primary one.

I can easily think of use cases for either approaches. Maybe the best is if we consider supporting both kinds straight from the beginning.

[gnachman] I would also propose that virtual cursors change position only when told to and have no automatic behavior.

I guess I'd rather they scroll along with the content. Otherwise e.g. cat'ing a binary file may result in visual glitches (additional cursors) that don't even scroll out as you tap Enter.

@egmontkob
Copy link

[kovidgoyal] Already implemented by what? [...] In either case whoever does the implementation, it is easier to output fewer escape codes than more escape codes.

You still don't understand what I'm speaking about. This will probably be my last attempt in explaining.

Let's assume a non-ncurses based editor (there are plenty of them). Presumably it already handles bold, italic, underline etc. By some internal means it figures out which cells it wants to render with such attributes, and then implements telling this to the terminal emulator. Given this current state, it's much easier to add another attribute that follows this pattern, than an attribute that uses a totally different approach. Easiness doesn't mean fewer bytes to be emitted, easiness doesn't mean something that would be easier to implement from scratch, easiness means easier to implement and maintain given the current codebase, that is, less deviations and less additional code to introduce, reusing whatever's already available (i.e. the way bold/italic/underline are handled by this app).

@kovidgoyal
Copy link
Owner

kovidgoyal commented Jul 20, 2018 via email

@kovidgoyal
Copy link
Owner

And just because I'm bored, let's do a bit of simple maths to calculate the probability of their being one extra cursor on the screen after catting a 10MB binary file.

You need a minimum of an 8-byte sequence to create an extra cursor. The probability of such a sequence is ((1/255)**7) * (1/245) which is 1e-20 (technically its a bit higher since you can have longer than 8 byte sequences as well that create extra cursors, but I doubt the contribution from them will be significant compared to the shortest sequence). There are approx 1e-6 8 byte sequences in a 10 MB file. Which leaves you with a first order probability of 1e-14 of seeing a one single extra cursor on your screen after catting a 10MB file. I can live with that.

@egmontkob
Copy link

In either case you have to output the escape codes for them, when you do simply output an incrementing counter. It's two extra lines of code.

If the app always removes and reinstalls all the cursors at once. If that was the only intended way of using these escape sequences, there'd clearly be no need for IDs.

If an app decides to use the more fine grained sequences, that is only remove the cursors that are no longer necessary, only add the newly appearing ones, and leave untouoched the ones that don't move, then having to have IDs is an additional, nontrivial burden.

Similarly after a resize it cannot know the new locations.

Why can't? Won't it be part of the specification?

Terminals do not paint the screen in an atomic manner, they never have and I doubt they ever will.

Not updating the entire screen as an atomic step is fine. What I'm mentioning is when something shouldn't change, but due to a lag in traffic, ends up changing back and forth on the UI. This can already happen to the real cursor, but would be nice not to extend to the extra cursors, if reasonably possible.

@kovidgoyal
Copy link
Owner

kovidgoyal commented Jul 20, 2018 via email

@egmontkob
Copy link

egmontkob commented Jul 20, 2018

it must be able to address them internally as well, which means they already have an internal id (or at least some kind of internal data structure to represent an individual cursor)

This can be just 1 bit per cell in its already existing matrix denoting what's on the screen, next to the bold, italic etc. attributes. With no IDs. This is what you said to mawww that he'd still be able to do. This is what I believe is the simplest approach to implement this feature in any non-ncurses based app, because it reuses what the app already does for bold etc., without having to come up with something new as well.

What shouldn't change? You are sending n cursor creation escape codes one after the other. [...]

Imagine that there are three extra cursors at positions A, B and C. The app wants to change it to four positions: A, B, D and E. The app's internal logic doesn't wish to fiddle with IDs, so instead clears and reinstalls all the cursors. There's a potential for a flicker at positions A and B, the cursor disappearing for a short time. Being able to specify multiple cursor operations in a single escape sequence could avoid this flicker.

@kovidgoyal
Copy link
Owner

kovidgoyal commented Jul 20, 2018 via email

@egmontkob
Copy link

As I said, literally two extra lines of code.

Two extra lines of code, with a design that increases by magnitudes the chance of flickering (an extra cursor disappearing and a few draw cycles later reappearing at the same position).

You mean between clearing the cursors and re-creating them? That would be present regardless, even if the protocol allowed you to create multiple cursors in one escape code.

The terminal emulator could decide whether to process individual requests as it encounters them, or batch them up until the terminating <ESC>\ sequence and then process them all, atomically. In case of VTE, due to the way its parser used to work (and I presume still works as of the rewrite, although I haven't checked), we'd do the second approach anyway, it'd be atomic.

@egmontkob
Copy link

Two extra lines of code

Sorry, I have to correct myself, I responded too quickly.

Screen drawing is probably more sophisticated than the pseudo-code you outlined. I mean they probably do some minimal diff-ing between the old and new buffers, e.g. update only the lines that changed, and maybe even within the line they omit the beginning and trailing portion that remained the same.

With your approach, they'd need a bit more than those 2 lines to re-process the extra cursors in lines that didn't change.

I'm not saying it's hard to do, but I'm still saying it's an unnecessary additional complexity.

@kovidgoyal
Copy link
Owner

kovidgoyal commented Jul 20, 2018 via email

@lenormf
Copy link

lenormf commented Jul 20, 2018

The maintainer of tmux had a few comments about the design, hope this helps:

Their choice of escape sequence for this seems unnecessarily different from everything else. Why make it delimited rather than using something with arguments? Why make it comma-separated when ; or : are the usual separators? If you are going to use key-value pairs, why not make them comprehensible rather than single letters?
If you want my opinion on the design then I would personally forget all this i=x,c=y,r=z stuff and just have a cursor set and cursor clear sequence. There seems no harm in requiring the existing cursor to move first like for tab stops. I'd just do something like add some extended WINOPS:

\e[99999;0;10t <-- add cursor ID 10 at current main cursor position
\e[99999;1;10t <-- delete cursor ID 10
\e[99999;1t <-- delete all extra cursors
\e[99999;2;10;x;y;zt <-- and so on, whatever else you want, cursor colour, etc

You are much more likely to get support if the escape sequence is similar to or extends an existing sequence rather than making everyone add a parser for yet another form.

@egmontkob
Copy link

What on earth have ids to do with flickering?

With the code snippet you showed, the removal of the cursor is followed by potentially up to a few kilobytes of text data (and attribute changing escape sequences), then re-installing the cursor at the same position. The amount of data inserted in between could easily make them no longer fit in the same ethernet or similar packet, eventually noticeably increasing the chance of a network lag kicking in.

And I'll just note in passing that cursors blink

or not (oh, wait, it seems to me that in Kitty it always blinks)

which means that flickering for them is meaningless.

A flickering during the "on" state of a blinking cursor is IMHO still a bit annoying.

At this point you are just arguing for the sake of arguing.

No. I'm trying to argue (although I'm really lost) because I have a bad feeling that no matter how I phrase my thoughts, I cannot make them come across to you. I mean, if I felt you understood what I'm saying and still disagreed, that'd be fine. But unfortunately I feel like you don't understand what I'm saying (maybe don't take the time or don't have the motivation to try to understand what I'm saying?? I'm really unsure) then it's very hard. I sincerely hope that my feelings are incorrect. I really don't know what I am doing wrong if my thoughts don't come across clearly.

I was just about to quit this conversation and let you continue in your preferred way. I'm sorry you did it first and not me, and sorry from others too. This enhancmenet request was filed against Kitty, it's your project, do as you wish. If you reopen and continue the work, I promise I'll stay out of the way.

@kovidgoyal
Copy link
Owner

Sorry, but this discussion has made it clear to me that I lack the patience to steward something like this. As I said, feel free to implement it in your own terminals using my existing proposal as a base or not. If and when some proposal becomes widely adopted/used by editors, I will be happy to implement it in kitty.

But I am done spending my valuable time creating proposals just to engage in endless meaningless arguments about them.

@leonerd
Copy link

leonerd commented Jul 20, 2018

@justinmk wrote:

Actually, I'd be more interested in new text attributes as @mawww hinted. E.g., ability to display (1) a hollow "box" around a cell, (2) a bar on the left side, and (3) bar on the right side. Entirely orthogonal to cursor(s).

This would be nice. Totally independently of extra cursors, I could see this being useful for other cases besides. They'd be very easy to add as new numbered SGR attributes

@leonerd
Copy link

leonerd commented Jul 20, 2018

@egmontkob wrote:

SGR 51, 60, 62 might be relevant here.

I hadn't heard of those before. Do you have some relevant documentation? I'd like to add them to my spreadsheet, where I'm trying to keep things like this organised:

https://docs.google.com/spreadsheets/d/19W-lXWS9jYwqCK-LwgYo31GucPPxYVld_hVEcfpNpXg/edit#gid=836709407

(I'm happy to add/edit things in there for other terminal emulators, also)

@kovidgoyal
Copy link
Owner

@leonerd it's in wikipedia https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_(Select_Graphic_Rendition)_parameters

@justinmk
Copy link

justinmk commented Jul 20, 2018

So the "hardly ever supported" (51-65) SGR parameters most likely will continue to lack support in kitty, iterm2, VTE, because of struct space?

@kovidgoyal
Copy link
Owner

If you want them supported, I am not adamantly opposed, but you will need to make a case for it to convince us :) kitty actually has space in its struct at the moment. The downside of supporting this in kitty will be extra per pixel operations in the shader. There are also some subtleties to be aware of, namely those are meant for ideographic characters, I dont know if it is appropriate to simply use them for ordinary characters.And one has to consider what happens with them on wide characters.

@kovidgoyal
Copy link
Owner

And if you do want to make a case, I suggest opening a new enhancement report for it, this one is already quite long :)

@egmontkob
Copy link

Do you have some relevant documentation?

No, I don't. All I know is what's written in ECMA-48 and the Wiki page linked by Kovid. I don't even know what "ideogram" means, nor have an idea what to do with "underline or right side like", I mean which one?

The feature could be useful for nice and compact representation of tables. Double width lines could be achieved by placing e.g. a character with a right side line and a next character with a left side line next to each other. Not sure if it's worth 4 new bits per cell, though. Not sure how we are with free bits in VTE, the main developer recently reorganized the bits and I haven't checked the code since. Just as Kovid, I'd also prefer to see actual (not hypothetical) use cases, utilities willing to add support.

If we'd like to move forward, I'd like to propose 4 new attributes for the 4 sides. SGR 51 ("boxed" or "framed") might be a shortcut for enabling all 4 at a time. The wording of SGR 60 and 62 are so unclear to me that I wouldn't want to use them, unless someone has a clarification on them. SGR 53 (overlined, hardly ever used) may be used for the top one, or may be obsoleted by the top one.

I'd like the bottom one to be separate from the underline. The underline goes slightly below the baseline of the font, and might be subject to "text-decoration-skip-ink" rendering, curliness, different color (or would we want that for the frame too?) etc. The border bits would I imagine go to the very edge of the character cell, so that adjacent ones are joined even in case of increased line spacing or character spacing.

@egmontkob
Copy link

As for the very concrete escape sequences, we might be thinking along the lines of 51:n (a subparameter just as with the underline style), the second parameter going from 0 to 15, being a 4-bit bitmap for the four sides. That way we don't pollute the "global" SGR space with 4-5 new entries.

@kovidgoyal
Copy link
Owner

I agree with Egmont about 51:n and these being independent of underlines. Those were the choices I would have made myself. The only question is whether to use 51 or some other number. After all some terminal emulators out there may already use 51.

I am curious as to what the use case for this is though. Since the border lines could potentially overlap with the rendered character, I dont know how nice it would look (think of wide characters like W that touch the edges off the cell in most fonts. But, as I said, feel free to make your case, preferably in a separate bug report.

@egmontkob
Copy link

51 could remain an alias for 51:15, that is, turning on all four sides. Existing implementations, however, may not put the frame at the very border of the cell, which – as far as I understand – would be a requirement for the new one. So I don't mind introducing a brand new number. We've recently assigned 58 and 59 to colored underline, but 56 and 57 are still free, aren't they? And we'd only need one.

I agree that this whole discussion should continue in a separate issue.

@gnachman
Copy link

As long as we're bikeshedding, I'd like to add an extension to enable atomic drawing so that when the entire screen gets redrawn it doesn't flash a half-drawn screen for a fraction of a second before the rest of it appears. I have a hack in iTerm2 that tries to use whether the cursor is visible to provide synchronization but it is far from perfect. Since that came up in this discussion, I wonder if anyone would be open to the idea? If so I'll open a new issue and write up a proposal.

@kovidgoyal
Copy link
Owner

Sure, I know this bother's Egmont a lot, so it's worth discussing. It should be trivial to have an escape code that turn on and off "pending mode". In pending mode all received bytes would be buffered. You'd probably also need a max pending timeout and a max buffer size.

@egmontkob
Copy link

Sounds good.

@gnachman
Copy link

Link to spec and issue to host the discussion are here: https://gitlab.com/gnachman/iterm2/issues/6873

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

9 participants