Repository: Textualize/textual

Issue:
Type selectors can't contain a number
It seems that type selectors can't handle types that have a number in their name. For example this:

```python
from textual.app import App, ComposeResult
from textual.widgets import Label

class H1( Label ):
    pass

class NumberError( App[ None ] ):

    CSS = """
    H1 {
        text-style: bold;
        color: yellow;
        background: red;
    }
    """
    def compose( self ) -> ComposeResult:
        yield H1( "This is a very important heading" )

if __name__ == "__main__":
    NumberError().run()
```

throws this error:

```
Error in stylesheet:
 /Users/davep/develop/python/textual-sandbox/not_all_types.py:NumberError:1:5
╭───────────────────────────────────────────────────────────────────────────────────────────╮
│ ❱ 1 │                                                                                     │
│   2 │   H1 {                                                                              │
│   3 │   │   text-style: bold;                                                             │
╰───────────────────────────────────────────────────────────────────────────────────────────╯
 • Expected one of 'combinator child', 'comment start', 'declaration set start', 'new
 selector', 'pseudo class', 'selector', 'selector class', 'selector id', 'selector
 universal', or 'whitespace'.
 • Did you forget a semicolon at the end of a line?
```

whereas this works fine:

```python
from textual.app import App, ComposeResult
from textual.widgets import Label

class HOne( Label ):
    pass

class NumberError( App[ None ] ):

    CSS = """
    HOne {
        text-style: bold;
        color: yellow;
        background: red;
    }
    """
    def compose( self ) -> ComposeResult:
        yield HOne( "This is a very important heading" )

if __name__ == "__main__":
    NumberError().run()
```

as does, of course, this:

```python
from textual.app import App, ComposeResult
from textual.widgets import Label

class H1( Label ):
    pass

class NumberError( App[ None ] ):

    CSS = """
    Label {
        text-style: bold;
        color: yellow;
        background: red;
    }
    """
    def compose( self ) -> ComposeResult:
        yield H1( "This is a very important heading" )

if __name__ == "__main__":
    NumberError().run()
```


Discussion:
Probably requires a tweak to the regex. It should at least allow any valid Python identifier.

Resolving patch:
diff --git a/src/textual/css/tokenize.py b/src/textual/css/tokenize.py
--- a/src/textual/css/tokenize.py
+++ b/src/textual/css/tokenize.py
@@ -49,7 +49,7 @@
     selector_start_id=r"\#" + IDENTIFIER,
     selector_start_class=r"\." + IDENTIFIER,
     selector_start_universal=r"\*",
-    selector_start=r"[a-zA-Z_\-]+",
+    selector_start=IDENTIFIER,
     variable_name=rf"{VARIABLE_REF}:",
 ).expect_eof(True)
 


Test patch:
diff --git a/tests/test_query.py b/tests/test_query.py
--- a/tests/test_query.py
+++ b/tests/test_query.py
@@ -8,12 +8,15 @@ def test_query():
     class View(Widget):
         pass
 
+    class View2(View):
+        pass
+
     class App(Widget):
         pass
 
     app = App()
     main_view = View(id="main")
-    help_view = View(id="help")
+    help_view = View2(id="help")
     app._add_child(main_view)
     app._add_child(help_view)
 
@@ -53,6 +56,7 @@ class App(Widget):
         assert list(app.query("App")) == [app]
         assert list(app.query("#main")) == [main_view]
         assert list(app.query("View#main")) == [main_view]
+        assert list(app.query("View2#help")) == [help_view]
         assert list(app.query("#widget1")) == [widget1]
         assert list(app.query("#Widget1")) == [] # Note case.
         assert list(app.query("#widget2")) == [widget2]