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

Make `edit:styled` more high-level and allow nesting #520

Open
xiaq opened this Issue Dec 1, 2017 · 8 comments

Comments

Projects
None yet
4 participants
@xiaq
Member

xiaq commented Dec 1, 2017

Right now, edit:styled is tied to the terminal: There is a one-to-one relationship (code) mapping the supported styles to ANSI color sequences ("\e[1;31m", where 1;31 can be any semicolon-separated list of numbers), and it is possible to specify a number (or multiple numbers separated by semicolons) as the "style".

This is an improvement over writing ANSI color sequences by hand, but it still has a few drawbacks:

  1. It is tied to the terminal. In future, Elvish will support a web frontend where ANSI color sequences are not directly supported. It is possible to parse and convert them to CSS, but that is backwards. If we start with a high-level internal format, it can then support multiple presentations, including both ANSI color sequences and CSS.

  2. It does not support nesting. For instance, the ANSI color sequence for reverse video is "\e[7m". Two inverses cancel each other: so echo (edit:styled (edit:styled lorem inverse) inverse) should print lorem in normal style. However, without knowing the semantics of those different styles, it is not possible to compose them. Again, we can hack composing into the current implementation, but it is cleaner to do it from the source by having a semantics-based internal format.

Another minor thing is that since this function is useful outside editors, it is appropriate to put it in the builtin namespace, making the constructor styled.

Current design is as follows:

  1. A styled text is internally a list of styled segments; a segment is a string, combined with several attributes: fg-color, bg-color, inverse, bold, etc. It is possible to construct a segment with styled-segment, specifying the attributes as options, e.g. styled-segment lorem &fg-color=red &bold=$true. If the argument is already a styled segment, styled-segment copies it and modifies the specified attributes. Attributes of styled segments can be accessed like map elements, e.g. $seg[bold].

  2. It is possible to concatenate plain strings, styled segments and styled texts together: they will form a styled text that combines them, e.g. echo (styled-segment lorem &fg-color=red)(styled-segment ipsum &inverse=$true)foobar. No implicit whitespace is inserted between concatenated elements.

  3. It is possible to apply style transformers to a piece of styled text. This uses the styled command, which accepts a plain string, a styled segment or styled text, and apply the transformer to all segments: e.g. echo (styled (styled-segment lorem &fg-color=red)" "(styled-segment ipsum &inverse=$true) inverse). The inverse transformer toggles the inverse attribute, so that the result will be a red-and-inversed lorem, an inverse space, and and a normal ipsum.

    A style transformer is simply a function that takes a styled segment and outputs another styled segment. For instance, the inverse transformer can be implemented as [seg]{ styled-segment $seg &inverse=(not $seg[inverse]) }. The mapping from the name to the implementation is kept in $styled-transformer. The default value implements all the styles supported by the current edit:styled implementation, like bold, blue, bg-blue.

    In practice, this means that the user never has to use styled-segment, and the API of styled is backwards compatible with the current edit:styled.

@zzamboni

This comment has been minimized.

Contributor

zzamboni commented Dec 2, 2017

Very nice!

@zzamboni

This comment has been minimized.

Contributor

zzamboni commented May 29, 2018

The new built-ins as iplemented in #674 are awesome. A couple of comments from my initial testing:

  • Numeric color codes are not supported. I strongly think this should be added, as it makes some modules much easier to code (and there's some code already depending on this capability, e.g. -session-color
  • &inverse doesn't seem to be toggling, even with &force-no-inverse. Not sure if I got the usage right:
[~]─> echo (styled (styled-segment foo &inverse))
foo << Inverted
[~]─> echo (styled (styled-segment (styled-segment foo &inverse) &inverse))
foo << Still inverted
[~]─> echo (styled (styled-segment (styled-segment foo &inverse) &force-no-inverse))
foo << Still inverted
@fehnomenal

This comment has been minimized.

Contributor

fehnomenal commented May 29, 2018

  1. I think numeric color codes are an implementation detail of how the colors are displayed in the shell and so it contradicts a bit the purpose to abstract from the shell coloring.
    Problematic is that in the new implementation currently only supports the 16 named colors...
    As a side note: you could convert -session-color to a style transformer that takes a styled segment and returns a new segment (possibly unmodified). Your -colorized could then be like this:

    fn -colorized [what color]{
      if $bold-prompt {
        what = (styled $what bold)
      }
      if (eq $color session) {
        styled $what $-session-color~
      } else {
        styled $what $color
      }
    }
    
  2. You don't need to build a styled segment by yourself. Probably only authors of style transformers should use it.
    styled-segment only supports options that correspond to foreground/background colors and the individual attributes: fg-color, bg-color, bold, dim, italic, underlined, blink and inverse. Valid values are colors strings for the first two and boolean values for the remaining options. Probably there should be a validation about the supplied options.
    styled accepts an arbitrary list of style transformers after the object to style. A style transformer is either:

    • a string that names one of the builtin transformers:
      • a valid color string (one of the 16 named ones) optionally prepended by bg- for setting the background color
      • a string matching the regex (no-|toggle-)?(bold|dim|italic|underlined|blink|inverse)
    • a lambda accepting a styled segment and returning a styled segment
    • a function (via the $function~ syntax) with the same properties as the lambda

    Example usage with inverse:

     echo (styled foo inverse) # is inverse
     echo (styled foo toggle-inverse) # is also inverse
     echo (styled foo inverse toggle-inverse) # not inverse
     echo (styled foo toggle-inverse no-inverse) # also not inverse
    

When I get around it I will write documentation about the two new builtins.

@iwoloschin

This comment has been minimized.

Contributor

iwoloschin commented May 29, 2018

If the end goal is to make colors appear the same in the terminal and via CSS it may make sense to not allow raw numbers, but instead use color names. Unfortunately the color namespace is kind of broken:

https://en.wikipedia.org/wiki/X11_color_names#Clashes_between_web_and_X11_colors_in_the_CSS_color_scheme

@zzamboni

This comment has been minimized.

Contributor

zzamboni commented May 29, 2018

@fehnomenal thanks for your response (and of course, for the implementation of styled). Some comments to your responses:

  1. I agree that from the abstraction point of view, allowing numeric color codes is not ideal. However, in practice most terminals use ANSI color codes, and ultimately any named colors are converted to a numeric specification for displaying them. If the user understands that using numeric codes may break portability, I think we should be able to do it. In my example, -session-color calculates a color based on the current PID. How could this be implemented using named colors? Even implementing a random color selector would be far more complicated - e.g. choosing a random element from a list of all known colors (which have to be encoded) vs simply choosing a random number in a certain range.

  2. Thanks a lot for the explanation! This really helps a lot in understanding how it works. Good stuff :)

@fehnomenal

This comment has been minimized.

Contributor

fehnomenal commented May 29, 2018

As there are currently only 16 valid colors I would put their names into a list and index into it: styled foo $colors[(% $pid (count $colors))]. Of course this will only result in 16 different colors but you could also include the bg- variants.
Probably the end goal should be to internally use color.Color, map color names to predefined values and let the displaying component choose how to render individual colors.

@zzamboni

This comment has been minimized.

Contributor

zzamboni commented May 29, 2018

@fehnomenal

This comment has been minimized.

Contributor

fehnomenal commented May 30, 2018

I am already working on it but there are some things needing discussion. I will soon open a new issue where we can focus on adding support for more colors.

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