Skip to main content

j_cli/command/chat/
markdown.rs

1use super::render::{char_width, display_width, wrap_text};
2use super::theme::Theme;
3use ratatui::{
4    style::{Modifier, Style},
5    text::{Line, Span},
6};
7
8pub fn markdown_to_lines(md: &str, max_width: usize, theme: &Theme) -> Vec<Line<'static>> {
9    use pulldown_cmark::{CodeBlockKind, Event, Options, Parser, Tag, TagEnd};
10
11    // 内容区宽度 = max_width - 2(左侧 "  " 缩进由外层负责)
12    let content_width = max_width.saturating_sub(2);
13
14    let options = Options::ENABLE_STRIKETHROUGH | Options::ENABLE_TABLES;
15    let parser = Parser::new_ext(md, options);
16
17    let mut lines: Vec<Line<'static>> = Vec::new();
18    let mut current_spans: Vec<Span<'static>> = Vec::new();
19    let mut style_stack: Vec<Style> = vec![Style::default().fg(theme.text_normal)];
20    let mut in_code_block = false;
21    let mut code_block_content = String::new();
22    let mut code_block_lang = String::new();
23    let mut list_depth: usize = 0;
24    let mut ordered_index: Option<u64> = None;
25    let mut heading_level: Option<u8> = None;
26    // 跟踪是否在引用块中
27    let mut in_blockquote = false;
28    // 表格相关状态
29    let mut in_table = false;
30    let mut table_rows: Vec<Vec<String>> = Vec::new(); // 收集所有行(含表头)
31    let mut current_row: Vec<String> = Vec::new();
32    let mut current_cell = String::new();
33    let mut table_alignments: Vec<pulldown_cmark::Alignment> = Vec::new();
34
35    let base_style = Style::default().fg(theme.text_normal);
36
37    let flush_line = |current_spans: &mut Vec<Span<'static>>, lines: &mut Vec<Line<'static>>| {
38        if !current_spans.is_empty() {
39            lines.push(Line::from(current_spans.drain(..).collect::<Vec<_>>()));
40        }
41    };
42
43    for event in parser {
44        match event {
45            Event::Start(Tag::Heading { level, .. }) => {
46                flush_line(&mut current_spans, &mut lines);
47                heading_level = Some(level as u8);
48                if !lines.is_empty() {
49                    lines.push(Line::from(""));
50                }
51                // 根据标题级别使用不同的颜色
52                let heading_style = match level as u8 {
53                    1 => Style::default()
54                        .fg(theme.md_h1)
55                        .add_modifier(Modifier::BOLD | Modifier::UNDERLINED),
56                    2 => Style::default()
57                        .fg(theme.md_h2)
58                        .add_modifier(Modifier::BOLD),
59                    3 => Style::default()
60                        .fg(theme.md_h3)
61                        .add_modifier(Modifier::BOLD),
62                    _ => Style::default()
63                        .fg(theme.md_h4)
64                        .add_modifier(Modifier::BOLD),
65                };
66                style_stack.push(heading_style);
67            }
68            Event::End(TagEnd::Heading(level)) => {
69                flush_line(&mut current_spans, &mut lines);
70                // h1/h2 下方加分隔线(完整填充 content_width)
71                if (level as u8) <= 2 {
72                    let sep_char = if (level as u8) == 1 { "━" } else { "─" };
73                    lines.push(Line::from(Span::styled(
74                        sep_char.repeat(content_width),
75                        Style::default().fg(theme.md_heading_sep),
76                    )));
77                }
78                style_stack.pop();
79                heading_level = None;
80            }
81            Event::Start(Tag::Strong) => {
82                let current = *style_stack.last().unwrap_or(&base_style);
83                style_stack.push(current.add_modifier(Modifier::BOLD).fg(theme.text_bold));
84            }
85            Event::End(TagEnd::Strong) => {
86                style_stack.pop();
87            }
88            Event::Start(Tag::Emphasis) => {
89                let current = *style_stack.last().unwrap_or(&base_style);
90                style_stack.push(current.add_modifier(Modifier::ITALIC));
91            }
92            Event::End(TagEnd::Emphasis) => {
93                style_stack.pop();
94            }
95            Event::Start(Tag::Strikethrough) => {
96                let current = *style_stack.last().unwrap_or(&base_style);
97                style_stack.push(current.add_modifier(Modifier::CROSSED_OUT));
98            }
99            Event::End(TagEnd::Strikethrough) => {
100                style_stack.pop();
101            }
102            Event::Start(Tag::CodeBlock(kind)) => {
103                flush_line(&mut current_spans, &mut lines);
104                in_code_block = true;
105                code_block_content.clear();
106                code_block_lang = match kind {
107                    CodeBlockKind::Fenced(lang) => lang.to_string(),
108                    CodeBlockKind::Indented => String::new(),
109                };
110                // 代码块上方边框(自适应宽度)
111                let label = if code_block_lang.is_empty() {
112                    " code ".to_string()
113                } else {
114                    format!(" {} ", code_block_lang)
115                };
116                let label_w = display_width(&label);
117                let border_fill = content_width.saturating_sub(2 + label_w);
118                let top_border = format!("┌─{}{}", label, "─".repeat(border_fill));
119                lines.push(Line::from(Span::styled(
120                    top_border,
121                    Style::default().fg(theme.code_border),
122                )));
123            }
124            Event::End(TagEnd::CodeBlock) => {
125                // 渲染代码块内容(带语法高亮)
126                let code_inner_w = content_width.saturating_sub(4); // "│ " 前缀 + 右侧 " │" 后缀占4
127                // 将 tab 替换为 4 空格(tab 的 unicode-width 为 0,但终端显示为多列,导致宽度计算错乱)
128                let code_content_expanded = code_block_content.replace('\t', "    ");
129                for code_line in code_content_expanded.lines() {
130                    let wrapped = wrap_text(code_line, code_inner_w);
131                    for wl in wrapped {
132                        let highlighted = highlight_code_line(&wl, &code_block_lang, theme);
133                        let text_w: usize =
134                            highlighted.iter().map(|s| display_width(&s.content)).sum();
135                        let fill = code_inner_w.saturating_sub(text_w);
136                        let mut spans_vec = Vec::new();
137                        spans_vec.push(Span::styled("│ ", Style::default().fg(theme.code_border)));
138                        for hs in highlighted {
139                            spans_vec.push(Span::styled(
140                                hs.content.to_string(),
141                                hs.style.bg(theme.code_bg),
142                            ));
143                        }
144                        spans_vec.push(Span::styled(
145                            format!("{} │", " ".repeat(fill)),
146                            Style::default().fg(theme.code_border).bg(theme.code_bg),
147                        ));
148                        lines.push(Line::from(spans_vec));
149                    }
150                }
151                let bottom_border = format!("└{}", "─".repeat(content_width.saturating_sub(1)));
152                lines.push(Line::from(Span::styled(
153                    bottom_border,
154                    Style::default().fg(theme.code_border),
155                )));
156                in_code_block = false;
157                code_block_content.clear();
158                code_block_lang.clear();
159            }
160            Event::Code(text) => {
161                if in_table {
162                    // 表格中的行内代码也收集到当前单元格
163                    current_cell.push('`');
164                    current_cell.push_str(&text);
165                    current_cell.push('`');
166                } else {
167                    // 行内代码:检查行宽,放不下则先换行
168                    let code_str = format!(" {} ", text);
169                    let code_w = display_width(&code_str);
170                    let effective_prefix_w = if in_blockquote { 2 } else { 0 };
171                    let full_line_w = content_width.saturating_sub(effective_prefix_w);
172                    let existing_w: usize = current_spans
173                        .iter()
174                        .map(|s| display_width(&s.content))
175                        .sum();
176                    if existing_w + code_w > full_line_w && !current_spans.is_empty() {
177                        flush_line(&mut current_spans, &mut lines);
178                        if in_blockquote {
179                            current_spans.push(Span::styled(
180                                "| ".to_string(),
181                                Style::default().fg(theme.md_blockquote_bar),
182                            ));
183                        }
184                    }
185                    current_spans.push(Span::styled(
186                        code_str,
187                        Style::default()
188                            .fg(theme.md_inline_code_fg)
189                            .bg(theme.md_inline_code_bg),
190                    ));
191                }
192            }
193            Event::Start(Tag::List(start)) => {
194                flush_line(&mut current_spans, &mut lines);
195                list_depth += 1;
196                ordered_index = start;
197            }
198            Event::End(TagEnd::List(_)) => {
199                flush_line(&mut current_spans, &mut lines);
200                list_depth = list_depth.saturating_sub(1);
201                ordered_index = None;
202            }
203            Event::Start(Tag::Item) => {
204                flush_line(&mut current_spans, &mut lines);
205                let indent = "  ".repeat(list_depth);
206                let bullet = if let Some(ref mut idx) = ordered_index {
207                    let s = format!("{}{}. ", indent, idx);
208                    *idx += 1;
209                    s
210                } else {
211                    format!("{}• ", indent)
212                };
213                current_spans.push(Span::styled(
214                    bullet,
215                    Style::default().fg(theme.md_list_bullet),
216                ));
217            }
218            Event::End(TagEnd::Item) => {
219                flush_line(&mut current_spans, &mut lines);
220            }
221            Event::Start(Tag::Paragraph) => {
222                if !lines.is_empty() && !in_code_block && heading_level.is_none() {
223                    let last_empty = lines.last().map(|l| l.spans.is_empty()).unwrap_or(false);
224                    if !last_empty {
225                        lines.push(Line::from(""));
226                    }
227                }
228            }
229            Event::End(TagEnd::Paragraph) => {
230                flush_line(&mut current_spans, &mut lines);
231            }
232            Event::Start(Tag::BlockQuote(_)) => {
233                flush_line(&mut current_spans, &mut lines);
234                in_blockquote = true;
235                style_stack.push(Style::default().fg(theme.md_blockquote_text));
236            }
237            Event::End(TagEnd::BlockQuote(_)) => {
238                flush_line(&mut current_spans, &mut lines);
239                in_blockquote = false;
240                style_stack.pop();
241            }
242            Event::Text(text) => {
243                if in_code_block {
244                    code_block_content.push_str(&text);
245                } else if in_table {
246                    // 表格中的文本收集到当前单元格
247                    current_cell.push_str(&text);
248                } else {
249                    let style = *style_stack.last().unwrap_or(&base_style);
250                    let text_str = text.to_string();
251
252                    // 标题:添加可视化符号前缀代替 # 标记
253                    if let Some(level) = heading_level {
254                        let (prefix, prefix_style) = match level {
255                            1 => (
256                                ">> ",
257                                Style::default()
258                                    .fg(theme.md_h1)
259                                    .add_modifier(Modifier::BOLD),
260                            ),
261                            2 => (
262                                ">> ",
263                                Style::default()
264                                    .fg(theme.md_h2)
265                                    .add_modifier(Modifier::BOLD),
266                            ),
267                            3 => (
268                                "> ",
269                                Style::default()
270                                    .fg(theme.md_h3)
271                                    .add_modifier(Modifier::BOLD),
272                            ),
273                            _ => (
274                                "> ",
275                                Style::default()
276                                    .fg(theme.md_h4)
277                                    .add_modifier(Modifier::BOLD),
278                            ),
279                        };
280                        current_spans.push(Span::styled(prefix.to_string(), prefix_style));
281                        heading_level = None; // 只加一次前缀
282                    }
283
284                    // 引用块:加左侧竖线
285                    let effective_prefix_w = if in_blockquote { 2 } else { 0 }; // "| " 宽度
286                    let full_line_w = content_width.saturating_sub(effective_prefix_w);
287
288                    // 计算 current_spans 已有的显示宽度
289                    let existing_w: usize = current_spans
290                        .iter()
291                        .map(|s| display_width(&s.content))
292                        .sum();
293
294                    // 剩余可用宽度
295                    let wrap_w = full_line_w.saturating_sub(existing_w);
296
297                    // 如果剩余宽度太小(不足整行的 1/4),先 flush 当前行再换行,
298                    // 避免文字被挤到极窄的空间导致竖排
299                    let min_useful_w = full_line_w / 4;
300                    let wrap_w = if wrap_w < min_useful_w.max(4) && !current_spans.is_empty() {
301                        flush_line(&mut current_spans, &mut lines);
302                        if in_blockquote {
303                            current_spans.push(Span::styled(
304                                "| ".to_string(),
305                                Style::default().fg(theme.md_blockquote_bar),
306                            ));
307                        }
308                        // flush 后使用完整行宽
309                        full_line_w
310                    } else {
311                        wrap_w
312                    };
313
314                    for (i, line) in text_str.split('\n').enumerate() {
315                        if i > 0 {
316                            flush_line(&mut current_spans, &mut lines);
317                            if in_blockquote {
318                                current_spans.push(Span::styled(
319                                    "| ".to_string(),
320                                    Style::default().fg(theme.md_blockquote_bar),
321                                ));
322                            }
323                        }
324                        if !line.is_empty() {
325                            // 第一行使用减去已有 span 宽度的 wrap_w,后续行使用完整 content_width
326                            let effective_wrap = if i == 0 {
327                                wrap_w
328                            } else {
329                                content_width.saturating_sub(effective_prefix_w)
330                            };
331                            let wrapped = wrap_text(line, effective_wrap);
332                            for (j, wl) in wrapped.iter().enumerate() {
333                                if j > 0 {
334                                    flush_line(&mut current_spans, &mut lines);
335                                    if in_blockquote {
336                                        current_spans.push(Span::styled(
337                                            "| ".to_string(),
338                                            Style::default().fg(theme.md_blockquote_bar),
339                                        ));
340                                    }
341                                }
342                                current_spans.push(Span::styled(wl.clone(), style));
343                            }
344                        }
345                    }
346                }
347            }
348            Event::SoftBreak => {
349                if in_table {
350                    current_cell.push(' ');
351                } else {
352                    current_spans.push(Span::raw(" "));
353                }
354            }
355            Event::HardBreak => {
356                if in_table {
357                    current_cell.push(' ');
358                } else {
359                    flush_line(&mut current_spans, &mut lines);
360                }
361            }
362            Event::Rule => {
363                flush_line(&mut current_spans, &mut lines);
364                lines.push(Line::from(Span::styled(
365                    "─".repeat(content_width),
366                    Style::default().fg(theme.md_rule),
367                )));
368            }
369            // ===== 表格支持 =====
370            Event::Start(Tag::Table(alignments)) => {
371                flush_line(&mut current_spans, &mut lines);
372                in_table = true;
373                table_rows.clear();
374                table_alignments = alignments;
375            }
376            Event::End(TagEnd::Table) => {
377                // 表格结束:计算列宽,渲染完整表格
378                flush_line(&mut current_spans, &mut lines);
379                in_table = false;
380
381                if !table_rows.is_empty() {
382                    let num_cols = table_rows.iter().map(|r| r.len()).max().unwrap_or(0);
383                    if num_cols > 0 {
384                        // 计算每列最大宽度
385                        let mut col_widths: Vec<usize> = vec![0; num_cols];
386                        for row in &table_rows {
387                            for (i, cell) in row.iter().enumerate() {
388                                let w = display_width(cell);
389                                if w > col_widths[i] {
390                                    col_widths[i] = w;
391                                }
392                            }
393                        }
394
395                        // 限制总宽度不超过 content_width,等比缩放
396                        let sep_w = num_cols + 1; // 竖线占用
397                        let pad_w = num_cols * 2; // 每列左右各1空格
398                        let avail = content_width.saturating_sub(sep_w + pad_w);
399                        // 单列最大宽度限制(避免一列过宽)
400                        let max_col_w = avail * 2 / 3;
401                        for cw in col_widths.iter_mut() {
402                            if *cw > max_col_w {
403                                *cw = max_col_w;
404                            }
405                        }
406                        let total_col_w: usize = col_widths.iter().sum();
407                        if total_col_w > avail && total_col_w > 0 {
408                            // 等比缩放
409                            let mut remaining = avail;
410                            for (i, cw) in col_widths.iter_mut().enumerate() {
411                                if i == num_cols - 1 {
412                                    // 最后一列取剩余宽度,避免取整误差
413                                    *cw = remaining.max(1);
414                                } else {
415                                    *cw = ((*cw) * avail / total_col_w).max(1);
416                                    remaining = remaining.saturating_sub(*cw);
417                                }
418                            }
419                        }
420
421                        let table_style = Style::default().fg(theme.table_body);
422                        let header_style = Style::default()
423                            .fg(theme.table_header)
424                            .add_modifier(Modifier::BOLD);
425                        let border_style = Style::default().fg(theme.table_border);
426
427                        // 表格行的实际字符宽度(用空格字符计算,不依赖 Box Drawing 字符宽度)
428                        // table_row_w = 竖线数(num_cols+1) + 每列(cw+2) = sep_w + pad_w + total_col_w
429                        let total_col_w_final: usize = col_widths.iter().sum();
430                        let table_row_w = sep_w + pad_w + total_col_w_final;
431                        // 表格行右侧需要补充的空格数,使整行宽度等于 content_width
432                        let table_right_pad = content_width.saturating_sub(table_row_w);
433
434                        // 渲染顶边框 ┌─┬─┐
435                        let mut top = String::from("┌");
436                        for (i, cw) in col_widths.iter().enumerate() {
437                            top.push_str(&"─".repeat(cw + 2));
438                            if i < num_cols - 1 {
439                                top.push('┬');
440                            }
441                        }
442                        top.push('┐');
443                        // 补充右侧空格,使宽度对齐 content_width
444                        let mut top_spans = vec![Span::styled(top, border_style)];
445                        if table_right_pad > 0 {
446                            top_spans.push(Span::raw(" ".repeat(table_right_pad)));
447                        }
448                        lines.push(Line::from(top_spans));
449
450                        for (row_idx, row) in table_rows.iter().enumerate() {
451                            // 数据行 │ cell │ cell │
452                            let mut row_spans: Vec<Span> = Vec::new();
453                            row_spans.push(Span::styled("│", border_style));
454                            for (i, cw) in col_widths.iter().enumerate() {
455                                let cell_text = row.get(i).map(|s| s.as_str()).unwrap_or("");
456                                let cell_w = display_width(cell_text);
457                                let text = if cell_w > *cw {
458                                    // 截断
459                                    let mut t = String::new();
460                                    let mut w = 0;
461                                    for ch in cell_text.chars() {
462                                        let chw = char_width(ch);
463                                        if w + chw > *cw {
464                                            break;
465                                        }
466                                        t.push(ch);
467                                        w += chw;
468                                    }
469                                    let fill = cw.saturating_sub(w);
470                                    format!(" {}{} ", t, " ".repeat(fill))
471                                } else {
472                                    // 根据对齐方式填充
473                                    let fill = cw.saturating_sub(cell_w);
474                                    let align = table_alignments
475                                        .get(i)
476                                        .copied()
477                                        .unwrap_or(pulldown_cmark::Alignment::None);
478                                    match align {
479                                        pulldown_cmark::Alignment::Center => {
480                                            let left = fill / 2;
481                                            let right = fill - left;
482                                            format!(
483                                                " {}{}{} ",
484                                                " ".repeat(left),
485                                                cell_text,
486                                                " ".repeat(right)
487                                            )
488                                        }
489                                        pulldown_cmark::Alignment::Right => {
490                                            format!(" {}{} ", " ".repeat(fill), cell_text)
491                                        }
492                                        _ => {
493                                            format!(" {}{} ", cell_text, " ".repeat(fill))
494                                        }
495                                    }
496                                };
497                                let style = if row_idx == 0 {
498                                    header_style
499                                } else {
500                                    table_style
501                                };
502                                row_spans.push(Span::styled(text, style));
503                                row_spans.push(Span::styled("│", border_style));
504                            }
505                            // 补充右侧空格,使宽度对齐 content_width
506                            if table_right_pad > 0 {
507                                row_spans.push(Span::raw(" ".repeat(table_right_pad)));
508                            }
509                            lines.push(Line::from(row_spans));
510
511                            // 表头行后加分隔线 ├─┼─┤
512                            if row_idx == 0 {
513                                let mut sep = String::from("├");
514                                for (i, cw) in col_widths.iter().enumerate() {
515                                    sep.push_str(&"─".repeat(cw + 2));
516                                    if i < num_cols - 1 {
517                                        sep.push('┼');
518                                    }
519                                }
520                                sep.push('┤');
521                                let mut sep_spans = vec![Span::styled(sep, border_style)];
522                                if table_right_pad > 0 {
523                                    sep_spans.push(Span::raw(" ".repeat(table_right_pad)));
524                                }
525                                lines.push(Line::from(sep_spans));
526                            }
527                        }
528
529                        // 底边框 └─┴─┘
530                        let mut bottom = String::from("└");
531                        for (i, cw) in col_widths.iter().enumerate() {
532                            bottom.push_str(&"─".repeat(cw + 2));
533                            if i < num_cols - 1 {
534                                bottom.push('┴');
535                            }
536                        }
537                        bottom.push('┘');
538                        let mut bottom_spans = vec![Span::styled(bottom, border_style)];
539                        if table_right_pad > 0 {
540                            bottom_spans.push(Span::raw(" ".repeat(table_right_pad)));
541                        }
542                        lines.push(Line::from(bottom_spans));
543                    }
544                }
545                table_rows.clear();
546                table_alignments.clear();
547            }
548            Event::Start(Tag::TableHead) => {
549                current_row.clear();
550            }
551            Event::End(TagEnd::TableHead) => {
552                table_rows.push(current_row.clone());
553                current_row.clear();
554            }
555            Event::Start(Tag::TableRow) => {
556                current_row.clear();
557            }
558            Event::End(TagEnd::TableRow) => {
559                table_rows.push(current_row.clone());
560                current_row.clear();
561            }
562            Event::Start(Tag::TableCell) => {
563                current_cell.clear();
564            }
565            Event::End(TagEnd::TableCell) => {
566                current_row.push(current_cell.clone());
567                current_cell.clear();
568            }
569            _ => {}
570        }
571    }
572
573    // 刷新最后一行
574    if !current_spans.is_empty() {
575        lines.push(Line::from(current_spans));
576    }
577
578    // 如果解析结果为空,至少返回原始文本
579    if lines.is_empty() {
580        let wrapped = wrap_text(md, content_width);
581        for wl in wrapped {
582            lines.push(Line::from(Span::styled(wl, base_style)));
583        }
584    }
585
586    lines
587}
588
589/// 简单的代码语法高亮(无需外部依赖)
590/// 根据语言类型对常见关键字、字符串、注释、数字进行着色
591pub fn highlight_code_line<'a>(line: &'a str, lang: &str, theme: &Theme) -> Vec<Span<'static>> {
592    let lang_lower = lang.to_lowercase();
593    // Rust 使用多组词汇分别高亮
594    // keywords: 控制流/定义关键字 → 紫色
595    // primitive_types: 原始类型 → 青绿色
596    // 其他类型名(大写开头)自动通过 type_style 高亮 → 暖黄色
597    // 宏调用(word!)通过 macro_style 高亮 → 淡蓝色
598    let keywords: &[&str] = match lang_lower.as_str() {
599        "rust" | "rs" => &[
600            // 控制流/定义关键字(紫色)
601            "fn", "let", "mut", "pub", "use", "mod", "struct", "enum", "impl", "trait", "for",
602            "while", "loop", "if", "else", "match", "return", "self", "Self", "where", "async",
603            "await", "move", "ref", "type", "const", "static", "crate", "super", "as", "in",
604            "true", "false", "unsafe", "extern", "dyn", "abstract", "become", "box", "do", "final",
605            "macro", "override", "priv", "typeof", "unsized", "virtual", "yield", "union", "break",
606            "continue",
607        ],
608        "python" | "py" => &[
609            "def", "class", "return", "if", "elif", "else", "for", "while", "import", "from", "as",
610            "with", "try", "except", "finally", "raise", "pass", "break", "continue", "yield",
611            "lambda", "and", "or", "not", "in", "is", "True", "False", "None", "global",
612            "nonlocal", "assert", "del", "async", "await", "self", "print",
613        ],
614        "javascript" | "js" | "typescript" | "ts" | "jsx" | "tsx" => &[
615            "function",
616            "const",
617            "let",
618            "var",
619            "return",
620            "if",
621            "else",
622            "for",
623            "while",
624            "class",
625            "new",
626            "this",
627            "import",
628            "export",
629            "from",
630            "default",
631            "async",
632            "await",
633            "try",
634            "catch",
635            "finally",
636            "throw",
637            "typeof",
638            "instanceof",
639            "true",
640            "false",
641            "null",
642            "undefined",
643            "of",
644            "in",
645            "switch",
646            "case",
647        ],
648        "go" | "golang" => &[
649            "func",
650            "package",
651            "import",
652            "return",
653            "if",
654            "else",
655            "for",
656            "range",
657            "struct",
658            "interface",
659            "type",
660            "var",
661            "const",
662            "defer",
663            "go",
664            "chan",
665            "select",
666            "case",
667            "switch",
668            "default",
669            "break",
670            "continue",
671            "map",
672            "true",
673            "false",
674            "nil",
675            "make",
676            "append",
677            "len",
678            "cap",
679        ],
680        "java" | "kotlin" | "kt" => &[
681            "public",
682            "private",
683            "protected",
684            "class",
685            "interface",
686            "extends",
687            "implements",
688            "return",
689            "if",
690            "else",
691            "for",
692            "while",
693            "new",
694            "this",
695            "import",
696            "package",
697            "static",
698            "final",
699            "void",
700            "int",
701            "String",
702            "boolean",
703            "true",
704            "false",
705            "null",
706            "try",
707            "catch",
708            "throw",
709            "throws",
710            "fun",
711            "val",
712            "var",
713            "when",
714            "object",
715            "companion",
716        ],
717        "sh" | "bash" | "zsh" | "shell" => &[
718            "if",
719            "then",
720            "else",
721            "elif",
722            "fi",
723            "for",
724            "while",
725            "do",
726            "done",
727            "case",
728            "esac",
729            "function",
730            "return",
731            "exit",
732            "echo",
733            "export",
734            "local",
735            "readonly",
736            "set",
737            "unset",
738            "shift",
739            "source",
740            "in",
741            "true",
742            "false",
743            "read",
744            "declare",
745            "typeset",
746            "trap",
747            "eval",
748            "exec",
749            "test",
750            "select",
751            "until",
752            "break",
753            "continue",
754            "printf",
755            // Go 命令
756            "go",
757            "build",
758            "run",
759            "test",
760            "fmt",
761            "vet",
762            "mod",
763            "get",
764            "install",
765            "clean",
766            "doc",
767            "list",
768            "version",
769            "env",
770            "generate",
771            "tool",
772            "proxy",
773            "GOPATH",
774            "GOROOT",
775            "GOBIN",
776            "GOMODCACHE",
777            "GOPROXY",
778            "GOSUMDB",
779            // Cargo 命令
780            "cargo",
781            "new",
782            "init",
783            "add",
784            "remove",
785            "update",
786            "check",
787            "clippy",
788            "rustfmt",
789            "rustc",
790            "rustup",
791            "publish",
792            "install",
793            "uninstall",
794            "search",
795            "tree",
796            "locate_project",
797            "metadata",
798            "audit",
799            "watch",
800            "expand",
801        ],
802        "c" | "cpp" | "c++" | "h" | "hpp" => &[
803            "int",
804            "char",
805            "float",
806            "double",
807            "void",
808            "long",
809            "short",
810            "unsigned",
811            "signed",
812            "const",
813            "static",
814            "extern",
815            "struct",
816            "union",
817            "enum",
818            "typedef",
819            "sizeof",
820            "return",
821            "if",
822            "else",
823            "for",
824            "while",
825            "do",
826            "switch",
827            "case",
828            "break",
829            "continue",
830            "default",
831            "goto",
832            "auto",
833            "register",
834            "volatile",
835            "class",
836            "public",
837            "private",
838            "protected",
839            "virtual",
840            "override",
841            "template",
842            "namespace",
843            "using",
844            "new",
845            "delete",
846            "try",
847            "catch",
848            "throw",
849            "nullptr",
850            "true",
851            "false",
852            "this",
853            "include",
854            "define",
855            "ifdef",
856            "ifndef",
857            "endif",
858        ],
859        "sql" => &[
860            "SELECT",
861            "FROM",
862            "WHERE",
863            "INSERT",
864            "UPDATE",
865            "DELETE",
866            "CREATE",
867            "DROP",
868            "ALTER",
869            "TABLE",
870            "INDEX",
871            "INTO",
872            "VALUES",
873            "SET",
874            "AND",
875            "OR",
876            "NOT",
877            "NULL",
878            "JOIN",
879            "LEFT",
880            "RIGHT",
881            "INNER",
882            "OUTER",
883            "ON",
884            "GROUP",
885            "BY",
886            "ORDER",
887            "ASC",
888            "DESC",
889            "HAVING",
890            "LIMIT",
891            "OFFSET",
892            "UNION",
893            "AS",
894            "DISTINCT",
895            "COUNT",
896            "SUM",
897            "AVG",
898            "MIN",
899            "MAX",
900            "LIKE",
901            "IN",
902            "BETWEEN",
903            "EXISTS",
904            "CASE",
905            "WHEN",
906            "THEN",
907            "ELSE",
908            "END",
909            "BEGIN",
910            "COMMIT",
911            "ROLLBACK",
912            "PRIMARY",
913            "KEY",
914            "FOREIGN",
915            "REFERENCES",
916            "select",
917            "from",
918            "where",
919            "insert",
920            "update",
921            "delete",
922            "create",
923            "drop",
924            "alter",
925            "table",
926            "index",
927            "into",
928            "values",
929            "set",
930            "and",
931            "or",
932            "not",
933            "null",
934            "join",
935            "left",
936            "right",
937            "inner",
938            "outer",
939            "on",
940            "group",
941            "by",
942            "order",
943            "asc",
944            "desc",
945            "having",
946            "limit",
947            "offset",
948            "union",
949            "as",
950            "distinct",
951            "count",
952            "sum",
953            "avg",
954            "min",
955            "max",
956            "like",
957            "in",
958            "between",
959            "exists",
960            "case",
961            "when",
962            "then",
963            "else",
964            "end",
965            "begin",
966            "commit",
967            "rollback",
968            "primary",
969            "key",
970            "foreign",
971            "references",
972        ],
973        "yaml" | "yml" => &["true", "false", "null", "yes", "no", "on", "off"],
974        "toml" => &[
975            "true",
976            "false",
977            "true",
978            "false",
979            // Cargo.toml 常用
980            "name",
981            "version",
982            "edition",
983            "authors",
984            "dependencies",
985            "dev-dependencies",
986            "build-dependencies",
987            "features",
988            "workspace",
989            "members",
990            "exclude",
991            "include",
992            "path",
993            "git",
994            "branch",
995            "tag",
996            "rev",
997            "package",
998            "lib",
999            "bin",
1000            "example",
1001            "test",
1002            "bench",
1003            "doc",
1004            "profile",
1005            "release",
1006            "debug",
1007            "opt-level",
1008            "lto",
1009            "codegen-units",
1010            "panic",
1011            "strip",
1012            "default",
1013            "features",
1014            "optional",
1015            // 常见配置项
1016            "repository",
1017            "homepage",
1018            "documentation",
1019            "license",
1020            "license-file",
1021            "keywords",
1022            "categories",
1023            "readme",
1024            "description",
1025            "resolver",
1026        ],
1027        "css" | "scss" | "less" => &[
1028            "color",
1029            "background",
1030            "border",
1031            "margin",
1032            "padding",
1033            "display",
1034            "position",
1035            "width",
1036            "height",
1037            "font",
1038            "text",
1039            "flex",
1040            "grid",
1041            "align",
1042            "justify",
1043            "important",
1044            "none",
1045            "auto",
1046            "inherit",
1047            "initial",
1048            "unset",
1049        ],
1050        "dockerfile" | "docker" => &[
1051            "FROM",
1052            "RUN",
1053            "CMD",
1054            "LABEL",
1055            "EXPOSE",
1056            "ENV",
1057            "ADD",
1058            "COPY",
1059            "ENTRYPOINT",
1060            "VOLUME",
1061            "USER",
1062            "WORKDIR",
1063            "ARG",
1064            "ONBUILD",
1065            "STOPSIGNAL",
1066            "HEALTHCHECK",
1067            "SHELL",
1068            "AS",
1069        ],
1070        "ruby" | "rb" => &[
1071            "def", "end", "class", "module", "if", "elsif", "else", "unless", "while", "until",
1072            "for", "do", "begin", "rescue", "ensure", "raise", "return", "yield", "require",
1073            "include", "attr", "self", "true", "false", "nil", "puts", "print",
1074        ],
1075        _ => &[
1076            "fn", "function", "def", "class", "return", "if", "else", "for", "while", "import",
1077            "export", "const", "let", "var", "true", "false", "null", "nil", "None", "self",
1078            "this",
1079        ],
1080    };
1081
1082    // 原始/内建类型列表(青绿色)
1083    let primitive_types: &[&str] = match lang_lower.as_str() {
1084        "rust" | "rs" => &[
1085            "i8", "i16", "i32", "i64", "i128", "isize", "u8", "u16", "u32", "u64", "u128", "usize",
1086            "f32", "f64", "bool", "char", "str",
1087        ],
1088        "go" | "golang" => &[
1089            // Go 内建类型
1090            "int",
1091            "int8",
1092            "int16",
1093            "int32",
1094            "int64",
1095            "uint",
1096            "uint8",
1097            "uint16",
1098            "uint32",
1099            "uint64",
1100            "uintptr",
1101            "float32",
1102            "float64",
1103            "complex64",
1104            "complex128",
1105            "bool",
1106            "byte",
1107            "rune",
1108            "string",
1109            "error",
1110            "any",
1111        ],
1112        _ => &[],
1113    };
1114
1115    // Go 语言显式类型名列表(暖黄色,因为 Go 的大写开头不代表类型)
1116    let go_type_names: &[&str] = match lang_lower.as_str() {
1117        "go" | "golang" => &[
1118            // 常见标准库类型和接口
1119            "Reader",
1120            "Writer",
1121            "Closer",
1122            "ReadWriter",
1123            "ReadCloser",
1124            "WriteCloser",
1125            "ReadWriteCloser",
1126            "Seeker",
1127            "Context",
1128            "Error",
1129            "Stringer",
1130            "Mutex",
1131            "RWMutex",
1132            "WaitGroup",
1133            "Once",
1134            "Pool",
1135            "Map",
1136            "Duration",
1137            "Time",
1138            "Timer",
1139            "Ticker",
1140            "Buffer",
1141            "Builder",
1142            "Request",
1143            "Response",
1144            "ResponseWriter",
1145            "Handler",
1146            "HandlerFunc",
1147            "Server",
1148            "Client",
1149            "Transport",
1150            "File",
1151            "FileInfo",
1152            "FileMode",
1153            "Decoder",
1154            "Encoder",
1155            "Marshaler",
1156            "Unmarshaler",
1157            "Logger",
1158            "Flag",
1159            "Regexp",
1160            "Conn",
1161            "Listener",
1162            "Addr",
1163            "Scanner",
1164            "Token",
1165            "Type",
1166            "Value",
1167            "Kind",
1168            "Cmd",
1169            "Signal",
1170        ],
1171        _ => &[],
1172    };
1173
1174    let comment_prefix = match lang_lower.as_str() {
1175        "python" | "py" | "sh" | "bash" | "zsh" | "shell" | "ruby" | "rb" | "yaml" | "yml"
1176        | "toml" | "dockerfile" | "docker" => "#",
1177        "sql" => "--",
1178        "css" | "scss" | "less" => "/*",
1179        _ => "//",
1180    };
1181
1182    // ===== 代码高亮配色方案(基于主题)=====
1183    let code_style = Style::default().fg(theme.code_default);
1184    let kw_style = Style::default().fg(theme.code_keyword);
1185    let str_style = Style::default().fg(theme.code_string);
1186    let comment_style = Style::default()
1187        .fg(theme.code_comment)
1188        .add_modifier(Modifier::ITALIC);
1189    let num_style = Style::default().fg(theme.code_number);
1190    let type_style = Style::default().fg(theme.code_type);
1191    let primitive_style = Style::default().fg(theme.code_primitive);
1192    let macro_style = Style::default().fg(theme.code_macro);
1193
1194    let trimmed = line.trim_start();
1195
1196    // 注释行
1197    if trimmed.starts_with(comment_prefix) {
1198        return vec![Span::styled(line.to_string(), comment_style)];
1199    }
1200
1201    // 逐词解析
1202    let mut spans = Vec::new();
1203    let mut chars = line.chars().peekable();
1204    let mut buf = String::new();
1205
1206    while let Some(&ch) = chars.peek() {
1207        // 双引号字符串(支持 \ 转义)
1208        if ch == '"' {
1209            if !buf.is_empty() {
1210                spans.extend(colorize_tokens(
1211                    &buf,
1212                    keywords,
1213                    primitive_types,
1214                    go_type_names,
1215                    code_style,
1216                    kw_style,
1217                    num_style,
1218                    type_style,
1219                    primitive_style,
1220                    macro_style,
1221                    &lang_lower,
1222                ));
1223                buf.clear();
1224            }
1225            let mut s = String::new();
1226            s.push(ch);
1227            chars.next();
1228            let mut escaped = false;
1229            while let Some(&c) = chars.peek() {
1230                s.push(c);
1231                chars.next();
1232                if escaped {
1233                    escaped = false;
1234                    continue;
1235                }
1236                if c == '\\' {
1237                    escaped = true;
1238                    continue;
1239                }
1240                if c == '"' {
1241                    break;
1242                }
1243            }
1244            spans.push(Span::styled(s, str_style));
1245            continue;
1246        }
1247        // 反引号字符串(不支持转义,遇到配对反引号结束)
1248        // Go 原始字符串、JS 模板字符串、Rust 无此语义但兼容处理
1249        if ch == '`' {
1250            if !buf.is_empty() {
1251                spans.extend(colorize_tokens(
1252                    &buf,
1253                    keywords,
1254                    primitive_types,
1255                    go_type_names,
1256                    code_style,
1257                    kw_style,
1258                    num_style,
1259                    type_style,
1260                    primitive_style,
1261                    macro_style,
1262                    &lang_lower,
1263                ));
1264                buf.clear();
1265            }
1266            let mut s = String::new();
1267            s.push(ch);
1268            chars.next();
1269            while let Some(&c) = chars.peek() {
1270                s.push(c);
1271                chars.next();
1272                if c == '`' {
1273                    break; // 反引号不支持转义,遇到配对直接结束
1274                }
1275            }
1276            spans.push(Span::styled(s, str_style));
1277            continue;
1278        }
1279        // Rust 生命周期参数 ('a, 'static 等) vs 字符字面量 ('x')
1280        if ch == '\'' && matches!(lang_lower.as_str(), "rust" | "rs") {
1281            if !buf.is_empty() {
1282                spans.extend(colorize_tokens(
1283                    &buf,
1284                    keywords,
1285                    primitive_types,
1286                    go_type_names,
1287                    code_style,
1288                    kw_style,
1289                    num_style,
1290                    type_style,
1291                    primitive_style,
1292                    macro_style,
1293                    &lang_lower,
1294                ));
1295                buf.clear();
1296            }
1297            let mut s = String::new();
1298            s.push(ch);
1299            chars.next();
1300            let mut is_lifetime = false;
1301            // 收集后续字符来判断是生命周期还是字符字面量
1302            while let Some(&c) = chars.peek() {
1303                if c.is_alphanumeric() || c == '_' {
1304                    s.push(c);
1305                    chars.next();
1306                } else if c == '\'' && s.len() == 2 {
1307                    // 'x' 形式 - 字符字面量
1308                    s.push(c);
1309                    chars.next();
1310                    break;
1311                } else {
1312                    // 'name 形式 - 生命周期参数
1313                    is_lifetime = true;
1314                    break;
1315                }
1316            }
1317            if is_lifetime || (s.len() > 1 && !s.ends_with('\'')) {
1318                // 生命周期参数 - 使用浅橙色(与关键字紫色区分)
1319                let lifetime_style = Style::default().fg(theme.code_lifetime);
1320                spans.push(Span::styled(s, lifetime_style));
1321            } else {
1322                // 字符字面量
1323                spans.push(Span::styled(s, str_style));
1324            }
1325            continue;
1326        }
1327        // 其他语言的字符串(包含单引号)
1328        if ch == '\'' && !matches!(lang_lower.as_str(), "rust" | "rs") {
1329            // 先刷新 buf
1330            if !buf.is_empty() {
1331                spans.extend(colorize_tokens(
1332                    &buf,
1333                    keywords,
1334                    primitive_types,
1335                    go_type_names,
1336                    code_style,
1337                    kw_style,
1338                    num_style,
1339                    type_style,
1340                    primitive_style,
1341                    macro_style,
1342                    &lang_lower,
1343                ));
1344                buf.clear();
1345            }
1346            let mut s = String::new();
1347            s.push(ch);
1348            chars.next();
1349            let mut escaped = false;
1350            while let Some(&c) = chars.peek() {
1351                s.push(c);
1352                chars.next();
1353                if escaped {
1354                    escaped = false;
1355                    continue;
1356                }
1357                if c == '\\' {
1358                    escaped = true;
1359                    continue;
1360                }
1361                if c == '\'' {
1362                    break;
1363                }
1364            }
1365            spans.push(Span::styled(s, str_style));
1366            continue;
1367        }
1368        // Rust 属性 (#[...] 或 #![...])
1369        if ch == '#' && matches!(lang_lower.as_str(), "rust" | "rs") {
1370            let mut lookahead = chars.clone();
1371            if let Some(next) = lookahead.next() {
1372                if next == '[' {
1373                    if !buf.is_empty() {
1374                        spans.extend(colorize_tokens(
1375                            &buf,
1376                            keywords,
1377                            primitive_types,
1378                            go_type_names,
1379                            code_style,
1380                            kw_style,
1381                            num_style,
1382                            type_style,
1383                            primitive_style,
1384                            macro_style,
1385                            &lang_lower,
1386                        ));
1387                        buf.clear();
1388                    }
1389                    let mut attr = String::new();
1390                    attr.push(ch);
1391                    chars.next();
1392                    let mut depth = 0;
1393                    while let Some(&c) = chars.peek() {
1394                        attr.push(c);
1395                        chars.next();
1396                        if c == '[' {
1397                            depth += 1;
1398                        } else if c == ']' {
1399                            depth -= 1;
1400                            if depth == 0 {
1401                                break;
1402                            }
1403                        }
1404                    }
1405                    // 属性使用青绿色(与关键字紫色区分)
1406                    let attr_style = Style::default().fg(theme.code_attribute);
1407                    spans.push(Span::styled(attr, attr_style));
1408                    continue;
1409                }
1410            }
1411        }
1412        // Shell 变量 ($VAR, ${VAR}, $1 等)
1413        if ch == '$'
1414            && matches!(
1415                lang_lower.as_str(),
1416                "sh" | "bash" | "zsh" | "shell" | "dockerfile" | "docker"
1417            )
1418        {
1419            if !buf.is_empty() {
1420                spans.extend(colorize_tokens(
1421                    &buf,
1422                    keywords,
1423                    primitive_types,
1424                    go_type_names,
1425                    code_style,
1426                    kw_style,
1427                    num_style,
1428                    type_style,
1429                    primitive_style,
1430                    macro_style,
1431                    &lang_lower,
1432                ));
1433                buf.clear();
1434            }
1435            // Shell 变量使用青色(与属性统一风格)
1436            let var_style = Style::default().fg(theme.code_shell_var);
1437            let mut var = String::new();
1438            var.push(ch);
1439            chars.next();
1440            if let Some(&next_ch) = chars.peek() {
1441                if next_ch == '{' {
1442                    // ${VAR}
1443                    var.push(next_ch);
1444                    chars.next();
1445                    while let Some(&c) = chars.peek() {
1446                        var.push(c);
1447                        chars.next();
1448                        if c == '}' {
1449                            break;
1450                        }
1451                    }
1452                } else if next_ch == '(' {
1453                    // $(cmd)
1454                    var.push(next_ch);
1455                    chars.next();
1456                    let mut depth = 1;
1457                    while let Some(&c) = chars.peek() {
1458                        var.push(c);
1459                        chars.next();
1460                        if c == '(' {
1461                            depth += 1;
1462                        }
1463                        if c == ')' {
1464                            depth -= 1;
1465                            if depth == 0 {
1466                                break;
1467                            }
1468                        }
1469                    }
1470                } else if next_ch.is_alphanumeric()
1471                    || next_ch == '_'
1472                    || next_ch == '@'
1473                    || next_ch == '#'
1474                    || next_ch == '?'
1475                    || next_ch == '!'
1476                {
1477                    // $VAR, $1, $@, $#, $? 等
1478                    while let Some(&c) = chars.peek() {
1479                        if c.is_alphanumeric() || c == '_' {
1480                            var.push(c);
1481                            chars.next();
1482                        } else {
1483                            break;
1484                        }
1485                    }
1486                }
1487            }
1488            spans.push(Span::styled(var, var_style));
1489            continue;
1490        }
1491        // 行内注释检测
1492        // 注意:chars.clone().collect() 包含当前 peek 的 ch,所以 rest 已经以 ch 开头
1493        if ch == '/' || ch == '#' || ch == '-' {
1494            let rest: String = chars.clone().collect();
1495            if rest.starts_with(comment_prefix) {
1496                if !buf.is_empty() {
1497                    spans.extend(colorize_tokens(
1498                        &buf,
1499                        keywords,
1500                        primitive_types,
1501                        go_type_names,
1502                        code_style,
1503                        kw_style,
1504                        num_style,
1505                        type_style,
1506                        primitive_style,
1507                        macro_style,
1508                        &lang_lower,
1509                    ));
1510                    buf.clear();
1511                }
1512                // 消耗掉所有剩余字符(包括当前 ch)
1513                while chars.peek().is_some() {
1514                    chars.next();
1515                }
1516                spans.push(Span::styled(rest, comment_style));
1517                break;
1518            }
1519        }
1520        buf.push(ch);
1521        chars.next();
1522    }
1523
1524    if !buf.is_empty() {
1525        spans.extend(colorize_tokens(
1526            &buf,
1527            keywords,
1528            primitive_types,
1529            go_type_names,
1530            code_style,
1531            kw_style,
1532            num_style,
1533            type_style,
1534            primitive_style,
1535            macro_style,
1536            &lang_lower,
1537        ));
1538    }
1539
1540    if spans.is_empty() {
1541        spans.push(Span::styled(line.to_string(), code_style));
1542    }
1543
1544    spans
1545}
1546
1547/// 将文本按照 word boundary 拆分并对关键字、数字、类型名、原始类型着色
1548pub fn colorize_tokens<'a>(
1549    text: &str,
1550    keywords: &[&str],
1551    primitive_types: &[&str],
1552    go_type_names: &[&str],
1553    default_style: Style,
1554    kw_style: Style,
1555    num_style: Style,
1556    type_style: Style,
1557    primitive_style: Style,
1558    macro_style: Style,
1559    lang: &str,
1560) -> Vec<Span<'static>> {
1561    let mut spans = Vec::new();
1562    let mut current_word = String::new();
1563    let mut current_non_word = String::new();
1564    let mut chars = text.chars().peekable();
1565
1566    while let Some(ch) = chars.next() {
1567        if ch.is_alphanumeric() || ch == '_' {
1568            if !current_non_word.is_empty() {
1569                spans.push(Span::styled(current_non_word.clone(), default_style));
1570                current_non_word.clear();
1571            }
1572            current_word.push(ch);
1573        } else {
1574            // Rust 宏调用高亮:word! 或 word!()
1575            if ch == '!' && matches!(lang, "rust" | "rs") && !current_word.is_empty() {
1576                // 检查后面是否跟着 ( 或 { 或 [
1577                let is_macro = chars
1578                    .peek()
1579                    .map(|&c| c == '(' || c == '{' || c == '[' || c.is_whitespace())
1580                    .unwrap_or(true);
1581                if is_macro {
1582                    // 将当前 word 作为宏名高亮
1583                    spans.push(Span::styled(current_word.clone(), macro_style));
1584                    current_word.clear();
1585                    spans.push(Span::styled("!".to_string(), macro_style));
1586                    continue;
1587                }
1588            }
1589            if !current_word.is_empty() {
1590                let style = classify_word(
1591                    &current_word,
1592                    keywords,
1593                    primitive_types,
1594                    go_type_names,
1595                    kw_style,
1596                    primitive_style,
1597                    num_style,
1598                    type_style,
1599                    default_style,
1600                    lang,
1601                );
1602                spans.push(Span::styled(current_word.clone(), style));
1603                current_word.clear();
1604            }
1605            current_non_word.push(ch);
1606        }
1607    }
1608
1609    // 刷新剩余
1610    if !current_non_word.is_empty() {
1611        spans.push(Span::styled(current_non_word, default_style));
1612    }
1613    if !current_word.is_empty() {
1614        let style = classify_word(
1615            &current_word,
1616            keywords,
1617            primitive_types,
1618            go_type_names,
1619            kw_style,
1620            primitive_style,
1621            num_style,
1622            type_style,
1623            default_style,
1624            lang,
1625        );
1626        spans.push(Span::styled(current_word, style));
1627    }
1628
1629    spans
1630}
1631
1632/// 根据语言规则判断一个 word 应该使用哪种颜色样式
1633pub fn classify_word(
1634    word: &str,
1635    keywords: &[&str],
1636    primitive_types: &[&str],
1637    go_type_names: &[&str],
1638    kw_style: Style,
1639    primitive_style: Style,
1640    num_style: Style,
1641    type_style: Style,
1642    default_style: Style,
1643    lang: &str,
1644) -> Style {
1645    if keywords.contains(&word) {
1646        kw_style
1647    } else if primitive_types.contains(&word) {
1648        primitive_style
1649    } else if word
1650        .chars()
1651        .next()
1652        .map(|c| c.is_ascii_digit())
1653        .unwrap_or(false)
1654    {
1655        num_style
1656    } else if matches!(lang, "go" | "golang") {
1657        // Go 语言:大写开头不代表类型,只有显式列表中的才高亮为类型色
1658        if go_type_names.contains(&word) {
1659            type_style
1660        } else {
1661            default_style
1662        }
1663    } else if word
1664        .chars()
1665        .next()
1666        .map(|c| c.is_uppercase())
1667        .unwrap_or(false)
1668    {
1669        // 其他语言:大写开头 → 类型名高亮
1670        type_style
1671    } else {
1672        default_style
1673    }
1674}