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

Allow for empty CSS variable definitions #1849

Closed
rodrigogiraoserrao opened this issue Feb 21, 2023 · 6 comments · Fixed by #2984
Closed

Allow for empty CSS variable definitions #1849

rodrigogiraoserrao opened this issue Feb 21, 2023 · 6 comments · Fixed by #2984
Assignees
Labels
enhancement New feature or request Task

Comments

@rodrigogiraoserrao
Copy link
Contributor

Consider the following CSS snippets:

$x: 
* {
    background: red;
}
$x:
* {
    background: $x;
}
* {
    background: red;
}
$x:

All three should work with the following behaviour:

  • an empty variable definition is allowed; and
  • setting a style to an empty variable should unset that style.

For example, 2. should unset the background of all widgets.

As of now, 2. and 3. can't be parsed.
2. raises an error regarding an undefined variable and 3. raises an AttributeError inside substitute_references in parse.py.

@rodrigogiraoserrao rodrigogiraoserrao added enhancement New feature or request Task labels Feb 21, 2023
rodrigogiraoserrao added a commit that referenced this issue Feb 21, 2023
Checking if the token is not 'None' brings us a tiny step closer to fixing #1849, which still needs to ensure the variable definition is complete and valid, even if empty.
willmcgugan pushed a commit that referenced this issue Mar 1, 2023
* Helper file for error progress tracking.

* Fix typing of Keys.???.value.

* Assert that we have frame information.

@willmcgugan I went with the asserts because the tone of the code tells me that we are kind of certain that the current and previous frames always exist. Should the function be refactored to handle None for the previous frame (or the current & previous frames) by writing to the buffer without caller information, for example?

* Use inspect.Traceback instead of inspect.FrameInfo.

Looks like the two shared a lot of attributes, so we were trying to use FrameInfo but inspect.getframeinfo returns a Traceback, so that felt like the correct type to use.

* Update after installing msgpack-types.

* Assert we have frame info.

Same fix as in b709219.

* Fix MapGeometry order information typing.

There was another alternative solution, which was to just flatten everything entirely, so that the code actually obeyed the comments.
After all, the comments for the attribute `order` in the definition of `MapGeometry` said that `order` was a tuple of integers defining the painting order, which was not true because the very first widget was having its order defined as `order = ((0,),)` instead of `order = (0,)`.
Thus, another possible solution would be to retype `order` as `tuple[int, ...]` and make everything flat, but I opted for the “tuple of tuples” approach because the sub-tuples will highlight the nested structure of the DOM hierarchy.

* Fix import and typing for fromkeys.

* Assert app is not None.

[skip cli]

* Import missing type.

* Use CallbackType for event Callback.

* Remove variable name clash.

* Ensure ScalarAnimation only receives widgets.

Two other alternatives would be:
 - leave typing of 'widget: DOMNode' and then assert 'widget' is actually of type 'Widget', which works just fine but looks weird in the sense that we type a parameter in one way but then only manage to do any work if it is of another type;
 - type it as 'widget: DOMNode | Widget' and set 'size' to something other than 'widget.outer_size' if 'widget' is a 'DOMNode'.

* Count spacing values as 1 for int instance.

Adding an 'assert not isinstance(spacing, int)' before raising the error sounds reasonable, because 'Spacing.unpack' seems to be able to handle a lone integer just fine, but I decided against it because I would not like to raise an assertion error from within an exception handling inside Textual.
So, to keep it on the safer side, I went with the conditional expression that checks if we have an integer or a tuple.

* Use correct default.

The obvious fix would be to do 'default_factory=RulesMap' but mypy will infer that the type of 'RulesMap' is 'type[RulesMap]' and will not see it as a 'Callable[[], RulesMap]'. That could be fixed by using 'cast'. I decided to use 'RulesMap.__call__'.

[skip ci]

* Use correct abstract methods.

We fix the LSP violation by using the abstract methods that the ABC already provides... Which is a shame, because I thought I was about to write my first Protocol.

* Add missing annotation.

* Fix type inference.

* Check token is not None.

* Revert "Check token is not None."

This reverts commit 0ae4633.
Upon closer look, this is not the correct fix.

* Check that token is not None.

Checking if the token is not 'None' brings us a tiny step closer to fixing #1849, which still needs to ensure the variable definition is complete and valid, even if empty.

* Type DOMQuery instantiation correctly.

After some fiddling, some crying, and talking to Dave and Will, we got to a partial solution.
I cried a bit more and came up with the fix that entailed lifting 'ExpectType' to outside of 'DOMNode'.
Then, I realised 'ExpectType' and 'QueryType' from 'query.py' were essentially the same thing so I decided to only make use of 'QueryType'.

* Infer correct type while walking.

* Cast to remove doubt about None.

mypy can't infer that if after is None, then before won't.

* Explicitly type variable.

* Cast to console renderable.

@willmcgugan did I understand correctly that this 'cast' is exactly what renderable being possibly a 'RichCast' asks me to do..? To be honest, I was not 100% sure after reading rich's documentation for 'RichCast' and after reading the source of 'rich_cast'.

* Type variable to remove literal ambiguity.

mypy was inferring the type of the empty string as a literal and thus was not type checking the fact that 'render' would either be a string or Text.

* Assert scrollbars always have parents.

