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

[Merged by Bors] - feat(meta/widget): Add css classes for indentation as required by group and nest. #3764

Closed
wants to merge 8 commits into from

Conversation

DanielFabian
Copy link
Collaborator

@DanielFabian DanielFabian commented Aug 13, 2020

this is a transplant of leanprover-community/lean#439

the relevant css section looks more or less like this:

        .indent-code {
            text-indent: calc(var(--indent-level) * -1ch);
            padding-left: calc(var(--indent-level) * 1ch);
        }

For details, one can play around here: https://jsfiddle.net/xbzhL60m/45/

pure [h "span" [cn "indent-code", style [("--indent-level", to_string i)]] inner]
| ca (sf.highlight c a) := do
inner ← view ca a,
pure [h "span" [cn $ "highlight_" ++ c.to_string] inner]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you try replacing [cn $ "highlight_" ++ c.to_string] with [cn c.to_string]? There are already some CSS classes which style particular colours.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess a problem is that you want the highlighting colours to be theme-dependent so that they are readable for both light and dark themes.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW, would it be possible to inline the .indent-code class for now (i.e. use style instead of cn)? Then we could test it without modifying the extension.

Unfortunately not. We need to change the CSS in the extension in such a way, that the whole subgoals become display:inline-block as opposed to display:inline what they are now. If you do it only on one tag, it completely moves stuff around and it's not even in the same order anymore.

I was in contact with @EdAyers about this and understood, that we can take a look at the vscode extension shortly, this is mostly preparatory work, really.

src/tactic/interactive_expr.lean Show resolved Hide resolved
Copy link
Member

@gebner gebner left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Maybe @EdAyers wants to look at this as well (there is a demo webpage linked from the PR in the lean repo).

bors d=EdAyers

