Skip to main content

j_cli/command/chat/
markdown.rs

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