* Make scrollbar scroll actions synchronous.

The scrollbars were posting the messages and awaiting for them but that's not what Widget does, which handles scrolling synchronously. Thus, I made them synchronous methods by using 'post_message_no_wait'.

* Use link only when available.

* Update errors.

* Relax type inference.

* Ignore missing imports for uvloop.

'uvloop' is completely optional and this code only exists to cater for the case where the user _already_ has uvloop installed.
With that in mind, it makes sense to silence errors about uvloop not being available because we don't need to know about that.
We only care if uvloop happens to be installed.

* Enable variable reuse.

* Fix type issues in easing.py.

* Fix typing issues in demo.

Fixing the typing issues here involved making use of the messaging features to replace a method (wasn't exactly wrong, but this is more in line with the Textual way of doing things, which should be prevalent in the demo of the framework).
We also dealt with a typing issue in DarkSwitch.on_dark_change by deleting an unnecessary parameter, but the underlying problem is unsolved and was logged as issue #1865.

Related issues: #1865.

* Fix typing issues in _doc.py.

* Type actions with a type alias.

* Make return values optional.

As per discussion with @darrenburns.

* Make StringKey idempotent.

This set up is so that we can easily make sure that a variable of the type str | SK (where SK is a subclass of StringKey) becomes of the type SK.
Instead of having to write a conditional expression to only convert in case of a string, we make SK idempotent so that we can just call it on the value that may still be a string.

* Make sorting type safe.

* Fix typing of StringKey.__new__.

This is needed to ensure the type checkers know the exact type of the instances returned when instantiating subclasses of StringKey.

* Add explicit type variables.

* Type-safe line rendering.

* Type safe _render_line_in_row.

* Type safe _render_cell.

* Type safe ordered_rows property.

* Type safe ordered_columns property.

* Simplify handling of Nones in TwoWayDict.

In the beginning of the work on this PR, mypy flagged two issues in the implementation of TwoWayDict, which would return None when the keys/values were not available but the signatures of get/get_key did not have the missing '| None'.
When I added the '| None' to the return values of TwoWayDict.get and TwoWayDict.get_key, many new errors popped up because the implementation of DataTable assumes, in many different places, that those methods return the exact thing that we asked for, and not None.

To fix this, I started going over the call sites for get/get_key and started adding checks for 'None' and raising the appropriate RowDoesNotExist/ColumnDoesNotExist error.

At some point, I realised that went a little bit against the semantics of the code, which pretty much assumes those accesses WILL work.

So, I subclassed TwoWayDict to specialise it even further to this use case.
The subclass is meant to work with RowKey/ColumnKey as the underlying keys and it will automatically raise an error if the access fails.

CC: @darrenburns.

* Make use of idempotency of StringKey subclasses.

* Make message aware of type of sender.

* Only select when possible.

* Fix typing of reactive attribute.

* Reset cursor upon moving action if needed.

* Assure mypy we have ListItems.

This could be improved (as in, cast wouldn't be needed) if #1871 is resolved favourably.

* Add explicit return.

* Ignore argument types to watch.

Related issues: #1865.

* Type safe App._register.

* Redirect output to void.

* Remove helper file.

* Fix Python compat. issues.

* Button can only accept str/Text as label.

Fixes: #1870.

* Add runtime type check for list items.

This change follows from a discussion in issue #1871.

* Address review comments.

* Revert "Fix typing issues in demo."

This reverts commit f366783.

* Address review comments.

Related comments: #1831

* Add clarifying comment.

See: #1831

* Revert changes to data table.
@rodrigogiraoserrao
Copy link
Contributor Author

(This issue is reserved for the EuroPython 2023 sprint. If you are not participating in the sprint, please refrain from working on this issue. Thanks!)

This isn't a trivial fix but it also isn't an impossible one.
If you like puzzles, this might be a good one for you!
Fixing this will likely require toying with the CSS parser in src/textual/css/parser.py.

Good luck and have fun!

@zormit
Copy link
Contributor

zormit commented Jul 22, 2023

We'll have a look (@eliasdorneles and I). We're sitting in RB116@EuroPython Sprints.

@rodrigogiraoserrao
Copy link
Contributor Author

Sure thing. Feel free to join us at 103 if you want.

@eliasdorneles
Copy link

eliasdorneles commented Jul 22, 2023

I've noticed that this also breaks, with the same error as 2 (undefined variable):

* {
    background: $x;
}

$x: red;

@rodrigogiraoserrao is this expected? do the CSS variables need to be declared before they're used?

@rodrigogiraoserrao
Copy link
Contributor Author

I'm not sure if we want to have the ability of using a variable before defining it, but it doesn't shock me that you have to define it before using it.
So, I'd say to worry about the other cases first and then this could come after.

What do you think?

zormit added a commit to zormit/textual that referenced this issue Jul 22, 2023
@zormit zormit mentioned this issue Jul 22, 2023
3 tasks
zormit added a commit to zormit/textual that referenced this issue Jul 22, 2023
@github-actions
Copy link

Don't forget to star the repository!

Follow @textualizeio for Textual updates.

seifertm pushed a commit to seifertm/textual that referenced this issue Jul 22, 2023
seifertm pushed a commit to seifertm/textual that referenced this issue Jul 22, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request Task
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants