Support named style closing tags

This commit is contained in:
Nanaloveyuki
2026-05-10 15:22:56 +08:00
parent e78183d267
commit 2d2388c79f
6 changed files with 77 additions and 4 deletions
+24
View File
@@ -107,6 +107,30 @@ test "text formatter supports nested inline tags" {
)
}
test "text formatter supports named closing tags" {
let rec = record(Level::Info, "<red>boom</red>")
inspect(
format_text(rec, formatter=text_formatter(show_level=false, show_target=false, color_mode=ColorMode::Always)),
content="\u{001b}[31mboom\u{001b}[0m",
)
}
test "text formatter supports mixed short and named closing tags" {
let rec = record(Level::Info, "<red><b>fatal</b></red>")
inspect(
format_text(rec, formatter=text_formatter(show_level=false, show_target=false, color_mode=ColorMode::Always)),
content="\u{001b}[31;1mfatal\u{001b}[0m",
)
}
test "text formatter keeps unmatched named closing tags as plain text" {
let rec = record(Level::Info, "boom</red>")
inspect(
format_text(rec, formatter=text_formatter(show_level=false, show_target=false)),
content="boom</red>",
)
}
test "text formatter supports hex inline colors" {
let rec = record(Level::Info, "<#ff0000>hot</> <bg:#010203>bg</>")
inspect(
+1
View File
@@ -254,6 +254,7 @@ test {
- `style_markup` / `style_markup`: `disabled`, `builtin`, `full`
- `target_style_markup` / `target_style_markup`, `fields_style_markup` / `fields_style_markup`: `disabled`, `builtin`, `full`
- inline style tags / inline 样式标签: `<red>...</>`, `<b>...</>`, `<#ff0000>...</>`, `<bg:#202020>...</>`
- closing tags / 闭合标签: `</>` 以及具名闭合 `</red>`, `</danger>`, `</b>`
- builtin semantic tags / 内置语义标签: `<accent>`, `<info>`, `<success>`, `<warning>`, `<danger>`, `<muted>`
- runtime style tags / 运行期样式标签: `TextStyle`, `StyleTagRegistry`, `style_tag_registry()`, `default_style_tag_registry()`, `set_tag(...)`, `define_alias(...)`
- style tag priority / 标签优先级: formatter local `style_tags` > global style tag registry > builtin tags
+47 -3
View File
@@ -160,6 +160,11 @@ priv struct StyledSegment {
style : InlineStyle
}
priv struct StyleFrame {
tag : String
style : InlineStyle
}
fn code_unit(ch : Char) -> Int {
ch.to_int()
}
@@ -529,12 +534,41 @@ fn push_plain_segment(
}
}
fn pop_named_style_frame(stack : Array[StyleFrame], tag : String) -> Bool {
let normalized = normalize_style_tag_name(tag)
if stack.length() <= 1 {
return false
}
for i = stack.length() - 1; i > 0; i = i - 1 {
if stack[i].tag == normalized {
while stack.length() - 1 >= i {
ignore(stack.pop())
}
return true
}
}
false
}
fn has_named_style_frame(stack : Array[StyleFrame], tag : String) -> Bool {
let normalized = normalize_style_tag_name(tag)
if stack.length() <= 1 {
return false
}
for i = stack.length() - 1; i > 0; i = i - 1 {
if stack[i].tag == normalized {
return true
}
}
false
}
fn parse_inline_markup(input : String, formatter : TextFormatter) -> Array[StyledSegment] {
let segments : Array[StyledSegment] = []
let buffer = StringBuilder::new()
let stack : Array[InlineStyle] = [default_inline_style()]
let stack : Array[StyleFrame] = [{ tag: "", style: default_inline_style() }]
let chars = input.to_array()
let current_style = fn() { stack[stack.length() - 1] }
let current_style = fn() { stack[stack.length() - 1].style }
let flush = fn() { push_plain_segment(segments, buffer, current_style()) }
let append_raw = fn(start : Int, finish : Int) {
for i = start; i < finish; i = i + 1 {
@@ -569,10 +603,20 @@ fn parse_inline_markup(input : String, formatter : TextFormatter) -> Array[Style
}
continue end + 1
}
if tag.has_prefix("/") {
let close_tag = tag[1:].to_owned()
if close_tag != "" && has_named_style_frame(stack, close_tag) {
flush()
ignore(pop_named_style_frame(stack, close_tag))
} else {
append_raw(i, end + 1)
}
continue end + 1
}
match apply_inline_tag(current_style(), tag, formatter) {
Some(next_style) => {
flush()
stack.push(next_style)
stack.push({ tag: normalize_style_tag_name(tag), style: next_style })
}
None => append_raw(i, end + 1)
}