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