fusesource / jansi

Jansi is a small java library that allows you to use ANSI escape sequences to format your console output which works even on windows.
http://fusesource.github.io/jansi/
Apache License 2.0
1.11k stars 140 forks source link

Provide DEC sequences for save & restore cursor position operations #226

Closed pmonks closed 11 months ago

pmonks commented 2 years ago

Background

The escape sequences for "save cursor position" and "restore cursor position" were never standardised as part of the ANSI (or subsequent) specs, resulting in two different sequences known in some circles as "DEC" and "SCO":

Different terminals (and OSes) support different combinations of these sequences (one, the other, neither or both); for example the iTerm2 terminal on macOS supports both, while the built-in macOS Terminal.app only supports the DEC sequences.

Problem

From the source I see that jansi's saveCursorPosition and restoreCursorPosition methods use the SCO sequences for these operations, meaning that they won't work by default on some terminals (including, notably, macOS' built-in Terminal.app).

Proposed Solution

Add support for both variants, so that jansi consumers can determine the correct one to use (itself a thorny problem, but one that feels appropriate to punt out of the library).

gnodet commented 2 years ago

What's your use case exactly ? It's a bad idea to depend on those cursor movements if the terminal can not use it and Jansi is somewhat limited when it comes to mimic ansi sequences. The original goal is to allow rendering colors and strip them on unsupported terminal and emulate on windows. Anything more advanced should really use a better suited library and I would recommend looking at JLine3 (on top of jansi) which has full terminal support.

pmonks commented 2 years ago

The project is here, but in a nutshell the use case is a text-mode "spinner" for long-running command line processes, which involves these steps:

  1. save the current cursor position (since the cursor could be anywhere on the screen, depending on what has already been written to it - this is nondeterministic)
  2. write some text (the next "frame" in the set of frames making up the spinner animation)
  3. short sleep (default is 250ms)
  4. restore the cursor position saved in step 1
  5. erase to end of line

This is done in a loop that spins through the frames in the caller's chosen spinner animation.

From a quick evaluation it appears that JLine3 is unsuitable for my use case given that:

And just to be crystal clear, I'm not asking for termcap/terminfo capabilities. I personally think that's well beyond the scope of jansi. Instead I'm simply asking for two new APIs that will (unconditionally) emit the DEC sequences for cursor save & restore when called (while maintaining the existing saveCursorPosition and restoreCursorPosition APIs that emit the SCO sequences). Leaving terminal capability detection to the jansi client (in this case my library) is absolutely fine by me (in fact I encourage that).

pmonks commented 2 years ago

Here's my workaround, which I've tested in various terminal emulators on both macOS and Linux.

As previously mentioned, it would be ideal if jansi added new APIs to emit the DEC sequences for save cursor position and restore cursor position, as it's highly implausible that my library is the only one that has run into this problem.

gnodet commented 11 months ago

@pmonks would the #262 PR fix your problem ?

pmonks commented 11 months ago

@gnodet I believe it would - that's the approach I took in my own code. However there may be folks who need finer-grained control over which sequences(s) are sent, so I'd probably suggest splitting this into multiple methods, perhaps something like:

    public Ansi saveCursorPosition() {
        saveCursorPositionSCO();
        return saveCursorPositionDEC();
    }

    // SCO command
    public Ansi saveCursorPositionSCO() {
        return appendEscapeSequence('s');
    }

    // DEC command
    public Ansi saveCursorPositionDEC {
        builder.append(FIRST_ESC_CHAR);
        builder.append('7');
        return this;
    }

    public Ansi restoreCursorPosition() {
        restoreCursorPositionSCO();
        return restoreCursorPositionDEC();
    }

    // SCO command
    public Ansi restoreCursorPositionSCO() {
        return appendEscapeSequence('u');
    }

    // DEC command
    public Ansi restoreCursorPositionDEC() {
        builder.append(FIRST_ESC_CHAR);
        builder.append('8');
        return this;
    }