pure [h "span" [cn "indent-code", style [("--indent-level", to_string i)]] inner]
| ca (sf.highlight c a) := do
inner ← view ca a,
pure [h "span" [cn $ "highlight_" ++ c.to_string] inner]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should probably be translated to one of the classes like hot-pink that are already in tachyon.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather not do that. Because it encodes a hard mapping between mathlib's source code and the plugin, whereas the CSS class is conceptually much more flexible. Plus highlighting might also be encoded with other means than color like italics or bold. From an accessibility point-of-view, I'd rather have the CSS handle that, if it's ok.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Everything about the lean widget system already assumes a hard mapping, namely it assumes that a CSS library called Tachyons is being used. So any styling choices should be made in the lean code not in a separate CSS file (I admit indent-level is an exception). I don't think it makes sense to have color.red mean anything other than colouring the text red.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess it's awkward for people who have a kind of colourblindness where they can't distinguish certain colours from black or white (idk if that's possible). I don't think we will have cases where it is necessary for the user to be able to distinguish the highlight colours to understand the format string.

src/tactic/interactive_expr.lean Show resolved Hide resolved
src/tactic/interactive_expr.lean Outdated Show resolved Hide resolved
@bors
Copy link

bors bot commented Aug 13, 2020

✌️ EdAyers can now approve this pull request. To approve and merge a pull request, simply reply with bors r+. More detailed instructions are available here.

@gebner
Copy link
Member

gebner commented Aug 13, 2020

BTW, would it be possible to inline the .indent-code class for now (i.e. use style instead of cn)? Then we could test it without modifying the extension.

@EdAyers
Copy link
Member

EdAyers commented Aug 13, 2020

We should hold merging this until the indent-code class is in vscode repo.

BTW, would it be possible to inline the .indent-code class for now (i.e. use style instead of cn)? Then we could test it without modifying the extension.

Maybe... I'm not sure if React styles supports these CSS variable calculations yet. Another approach is to use the customCSS vscode setting.

"lean.infoViewStyle": ".indent-code { text-indent: calc(var(--indent-level) * -1ch); padding-left: calc(var(--indent-level) * 1ch);}"

@EdAyers
Copy link
Member

EdAyers commented Aug 13, 2020

Doesn't seem to be working quite right for me:
image

You can see that it is adding an indent before the [ even though a newline wasn't inserted. It also cancels the padding of the expressions inside the tooltip and adds some unwanted indents to the explicit args list/

@EdAyers
Copy link
Member

EdAyers commented Aug 13, 2020

How did you get your output in the jsfiddle to have newlines non-breaking spaces?

@DanielFabian
Copy link
Collaborator Author

you need all elements to be display:inline-block. Then it behaves as you would want. Also you nee the text-indent to be negative, whilst the padding-left is positive.

@EdAyers
Copy link
Member

EdAyers commented Aug 13, 2020

I think not all elements need to be display:inline-block, just the one which has the block.

Added `dib` to render function for `sf.block`.
@DanielFabian
Copy link
Collaborator Author

could you share the HTML you used to draw this? I think I had similar kinds of artifacts previously, too

@EdAyers
Copy link
Member

EdAyers commented Aug 13, 2020

Sorry for deleting previous comment. Here is a simpler example of the problem:
image

@DanielFabian
Copy link
Collaborator Author

Ah, yes, I had exactly these kinds of artifacts, until everything, including \|- was inline-block.

@EdAyers
Copy link
Member

EdAyers commented Aug 13, 2020

The inner inline block is linebreaking prematurely, I guess because the width linebreak calculation is taking in to account the indentation / padding wrong. It's not in the CSS standard yet but in the future it should be possible to use text-indent: 4ch hanging instead: https://developer.mozilla.org/en-US/docs/Web/CSS/text-indent

@DanielFabian
Copy link
Collaborator Author

The problem with text-indent is, that it's being ignored for inline elements. That's why I had to make inline-block. And I wouldn't be too worried about using fairly new features as what we're running is essentially chrome, I believe.

@EdAyers
Copy link
Member

EdAyers commented Aug 13, 2020

hanging is not implemented in chrome yet

@DanielFabian
Copy link
Collaborator Author

How did you get your output in the jsfiddle to have newlines non-breaking spaces?

non-breaking spaces I get by using   instead of ' '. Ideally, we get from the format string, where there is a format.line. In these cases, we'd use ' ' and   everywhere else.

@gebner
Copy link
Member

gebner commented Aug 13, 2020

I just played around with the jsfiddle a bit. The --indent-level variable is completely unnecessary, right? We could just use [style "text-indent" "-1ch", style "padding-left" "1ch"] instead (substitute 1 by the nesting level, but everything uses 1 afaict).

@DanielFabian
Copy link
Collaborator Author

yes, in principle, The main idea was about not hard-coding that 1ch, but rather have css decide if 1ch is the right amount. On the other hand, from lean we do get the intent that this is supposed to be indented by 1 unit, so I just thought, we can let css handle the concrete amount by how much. But yes, we could also hard-code 1ch.

@DanielFabian
Copy link
Collaborator Author

Having the --indent-level just meant that we only have one parametric class vs. one class per indentation.

@gebner
Copy link
Member

gebner commented Aug 13, 2020

I think the weird rendering is due to directly nested indent-code elements:
https://jsfiddle.net/5jdshv0a/

@gebner
Copy link
Member

gebner commented Aug 13, 2020

Some experience reports from the documentation PR:

  1. If you don't directly nest these spans, the rendering is reliable.
  2. I only needed a single class, no variables.
  3. It seems to be a bit of a black art how to translate group and nest into spans, because they have slightly different semantics. If you use group, then I found that it will break before the comma in ∀ (x : α), x = x. This is really ugly. So I've tried only using nest instead. This mostly works fine, except that ∀ (x : α) (y : α) (z : α), x < y ∧ y < z can be broken like this:
∀ (x : α) (y :
    α) (z : α),
  x < y ∧ y < z

(example here: https://gebner.github.io/mathlib_docs/control/traversable/basic.html#is_lawful_traversable)

leanprover-community/doc-gen#47

@DanielFabian
Copy link
Collaborator Author

one thing we could do, is make the space after : an &nbsp;, you probably don't want to ever break after the colon.

@gebner
Copy link
Member

gebner commented Aug 13, 2020

But wouldn't you then get this?

∀ (x : α) (y
    : α) (z : α),
  x < y ∧ y < z

Ideally we'd have a span around (y : α).

@DanielFabian
Copy link
Collaborator Author

not span, because span has no meaning. What we really want is a non-breaking space. But the point is, the format from lean already has that info. It has a notion of spaces and lines. Whereas a line is something that can break, and a space cannot. So we should carry that info from C++ into the eformat, ideally. Because then we can encode breaking and non-breaking spaces correctly.

But just a span does not prevent breaking.

@gebner
Copy link
Member

gebner commented Aug 14, 2020

Regarding the wider discussion points:

First of all, I don't think there is a way to faithfully support the current format API in CSS. There is a huge semantic gap between format.group and display: inline-block. Concretely, format.group prints newlines as newlines if and only if the contents do not fit on the current line. I don't see any way to simulate this behavior in CSS.

Therefore, I believe we either need quick-to-implement hacky heuristics, or some extension to the current format code like an explicit new format.html_group constructor. (A third option would be to simulate the formatting using javascript.)

Extending the current format code (and pretty-printer) is a significant undertaking, though. May I remind you that the days of Lean 3 are numbered, and we will soon pay allegiance to the Clover. On the other hand, this could be an experiment that makes it into Lean 4, I don't know. The CSS-based formatting is less powerful (and hence uglier) than the Wadler-Leijen pretty-printer, so we can't get rid of the "old" one.

Maybe it is enough to distinguish between format.line and format.space, as you mentioned. This would be interesting (and also a bit of work to find out), but I'm sceptical.

If we start building custom heuristics, then on one hand, we'll diverge from the current pretty-printer very quickly.

I don't think the Lean 3 pretty printer is ever going to change anymore.

On the other hand, format was really meant to be the thing that would tell how things ought to be rendered. It does contain information about where line breaks are allowed and it contains the information by how much to indent.

The information in the format object is not a pretty-printer-agnostic description of how the text should be rendered. It has a precise semantics (see the Wadler paper), and we tune the format objects so that the one implementation we have prints it nicely.

Additionally, the format object also contains one more piece of information, which is at least as important as the two you mentioned. Namely the group information, which tells us that several line breaks can only be inserted together.

I'd really suggest just writing a new pretty-printer from scratch. It's not that hard and it could be more closely aligned to what HTML can and cannot do.

I wouldn't underestimate the effort here. You'd also need a lot of API that is only accessible from C++ at the moment (like notations, etc.).

@DanielFabian
Copy link
Collaborator Author

Fair enough, just one last comment. The behaviour or introducing line breaks if and only if the content doesn't fit is very much what a browser does all the time. In fact, it does it, whenever you don't disallow line breaks. Hence the distinction between ' ' and &nbsp;. For all other points, I get where you're coming from.

@DanielFabian
Copy link
Collaborator Author

Ah gotcha

@EdAyers
Copy link
Member

EdAyers commented Aug 21, 2020

Yeah this is way more difficult to implement than I was hoping it would be. When I originally wrote tagged_format I was just kinda hoping there would be a clever CSS trick that would be good enough, and Daniel almost got that working but there are still cases where it doesn't work properly because of some annoying inaccessible linebreaking logic. Lean 4 also makes me think that an expedient solution to the main cases should be preferred to an all-singing all-dancing pp. Since we have information about expression boundaries, perhaps there could be a button where you can collapse subexpressions or demand that the args for a given function be rendered one argument per line with an indent.

@EdAyers
Copy link
Member

EdAyers commented Aug 21, 2020

Suppose that the interactive_expr.mk routine knew the width of the viewport in characters, how hard would it be then to write some lean code that formatted overflow on a newline with an indent?

@EdAyers
Copy link
Member

EdAyers commented Aug 21, 2020

Would something like 2021706 work?

@EdAyers
Copy link
Member

EdAyers commented Aug 21, 2020

It tracks the length of the current line with a counter and then wraps the rest of the expr in a div if it overflows a fixed column count. It looks ugly rn but as a POC is it the right approach?

@EdAyers
Copy link
Member

EdAyers commented Aug 21, 2020

Eg:
image

But you could imagine tweaking the algo so that it is a bit more sensible than this.

@gebner
Copy link
Member

gebner commented Aug 21, 2020

@EdAyers If you have the screen width available, then you should just implement the formatting algorithm that format uses. See the Wadler paper for the classic introduction. I believe @digama0 has a Lean version of format lying around that could be reused.

The "insert newline on overflow" approach is pretty much was the CSS version does, AFAICT. I don't think we need to reimplement that in Lean. The heuristics used in the mathlib documentation seem to work mostly fine (maybe the trailing closing parentheses could be cleaned up though).

@EdAyers
Copy link
Member

EdAyers commented Aug 21, 2020

Right so could we just set the screen width to be a constant for now?

@gebner
Copy link
Member

gebner commented Aug 21, 2020

To be honest, I would prefer even the current slightly buggy version of this PR over a fixed screen width.

@DanielFabian
Copy link
Collaborator Author

I think getting the screen width from JavaScript would in principle enable us to use the correct format code in lean. Then you could have an adjustable Wadler-based pretty printer. We probably could even back-port it from lean 4 in the worst case.

But without it, I too think the current CSS-based version is a better trade-off.

@EdAyers
Copy link
Member

EdAyers commented Aug 29, 2020

I have a question, did the Wadler formatter ever work in the vscode extension?

@EdAyers
Copy link
Member

EdAyers commented Aug 30, 2020

Looks mostly good to me now. Thanks @gebner for sorting that out, much easier than implementing a formatter!
Sometimes there are still some issues in the type tooltip if there is a really big argument, it will add linebreaks. So the quick fix there is to allow one to turn off the indenting system.
image

@EdAyers
Copy link
Member

EdAyers commented Aug 30, 2020

Let's save the fix for above for another PR

bors r+

@bors
Copy link

bors bot commented Aug 30, 2020

👎 Rejected by label

@EdAyers EdAyers removed the WIP Work in progress label Aug 30, 2020
@EdAyers
Copy link
Member

EdAyers commented Aug 30, 2020

bors r+

bors bot pushed a commit that referenced this pull request Aug 30, 2020
…up and nest. (#3764)

this is a transplant of leanprover-community/lean#439

the relevant css section looks more or less like this:
```css
        .indent-code {
            text-indent: calc(var(--indent-level) * -1ch);
            padding-left: calc(var(--indent-level) * 1ch);
        }
```

For details, one can play around here: https://jsfiddle.net/xbzhL60m/45/


Co-authored-by: Ed Ayers <EdAyers@users.noreply.github.com>
Co-authored-by: Gabriel Ebner <gebner@gebner.org>
Co-authored-by: E.W.Ayers <E.W.Ayers@maths.cam.ac.uk>
@bors
Copy link

bors bot commented Aug 30, 2020

Pull request successfully merged into master.

Build succeeded:

@bors bors bot changed the title feat(meta/widget): Add css classes for indentation as required by group and nest. [Merged by Bors] - feat(meta/widget): Add css classes for indentation as required by group and nest. Aug 30, 2020
@bors bors bot closed this Aug 30, 2020
@bors bors bot deleted the pp-widgets branch August 30, 2020 13:14
robertylewis pushed a commit that referenced this pull request Aug 31, 2020
…up and nest. (#3764)

this is a transplant of leanprover-community/lean#439

the relevant css section looks more or less like this:
```css
        .indent-code {
            text-indent: calc(var(--indent-level) * -1ch);
            padding-left: calc(var(--indent-level) * 1ch);
        }
```

For details, one can play around here: https://jsfiddle.net/xbzhL60m/45/


Co-authored-by: Ed Ayers <EdAyers@users.noreply.github.com>
Co-authored-by: Gabriel Ebner <gebner@gebner.org>
Co-authored-by: E.W.Ayers <E.W.Ayers@maths.cam.ac.uk>
PatrickMassot pushed a commit that referenced this pull request Sep 8, 2020
…up and nest. (#3764)

this is a transplant of leanprover-community/lean#439

the relevant css section looks more or less like this:
```css
        .indent-code {
            text-indent: calc(var(--indent-level) * -1ch);
            padding-left: calc(var(--indent-level) * 1ch);
        }
```

For details, one can play around here: https://jsfiddle.net/xbzhL60m/45/


Co-authored-by: Ed Ayers <EdAyers@users.noreply.github.com>
Co-authored-by: Gabriel Ebner <gebner@gebner.org>
Co-authored-by: E.W.Ayers <E.W.Ayers@maths.cam.ac.uk>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants