11from __future__ import annotations
22
3+ __all__ = ["MarkupError" , "escape" , "to_content" ]
4+
35import re
46from ast import literal_eval
57from operator import attrgetter
1517 Union ,
1618)
1719
20+ from textual .css .tokenize import (
21+ COLOR ,
22+ PERCENT ,
23+ TOKEN ,
24+ VARIABLE_REF ,
25+ Expect ,
26+ TokenizerState ,
27+ )
1828from textual .style import Style
1929
2030if TYPE_CHECKING :
@@ -25,7 +35,63 @@ class MarkupError(Exception):
2535 """An error occurred parsing Textual markup."""
2636
2737
28- __all__ = ["MarkupError" , "escape" , "to_content" ]
38+ expect_markup_tag = Expect (
39+ "style token" ,
40+ end_tag = r"(?<!\\)\]" ,
41+ key = r"[@a-zA-Z_-][a-zA-Z0-9_-]*=" ,
42+ percent = PERCENT ,
43+ color = COLOR ,
44+ token = TOKEN ,
45+ variable_ref = VARIABLE_REF ,
46+ whitespace = r"\s+" ,
47+ )
48+
49+ expect_markup = Expect (
50+ "markup token" ,
51+ open_closing_tag = r"(?<!\\)\[/" ,
52+ open_tag = r"(?<!\\)\[" ,
53+ end_tag = r"(?<!\\)\]" ,
54+ ).extract_text ()
55+
56+ expect_markup_expression = Expect (
57+ "markup" ,
58+ end_tag = r"(?<!\\)\]" ,
59+ word = r"\w+" ,
60+ period = r"\." ,
61+ round_start = r"\(" ,
62+ round_end = r"\)" ,
63+ square_start = r"\[" ,
64+ square_end = r"\]" ,
65+ curly_start = r"\{" ,
66+ curly_end = r"\}" ,
67+ comma = "," ,
68+ whitespace = r"\s+" ,
69+ double_string = r"\".*?\"" ,
70+ single_string = r"'.*?'" ,
71+ )
72+
73+
74+ class MarkupTokenizer (TokenizerState ):
75+ """Tokenizes Textual markup."""
76+
77+ EXPECT = expect_markup .expect_eof (True )
78+ STATE_MAP = {
79+ "open_tag" : expect_markup_tag ,
80+ "open_closing_tag" : expect_markup_tag ,
81+ "end_tag" : expect_markup ,
82+ "key" : expect_markup_expression ,
83+ }
84+ STATE_PUSH = {
85+ "round_start" : expect_markup_expression ,
86+ "square_start" : expect_markup_expression ,
87+ "curly_start" : expect_markup_expression ,
88+ }
89+ STATE_POP = {
90+ "round_end" : "round_start" ,
91+ "square_end" : "square_start" ,
92+ "curly_end" : "curly_start" ,
93+ }
94+
2995
3096RE_TAGS = re .compile (
3197 r"""((\\*)\[([\$a-z#/@][^[]*?)])""" ,
@@ -248,6 +314,68 @@ def pop_style(style_name: str) -> Tuple[int, Tag]:
248314 return content
249315
250316
317+ def to_content (markup : str , style : str | Style = "" ) -> Content :
318+
319+ from textual .content import Content , Span
320+
321+ tokenizer = MarkupTokenizer ()
322+ text : list [str ] = []
323+ iter_tokens = iter (tokenizer (markup , ("inline" , "" )))
324+
325+ style_stack : list [tuple [int , str ]] = []
326+
327+ spans : list [Span ] = []
328+
329+ position = 0
330+ tag_text : list [str ]
331+ for token in iter_tokens :
332+ print (repr (token ))
333+ token_name = token .name
334+ if token_name == "text" :
335+ text .append (token .value )
336+ position += len (token .value )
337+ elif token_name == "open_tag" :
338+ tag_text = []
339+ print ("open" )
340+ for token in iter_tokens :
341+ print (" " , repr (token ))
342+ if token .name == "end_tag" :
343+ break
344+ tag_text .append (token .value )
345+ opening_tag = "" .join (tag_text )
346+ style_stack .append ((position , opening_tag ))
347+
348+ elif token_name == "open_closing_tag" :
349+ tag_text = []
350+ print ("closing" )
351+ for token in iter_tokens :
352+ print (" " , repr (token ))
353+ if token .name == "end_tag" :
354+ break
355+ tag_text .append (token .value )
356+ closing_tag = "" .join (tag_text )
357+ if closing_tag :
358+ for index , (tag_position , tag_body ) in enumerate (reversed (style_stack )):
359+ if tag_body == closing_tag :
360+ style_stack .pop (- index )
361+ spans .append (Span (tag_position , position , tag_body ))
362+ break
363+
364+ else :
365+ open_position , tag = style_stack .pop ()
366+ spans .append (Span (open_position , position , tag ))
367+
368+ content_text = "" .join (text )
369+ text_length = len (content_text )
370+ while style_stack :
371+ position , tag = style_stack .pop ()
372+ spans .append (Span (position , text_length , tag ))
373+
374+ content = Content (content_text , spans )
375+ print (repr (content ))
376+ return content
377+
378+
251379if __name__ == "__main__" : # pragma: no cover
252380 from rich .highlighter import ReprHighlighter
253381
@@ -296,7 +424,10 @@ def on_markup_changed(self, event: TextArea.Changed) -> None:
296424 results .update (event .text_area .text )
297425 except Exception as error :
298426 highlight = ReprHighlighter ()
299- results .update (highlight (str (error )))
427+ # results.update(highlight(str(error)))
428+ from rich .traceback import Traceback
429+
430+ results .update (Traceback ())
300431 self .query_one ("#results-container" ).add_class ("-error" )
301432 else :
302433 self .query_one ("#results-container" ).remove_class ("-error" )
0 commit comments