Skip to main content

j_cli/command/chat/markdown/
highlight.rs

1use super::super::theme::Theme;
2use ratatui::{
3    style::{Modifier, Style},
4    text::Span,
5};
6
7/// 简单的代码语法高亮(无需外部依赖)
8/// 根据语言类型对常见关键字、字符串、注释、数字进行着色
9pub fn highlight_code_line<'a>(line: &'a str, lang: &str, theme: &Theme) -> Vec<Span<'static>> {
10    let lang_lower = lang.to_lowercase();
11    // Rust 使用多组词汇分别高亮
12    // keywords: 控制流/定义关键字 → 紫色
13    // primitive_types: 原始类型 → 青绿色
14    // 其他类型名(大写开头)自动通过 type_style 高亮 → 暖黄色
15    // 宏调用(word!)通过 macro_style 高亮 → 淡蓝色
16    let keywords: &[&str] = match lang_lower.as_str() {
17        "rust" | "rs" => &[
18            // 控制流/定义关键字(紫色)
19            "fn", "let", "mut", "pub", "use", "mod", "struct", "enum", "impl", "trait", "for",
20            "while", "loop", "if", "else", "match", "return", "self", "Self", "where", "async",
21            "await", "move", "ref", "type", "const", "static", "crate", "super", "as", "in",
22            "true", "false", "unsafe", "extern", "dyn", "abstract", "become", "box", "do", "final",
23            "macro", "override", "priv", "typeof", "unsized", "virtual", "yield", "union", "break",
24            "continue",
25        ],
26        "python" | "py" => &[
27            "def", "class", "return", "if", "elif", "else", "for", "while", "import", "from", "as",
28            "with", "try", "except", "finally", "raise", "pass", "break", "continue", "yield",
29            "lambda", "and", "or", "not", "in", "is", "True", "False", "None", "global",
30            "nonlocal", "assert", "del", "async", "await", "self", "print",
31        ],
32        "javascript" | "js" | "typescript" | "ts" | "jsx" | "tsx" => &[
33            "function",
34            "const",
35            "let",
36            "var",
37            "return",
38            "if",
39            "else",
40            "for",
41            "while",
42            "class",
43            "new",
44            "this",
45            "import",
46            "export",
47            "from",
48            "default",
49            "async",
50            "await",
51            "try",
52            "catch",
53            "finally",
54            "throw",
55            "typeof",
56            "instanceof",
57            "true",
58            "false",
59            "null",
60            "undefined",
61            "of",
62            "in",
63            "switch",
64            "case",
65        ],
66        "go" | "golang" => &[
67            "func",
68            "package",
69            "import",
70            "return",
71            "if",
72            "else",
73            "for",
74            "range",
75            "struct",
76            "interface",
77            "type",
78            "var",
79            "const",
80            "defer",
81            "go",
82            "chan",
83            "select",
84            "case",
85            "switch",
86            "default",
87            "break",
88            "continue",
89            "map",
90            "true",
91            "false",
92            "nil",
93            "make",
94            "append",
95            "len",
96            "cap",
97        ],
98        "java" | "kotlin" | "kt" => &[
99            "public",
100            "private",
101            "protected",
102            "class",
103            "interface",
104            "extends",
105            "implements",
106            "return",
107            "if",
108            "else",
109            "for",
110            "while",
111            "new",
112            "this",
113            "import",
114            "package",
115            "static",
116            "final",
117            "void",
118            "int",
119            "String",
120            "boolean",
121            "true",
122            "false",
123            "null",
124            "try",
125            "catch",
126            "throw",
127            "throws",
128            "fun",
129            "val",
130            "var",
131            "when",
132            "object",
133            "companion",
134        ],
135        "sh" | "bash" | "zsh" | "shell" => &[
136            "if",
137            "then",
138            "else",
139            "elif",
140            "fi",
141            "for",
142            "while",
143            "do",
144            "done",
145            "case",
146            "esac",
147            "function",
148            "return",
149            "exit",
150            "echo",
151            "export",
152            "local",
153            "readonly",
154            "set",
155            "unset",
156            "shift",
157            "source",
158            "in",
159            "true",
160            "false",
161            "read",
162            "declare",
163            "typeset",
164            "trap",
165            "eval",
166            "exec",
167            "test",
168            "select",
169            "until",
170            "break",
171            "continue",
172            "printf",
173            // Go 命令
174            "go",
175            "build",
176            "run",
177            "test",
178            "fmt",
179            "vet",
180            "mod",
181            "get",
182            "install",
183            "clean",
184            "doc",
185            "list",
186            "version",
187            "env",
188            "generate",
189            "tool",
190            "proxy",
191            "GOPATH",
192            "GOROOT",
193            "GOBIN",
194            "GOMODCACHE",
195            "GOPROXY",
196            "GOSUMDB",
197            // Cargo 命令
198            "cargo",
199            "new",
200            "init",
201            "add",
202            "remove",
203            "update",
204            "check",
205            "clippy",
206            "rustfmt",
207            "rustc",
208            "rustup",
209            "publish",
210            "uninstall",
211            "search",
212            "tree",
213            "locate_project",
214            "metadata",
215            "audit",
216            "watch",
217            "expand",
218        ],
219        "c" | "cpp" | "c++" | "h" | "hpp" => &[
220            "int",
221            "char",
222            "float",
223            "double",
224            "void",
225            "long",
226            "short",
227            "unsigned",
228            "signed",
229            "const",
230            "static",
231            "extern",
232            "struct",
233            "union",
234            "enum",
235            "typedef",
236            "sizeof",
237            "return",
238            "if",
239            "else",
240            "for",
241            "while",
242            "do",
243            "switch",
244            "case",
245            "break",
246            "continue",
247            "default",
248            "goto",
249            "auto",
250            "register",
251            "volatile",
252            "class",
253            "public",
254            "private",
255            "protected",
256            "virtual",
257            "override",
258            "template",
259            "namespace",
260            "using",
261            "new",
262            "delete",
263            "try",
264            "catch",
265            "throw",
266            "nullptr",
267            "true",
268            "false",
269            "this",
270            "include",
271            "define",
272            "ifdef",
273            "ifndef",
274            "endif",
275        ],
276        "sql" => &[
277            "SELECT",
278            "FROM",
279            "WHERE",
280            "INSERT",
281            "UPDATE",
282            "DELETE",
283            "CREATE",
284            "DROP",
285            "ALTER",
286            "TABLE",
287            "INDEX",
288            "INTO",
289            "VALUES",
290            "SET",
291            "AND",
292            "OR",
293            "NOT",
294            "NULL",
295            "JOIN",
296            "LEFT",
297            "RIGHT",
298            "INNER",
299            "OUTER",
300            "ON",
301            "GROUP",
302            "BY",
303            "ORDER",
304            "ASC",
305            "DESC",
306            "HAVING",
307            "LIMIT",
308            "OFFSET",
309            "UNION",
310            "AS",
311            "DISTINCT",
312            "COUNT",
313            "SUM",
314            "AVG",
315            "MIN",
316            "MAX",
317            "LIKE",
318            "IN",
319            "BETWEEN",
320            "EXISTS",
321            "CASE",
322            "WHEN",
323            "THEN",
324            "ELSE",
325            "END",
326            "BEGIN",
327            "COMMIT",
328            "ROLLBACK",
329            "PRIMARY",
330            "KEY",
331            "FOREIGN",
332            "REFERENCES",
333            "select",
334            "from",
335            "where",
336            "insert",
337            "update",
338            "delete",
339            "create",
340            "drop",
341            "alter",
342            "table",
343            "index",
344            "into",
345            "values",
346            "set",
347            "and",
348            "or",
349            "not",
350            "null",
351            "join",
352            "left",
353            "right",
354            "inner",
355            "outer",
356            "on",
357            "group",
358            "by",
359            "order",
360            "asc",
361            "desc",
362            "having",
363            "limit",
364            "offset",
365            "union",
366            "as",
367            "distinct",
368            "count",
369            "sum",
370            "avg",
371            "min",
372            "max",
373            "like",
374            "in",
375            "between",
376            "exists",
377            "case",
378            "when",
379            "then",
380            "else",
381            "end",
382            "begin",
383            "commit",
384            "rollback",
385            "primary",
386            "key",
387            "foreign",
388            "references",
389        ],
390        "yaml" | "yml" => &["true", "false", "null", "yes", "no", "on", "off"],
391        "toml" => &[
392            "true",
393            "false",
394            // Cargo.toml 常用
395            "name",
396            "version",
397            "edition",
398            "authors",
399            "dependencies",
400            "dev-dependencies",
401            "build-dependencies",
402            "features",
403            "workspace",
404            "members",
405            "exclude",
406            "include",
407            "path",
408            "git",
409            "branch",
410            "tag",
411            "rev",
412            "package",
413            "lib",
414            "bin",
415            "example",
416            "test",
417            "bench",
418            "doc",
419            "profile",
420            "release",
421            "debug",
422            "opt-level",
423            "lto",
424            "codegen-units",
425            "panic",
426            "strip",
427            "default",
428            "optional",
429            // 常见配置项
430            "repository",
431            "homepage",
432            "documentation",
433            "license",
434            "license-file",
435            "keywords",
436            "categories",
437            "readme",
438            "description",
439            "resolver",
440        ],
441        "css" | "scss" | "less" => &[
442            "color",
443            "background",
444            "border",
445            "margin",
446            "padding",
447            "display",
448            "position",
449            "width",
450            "height",
451            "font",
452            "text",
453            "flex",
454            "grid",
455            "align",
456            "justify",
457            "important",
458            "none",
459            "auto",
460            "inherit",
461            "initial",
462            "unset",
463        ],
464        "dockerfile" | "docker" => &[
465            "FROM",
466            "RUN",
467            "CMD",
468            "LABEL",
469            "EXPOSE",
470            "ENV",
471            "ADD",
472            "COPY",
473            "ENTRYPOINT",
474            "VOLUME",
475            "USER",
476            "WORKDIR",
477            "ARG",
478            "ONBUILD",
479            "STOPSIGNAL",
480            "HEALTHCHECK",
481            "SHELL",
482            "AS",
483        ],
484        "ruby" | "rb" => &[
485            "def", "end", "class", "module", "if", "elsif", "else", "unless", "while", "until",
486            "for", "do", "begin", "rescue", "ensure", "raise", "return", "yield", "require",
487            "include", "attr", "self", "true", "false", "nil", "puts", "print",
488        ],
489        _ => &[
490            "fn", "function", "def", "class", "return", "if", "else", "for", "while", "import",
491            "export", "const", "let", "var", "true", "false", "null", "nil", "None", "self",
492            "this",
493        ],
494    };
495
496    // 原始/内建类型列表(青绿色)
497    let primitive_types: &[&str] = match lang_lower.as_str() {
498        "rust" | "rs" => &[
499            "i8", "i16", "i32", "i64", "i128", "isize", "u8", "u16", "u32", "u64", "u128", "usize",
500            "f32", "f64", "bool", "char", "str",
501        ],
502        "go" | "golang" => &[
503            "int",
504            "int8",
505            "int16",
506            "int32",
507            "int64",
508            "uint",
509            "uint8",
510            "uint16",
511            "uint32",
512            "uint64",
513            "uintptr",
514            "float32",
515            "float64",
516            "complex64",
517            "complex128",
518            "bool",
519            "byte",
520            "rune",
521            "string",
522            "error",
523            "any",
524        ],
525        _ => &[],
526    };
527
528    // Go 语言显式类型名列表(暖黄色,因为 Go 的大写开头不代表类型)
529    let go_type_names: &[&str] = match lang_lower.as_str() {
530        "go" | "golang" => &[
531            "Reader",
532            "Writer",
533            "Closer",
534            "ReadWriter",
535            "ReadCloser",
536            "WriteCloser",
537            "ReadWriteCloser",
538            "Seeker",
539            "Context",
540            "Error",
541            "Stringer",
542            "Mutex",
543            "RWMutex",
544            "WaitGroup",
545            "Once",
546            "Pool",
547            "Map",
548            "Duration",
549            "Time",
550            "Timer",
551            "Ticker",
552            "Buffer",
553            "Builder",
554            "Request",
555            "Response",
556            "ResponseWriter",
557            "Handler",
558            "HandlerFunc",
559            "Server",
560            "Client",
561            "Transport",
562            "File",
563            "FileInfo",
564            "FileMode",
565            "Decoder",
566            "Encoder",
567            "Marshaler",
568            "Unmarshaler",
569            "Logger",
570            "Flag",
571            "Regexp",
572            "Conn",
573            "Listener",
574            "Addr",
575            "Scanner",
576            "Token",
577            "Type",
578            "Value",
579            "Kind",
580            "Cmd",
581            "Signal",
582        ],
583        _ => &[],
584    };
585
586    let comment_prefix = match lang_lower.as_str() {
587        "python" | "py" | "sh" | "bash" | "zsh" | "shell" | "ruby" | "rb" | "yaml" | "yml"
588        | "toml" | "dockerfile" | "docker" => "#",
589        "sql" => "--",
590        "css" | "scss" | "less" => "/*",
591        _ => "//",
592    };
593
594    // ===== 代码高亮配色方案(基于主题)=====
595    let code_style = Style::default().fg(theme.code_default);
596    let kw_style = Style::default().fg(theme.code_keyword);
597    let str_style = Style::default().fg(theme.code_string);
598    let comment_style = Style::default()
599        .fg(theme.code_comment)
600        .add_modifier(Modifier::ITALIC);
601    let num_style = Style::default().fg(theme.code_number);
602    let type_style = Style::default().fg(theme.code_type);
603    let primitive_style = Style::default().fg(theme.code_primitive);
604    let macro_style = Style::default().fg(theme.code_macro);
605
606    let trimmed = line.trim_start();
607
608    // 注释行
609    if trimmed.starts_with(comment_prefix) {
610        return vec![Span::styled(line.to_string(), comment_style)];
611    }
612
613    // 逐词解析
614    let mut spans = Vec::new();
615    let mut chars = line.chars().peekable();
616    let mut buf = String::new();
617
618    while let Some(&ch) = chars.peek() {
619        // 双引号字符串(支持 \ 转义)
620        if ch == '"' {
621            if !buf.is_empty() {
622                spans.extend(colorize_tokens(
623                    &buf,
624                    keywords,
625                    primitive_types,
626                    go_type_names,
627                    code_style,
628                    kw_style,
629                    num_style,
630                    type_style,
631                    primitive_style,
632                    macro_style,
633                    &lang_lower,
634                ));
635                buf.clear();
636            }
637            let mut s = String::new();
638            s.push(ch);
639            chars.next();
640            let mut escaped = false;
641            while let Some(&c) = chars.peek() {
642                s.push(c);
643                chars.next();
644                if escaped {
645                    escaped = false;
646                    continue;
647                }
648                if c == '\\' {
649                    escaped = true;
650                    continue;
651                }
652                if c == '"' {
653                    break;
654                }
655            }
656            spans.push(Span::styled(s, str_style));
657            continue;
658        }
659        // 反引号字符串(不支持转义,遇到配对反引号结束)
660        if ch == '`' {
661            if !buf.is_empty() {
662                spans.extend(colorize_tokens(
663                    &buf,
664                    keywords,
665                    primitive_types,
666                    go_type_names,
667                    code_style,
668                    kw_style,
669                    num_style,
670                    type_style,
671                    primitive_style,
672                    macro_style,
673                    &lang_lower,
674                ));
675                buf.clear();
676            }
677            let mut s = String::new();
678            s.push(ch);
679            chars.next();
680            while let Some(&c) = chars.peek() {
681                s.push(c);
682                chars.next();
683                if c == '`' {
684                    break;
685                }
686            }
687            spans.push(Span::styled(s, str_style));
688            continue;
689        }
690        // Rust 生命周期参数 ('a, 'static 等) vs 字符字面量 ('x')
691        if ch == '\'' && matches!(lang_lower.as_str(), "rust" | "rs") {
692            if !buf.is_empty() {
693                spans.extend(colorize_tokens(
694                    &buf,
695                    keywords,
696                    primitive_types,
697                    go_type_names,
698                    code_style,
699                    kw_style,
700                    num_style,
701                    type_style,
702                    primitive_style,
703                    macro_style,
704                    &lang_lower,
705                ));
706                buf.clear();
707            }
708            let mut s = String::new();
709            s.push(ch);
710            chars.next();
711            let mut is_lifetime = false;
712            while let Some(&c) = chars.peek() {
713                if c.is_alphanumeric() || c == '_' {
714                    s.push(c);
715                    chars.next();
716                } else if c == '\'' && s.len() == 2 {
717                    s.push(c);
718                    chars.next();
719                    break;
720                } else {
721                    is_lifetime = true;
722                    break;
723                }
724            }
725            if is_lifetime || (s.len() > 1 && !s.ends_with('\'')) {
726                let lifetime_style = Style::default().fg(theme.code_lifetime);
727                spans.push(Span::styled(s, lifetime_style));
728            } else {
729                spans.push(Span::styled(s, str_style));
730            }
731            continue;
732        }
733        // 其他语言的字符串(包含单引号)
734        if ch == '\'' && !matches!(lang_lower.as_str(), "rust" | "rs") {
735            if !buf.is_empty() {
736                spans.extend(colorize_tokens(
737                    &buf,
738                    keywords,
739                    primitive_types,
740                    go_type_names,
741                    code_style,
742                    kw_style,
743                    num_style,
744                    type_style,
745                    primitive_style,
746                    macro_style,
747                    &lang_lower,
748                ));
749                buf.clear();
750            }
751            let mut s = String::new();
752            s.push(ch);
753            chars.next();
754            let mut escaped = false;
755            while let Some(&c) = chars.peek() {
756                s.push(c);
757                chars.next();
758                if escaped {
759                    escaped = false;
760                    continue;
761                }
762                if c == '\\' {
763                    escaped = true;
764                    continue;
765                }
766                if c == '\'' {
767                    break;
768                }
769            }
770            spans.push(Span::styled(s, str_style));
771            continue;
772        }
773        // Rust 属性 (#[...] 或 #![...])
774        if ch == '#' && matches!(lang_lower.as_str(), "rust" | "rs") {
775            let mut lookahead = chars.clone();
776            if let Some(next) = lookahead.next() {
777                if next == '[' {
778                    if !buf.is_empty() {
779                        spans.extend(colorize_tokens(
780                            &buf,
781                            keywords,
782                            primitive_types,
783                            go_type_names,
784                            code_style,
785                            kw_style,
786                            num_style,
787                            type_style,
788                            primitive_style,
789                            macro_style,
790                            &lang_lower,
791                        ));
792                        buf.clear();
793                    }
794                    let mut attr = String::new();
795                    attr.push(ch);
796                    chars.next();
797                    let mut depth = 0;
798                    while let Some(&c) = chars.peek() {
799                        attr.push(c);
800                        chars.next();
801                        if c == '[' {
802                            depth += 1;
803                        } else if c == ']' {
804                            depth -= 1;
805                            if depth == 0 {
806                                break;
807                            }
808                        }
809                    }
810                    let attr_style = Style::default().fg(theme.code_attribute);
811                    spans.push(Span::styled(attr, attr_style));
812                    continue;
813                }
814            }
815        }
816        // Shell 变量 ($VAR, ${VAR}, $1 等)
817        if ch == '$'
818            && matches!(
819                lang_lower.as_str(),
820                "sh" | "bash" | "zsh" | "shell" | "dockerfile" | "docker"
821            )
822        {
823            if !buf.is_empty() {
824                spans.extend(colorize_tokens(
825                    &buf,
826                    keywords,
827                    primitive_types,
828                    go_type_names,
829                    code_style,
830                    kw_style,
831                    num_style,
832                    type_style,
833                    primitive_style,
834                    macro_style,
835                    &lang_lower,
836                ));
837                buf.clear();
838            }
839            let var_style = Style::default().fg(theme.code_shell_var);
840            let mut var = String::new();
841            var.push(ch);
842            chars.next();
843            if let Some(&next_ch) = chars.peek() {
844                if next_ch == '{' {
845                    var.push(next_ch);
846                    chars.next();
847                    while let Some(&c) = chars.peek() {
848                        var.push(c);
849                        chars.next();
850                        if c == '}' {
851                            break;
852                        }
853                    }
854                } else if next_ch == '(' {
855                    var.push(next_ch);
856                    chars.next();
857                    let mut depth = 1;
858                    while let Some(&c) = chars.peek() {
859                        var.push(c);
860                        chars.next();
861                        if c == '(' {
862                            depth += 1;
863                        }
864                        if c == ')' {
865                            depth -= 1;
866                            if depth == 0 {
867                                break;
868                            }
869                        }
870                    }
871                } else if next_ch.is_alphanumeric()
872                    || next_ch == '_'
873                    || next_ch == '@'
874                    || next_ch == '#'
875                    || next_ch == '?'
876                    || next_ch == '!'
877                {
878                    while let Some(&c) = chars.peek() {
879                        if c.is_alphanumeric() || c == '_' {
880                            var.push(c);
881                            chars.next();
882                        } else {
883                            break;
884                        }
885                    }
886                }
887            }
888            spans.push(Span::styled(var, var_style));
889            continue;
890        }
891        // 行内注释检测
892        if ch == '/' || ch == '#' || ch == '-' {
893            let rest: String = chars.clone().collect();
894            if rest.starts_with(comment_prefix) {
895                if !buf.is_empty() {
896                    spans.extend(colorize_tokens(
897                        &buf,
898                        keywords,
899                        primitive_types,
900                        go_type_names,
901                        code_style,
902                        kw_style,
903                        num_style,
904                        type_style,
905                        primitive_style,
906                        macro_style,
907                        &lang_lower,
908                    ));
909                    buf.clear();
910                }
911                while chars.peek().is_some() {
912                    chars.next();
913                }
914                spans.push(Span::styled(rest, comment_style));
915                break;
916            }
917        }
918        buf.push(ch);
919        chars.next();
920    }
921
922    if !buf.is_empty() {
923        spans.extend(colorize_tokens(
924            &buf,
925            keywords,
926            primitive_types,
927            go_type_names,
928            code_style,
929            kw_style,
930            num_style,
931            type_style,
932            primitive_style,
933            macro_style,
934            &lang_lower,
935        ));
936    }
937
938    if spans.is_empty() {
939        spans.push(Span::styled(line.to_string(), code_style));
940    }
941
942    spans
943}
944
945/// 将文本按照 word boundary 拆分并对关键字、数字、类型名、原始类型着色
946pub fn colorize_tokens<'a>(
947    text: &str,
948    keywords: &[&str],
949    primitive_types: &[&str],
950    go_type_names: &[&str],
951    default_style: Style,
952    kw_style: Style,
953    num_style: Style,
954    type_style: Style,
955    primitive_style: Style,
956    macro_style: Style,
957    lang: &str,
958) -> Vec<Span<'static>> {
959    let mut spans = Vec::new();
960    let mut current_word = String::new();
961    let mut current_non_word = String::new();
962    let mut chars = text.chars().peekable();
963
964    while let Some(ch) = chars.next() {
965        if ch.is_alphanumeric() || ch == '_' {
966            if !current_non_word.is_empty() {
967                spans.push(Span::styled(current_non_word.clone(), default_style));
968                current_non_word.clear();
969            }
970            current_word.push(ch);
971        } else {
972            // Rust 宏调用高亮:word! 或 word!()
973            if ch == '!' && matches!(lang, "rust" | "rs") && !current_word.is_empty() {
974                let is_macro = chars
975                    .peek()
976                    .map(|&c| c == '(' || c == '{' || c == '[' || c.is_whitespace())
977                    .unwrap_or(true);
978                if is_macro {
979                    spans.push(Span::styled(current_word.clone(), macro_style));
980                    current_word.clear();
981                    spans.push(Span::styled("!".to_string(), macro_style));
982                    continue;
983                }
984            }
985            if !current_word.is_empty() {
986                let style = classify_word(
987                    &current_word,
988                    keywords,
989                    primitive_types,
990                    go_type_names,
991                    kw_style,
992                    primitive_style,
993                    num_style,
994                    type_style,
995                    default_style,
996                    lang,
997                );
998                spans.push(Span::styled(current_word.clone(), style));
999                current_word.clear();
1000            }
1001            current_non_word.push(ch);
1002        }
1003    }
1004
1005    // 刷新剩余
1006    if !current_non_word.is_empty() {
1007        spans.push(Span::styled(current_non_word, default_style));
1008    }
1009    if !current_word.is_empty() {
1010        let style = classify_word(
1011            &current_word,
1012            keywords,
1013            primitive_types,
1014            go_type_names,
1015            kw_style,
1016            primitive_style,
1017            num_style,
1018            type_style,
1019            default_style,
1020            lang,
1021        );
1022        spans.push(Span::styled(current_word, style));
1023    }
1024
1025    spans
1026}
1027
1028/// 根据语言规则判断一个 word 应该使用哪种颜色样式
1029pub fn classify_word(
1030    word: &str,
1031    keywords: &[&str],
1032    primitive_types: &[&str],
1033    go_type_names: &[&str],
1034    kw_style: Style,
1035    primitive_style: Style,
1036    num_style: Style,
1037    type_style: Style,
1038    default_style: Style,
1039    lang: &str,
1040) -> Style {
1041    if keywords.contains(&word) {
1042        kw_style
1043    } else if primitive_types.contains(&word) {
1044        primitive_style
1045    } else if word
1046        .chars()
1047        .next()
1048        .map(|c| c.is_ascii_digit())
1049        .unwrap_or(false)
1050    {
1051        num_style
1052    } else if matches!(lang, "go" | "golang") {
1053        if go_type_names.contains(&word) {
1054            type_style
1055        } else {
1056            default_style
1057        }
1058    } else if word
1059        .chars()
1060        .next()
1061        .map(|c| c.is_uppercase())
1062        .unwrap_or(false)
1063    {
1064        type_style
1065    } else {
1066        default_style
1067    }
1068}