ratatui_toolkit/widgets/markdown_widget/foundation/elements/
constants.rs

1//! Constants for markdown rendering styling.
2//!
3//! Contains icons, markers, and color constants used in markdown rendering.
4
5use ratatui::style::Color;
6
7/// Code block color theme
8#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
9pub enum CodeBlockTheme {
10    /// Ayu Dark theme (default) - warm orange/amber accents
11    #[default]
12    AyuDark,
13    /// GitHub Dark theme
14    GitHubDark,
15    /// Dracula theme - purple/pink tones
16    Dracula,
17    /// Nord theme - cool blue tones
18    Nord,
19    /// Monokai theme - warm vibrant colors
20    Monokai,
21    /// One Dark (Atom) theme
22    OneDark,
23    /// Gruvbox Dark theme
24    Gruvbox,
25    /// Tokyo Night theme
26    TokyoNight,
27    /// Catppuccin Mocha theme
28    Catppuccin,
29}
30
31/// Colors for a code block theme
32#[derive(Debug, Clone, Copy)]
33pub struct CodeBlockColors {
34    /// Border color
35    pub border: Color,
36    /// Background color
37    pub background: Color,
38    /// Header background
39    pub header_bg: Color,
40    /// Header text color
41    pub header_text: Color,
42    /// Icon/language color
43    pub icon: Color,
44    /// Line number color
45    pub line_number: Color,
46    /// Line number separator color
47    pub line_separator: Color,
48}
49
50impl CodeBlockTheme {
51    /// Get the colors for this theme
52    pub fn colors(&self) -> CodeBlockColors {
53        match self {
54            CodeBlockTheme::AyuDark => CodeBlockColors {
55                border: Color::Rgb(57, 63, 84),
56                background: Color::Rgb(10, 14, 20),
57                header_bg: Color::Rgb(15, 20, 28),
58                header_text: Color::Rgb(179, 186, 197),
59                icon: Color::Rgb(255, 180, 84), // Ayu orange
60                line_number: Color::Rgb(70, 80, 100),
61                line_separator: Color::Rgb(45, 52, 70),
62            },
63            CodeBlockTheme::GitHubDark => CodeBlockColors {
64                border: Color::Rgb(70, 70, 70),
65                background: Color::Rgb(30, 30, 30),
66                header_bg: Color::Rgb(40, 40, 40),
67                header_text: Color::Rgb(180, 180, 180),
68                icon: Color::Rgb(255, 200, 100),
69                line_number: Color::Rgb(90, 90, 90),
70                line_separator: Color::Rgb(60, 60, 60),
71            },
72            CodeBlockTheme::Dracula => CodeBlockColors {
73                border: Color::Rgb(98, 114, 164),
74                background: Color::Rgb(40, 42, 54),
75                header_bg: Color::Rgb(68, 71, 90),
76                header_text: Color::Rgb(248, 248, 242),
77                icon: Color::Rgb(255, 121, 198), // Pink
78                line_number: Color::Rgb(98, 114, 164),
79                line_separator: Color::Rgb(68, 71, 90),
80            },
81            CodeBlockTheme::Nord => CodeBlockColors {
82                border: Color::Rgb(76, 86, 106),
83                background: Color::Rgb(46, 52, 64),
84                header_bg: Color::Rgb(59, 66, 82),
85                header_text: Color::Rgb(216, 222, 233),
86                icon: Color::Rgb(136, 192, 208), // Frost blue
87                line_number: Color::Rgb(76, 86, 106),
88                line_separator: Color::Rgb(59, 66, 82),
89            },
90            CodeBlockTheme::Monokai => CodeBlockColors {
91                border: Color::Rgb(117, 113, 94),
92                background: Color::Rgb(39, 40, 34),
93                header_bg: Color::Rgb(54, 55, 49),
94                header_text: Color::Rgb(248, 248, 242),
95                icon: Color::Rgb(230, 219, 116), // Yellow
96                line_number: Color::Rgb(117, 113, 94),
97                line_separator: Color::Rgb(73, 72, 62),
98            },
99            CodeBlockTheme::OneDark => CodeBlockColors {
100                border: Color::Rgb(76, 82, 99),
101                background: Color::Rgb(40, 44, 52),
102                header_bg: Color::Rgb(50, 56, 66),
103                header_text: Color::Rgb(171, 178, 191),
104                icon: Color::Rgb(229, 192, 123), // Gold
105                line_number: Color::Rgb(76, 82, 99),
106                line_separator: Color::Rgb(58, 64, 76),
107            },
108            CodeBlockTheme::Gruvbox => CodeBlockColors {
109                border: Color::Rgb(146, 131, 116),
110                background: Color::Rgb(40, 40, 40),
111                header_bg: Color::Rgb(60, 56, 54),
112                header_text: Color::Rgb(235, 219, 178),
113                icon: Color::Rgb(250, 189, 47), // Yellow
114                line_number: Color::Rgb(146, 131, 116),
115                line_separator: Color::Rgb(80, 73, 69),
116            },
117            CodeBlockTheme::TokyoNight => CodeBlockColors {
118                border: Color::Rgb(61, 89, 161),
119                background: Color::Rgb(26, 27, 38),
120                header_bg: Color::Rgb(36, 40, 59),
121                header_text: Color::Rgb(169, 177, 214),
122                icon: Color::Rgb(125, 207, 255), // Cyan
123                line_number: Color::Rgb(61, 89, 161),
124                line_separator: Color::Rgb(41, 46, 66),
125            },
126            CodeBlockTheme::Catppuccin => CodeBlockColors {
127                border: Color::Rgb(127, 132, 156),
128                background: Color::Rgb(30, 30, 46),
129                header_bg: Color::Rgb(49, 50, 68),
130                header_text: Color::Rgb(205, 214, 244),
131                icon: Color::Rgb(249, 226, 175), // Yellow
132                line_number: Color::Rgb(127, 132, 156),
133                line_separator: Color::Rgb(69, 71, 90),
134            },
135        }
136    }
137
138    /// Get all available themes
139    pub fn all() -> &'static [CodeBlockTheme] {
140        &[
141            CodeBlockTheme::AyuDark,
142            CodeBlockTheme::GitHubDark,
143            CodeBlockTheme::Dracula,
144            CodeBlockTheme::Nord,
145            CodeBlockTheme::Monokai,
146            CodeBlockTheme::OneDark,
147            CodeBlockTheme::Gruvbox,
148            CodeBlockTheme::TokyoNight,
149            CodeBlockTheme::Catppuccin,
150        ]
151    }
152
153    /// Get the theme name
154    pub fn name(&self) -> &'static str {
155        match self {
156            CodeBlockTheme::AyuDark => "Ayu Dark",
157            CodeBlockTheme::GitHubDark => "GitHub Dark",
158            CodeBlockTheme::Dracula => "Dracula",
159            CodeBlockTheme::Nord => "Nord",
160            CodeBlockTheme::Monokai => "Monokai",
161            CodeBlockTheme::OneDark => "One Dark",
162            CodeBlockTheme::Gruvbox => "Gruvbox",
163            CodeBlockTheme::TokyoNight => "Tokyo Night",
164            CodeBlockTheme::Catppuccin => "Catppuccin",
165        }
166    }
167}
168
169/// Heading icons by level (matching render-markdown.nvim).
170pub const HEADING_ICONS: [&str; 6] = [
171    "󰲡 ", // H1
172    "󰲣 ", // H2
173    "󰲥 ", // H3
174    "󰲧 ", // H4
175    "󰲩 ", // H5
176    "󰲫 ", // H6
177];
178
179/// Bullet markers that cycle by nesting level (matching render-markdown.nvim).
180pub const BULLET_MARKERS: [&str; 4] = ["\u{25cf} ", "\u{25cb} ", "\u{25c6} ", "\u{25c7} "];
181
182/// Checkbox icons (matching render-markdown.nvim).
183pub const CHECKBOX_UNCHECKED: &str = "\u{f0131} "; // [ ]
184pub const CHECKBOX_CHECKED: &str = "\u{f0c52} "; // [x]
185pub const CHECKBOX_TODO: &str = "\u{f0954} "; // [-]
186
187/// Blockquote marker (matching render-markdown.nvim).
188pub const BLOCKQUOTE_MARKER: &str = "\u{258b}";
189
190/// Horizontal rule character.
191pub const HORIZONTAL_RULE_CHAR: char = '\u{2500}';
192
193/// Link icons (matching render-markdown.nvim).
194pub const LINK_ICON: &str = "\u{f0339} "; // Default hyperlink
195pub const IMAGE_ICON: &str = "\u{f0976} "; // Image
196pub const EMAIL_ICON: &str = "\u{f0013} "; // Email
197
198/// Domain-specific link icons.
199pub fn get_link_icon(url: &str) -> &'static str {
200    let url_lower = url.to_lowercase();
201    if url_lower.contains("github.com") {
202        "\u{f02a4} "
203    } else if url_lower.contains("gitlab.com") {
204        "\u{f0ba0} "
205    } else if url_lower.contains("discord.com") || url_lower.contains("discord.gg") {
206        "\u{f066f} "
207    } else if url_lower.contains("linkedin.com") {
208        "\u{f033b} "
209    } else if url_lower.contains("reddit.com") {
210        "\u{f044d} "
211    } else if url_lower.contains("slack.com") {
212        "\u{f04b1} "
213    } else if url_lower.contains("stackoverflow.com") {
214        "\u{f04cc} "
215    } else if url_lower.contains("twitter.com") || url_lower.contains("x.com") {
216        " "
217    } else if url_lower.contains("wikipedia.org") {
218        "\u{f05ac} "
219    } else if url_lower.contains("youtube.com") || url_lower.contains("youtu.be") {
220        "\u{f05c3} "
221    } else if url_lower.starts_with("mailto:") {
222        EMAIL_ICON
223    } else if url_lower.ends_with(".png")
224        || url_lower.ends_with(".jpg")
225        || url_lower.ends_with(".jpeg")
226        || url_lower.ends_with(".gif")
227        || url_lower.ends_with(".svg")
228        || url_lower.ends_with(".webp")
229    {
230        IMAGE_ICON
231    } else {
232        LINK_ICON
233    }
234}
235
236/// Language icons for code blocks (Nerd Font icons).
237pub fn get_language_icon(lang: &str) -> &'static str {
238    match lang.to_lowercase().as_str() {
239        // Systems Programming
240        "rust" | "rs" => "\u{e7a8} ",                //
241        "c" => "\u{e61e} ",                          //
242        "cpp" | "c++" | "cxx" | "cc" => "\u{e61d} ", //
243        "zig" => "\u{e6a9} ",                        //
244        "nim" => "\u{e677} ",                        //
245        "d" => "\u{e7af} ",                          //
246        "ada" => "\u{f1786} ",
247        "fortran" | "f90" | "f95" => "\u{f121a} ",
248        "assembly" | "asm" | "nasm" => "\u{e6ab} ", //
249
250        // Web Frontend
251        "javascript" | "js" | "mjs" | "cjs" => "\u{e74e} ", //
252        "typescript" | "ts" | "mts" | "cts" => "\u{e628} ", //
253        "jsx" | "tsx" | "react" => "\u{e7ba} ",             //
254        "html" | "htm" => "\u{e736} ",                      //
255        "css" => "\u{e749} ",                               //
256        "scss" | "sass" => "\u{e74b} ",                     //
257        "less" => "\u{e758} ",                              //
258        "svelte" => "\u{e697} ",                            //
259        "vue" => "\u{e6a0} ",                               //
260        "astro" => "\u{e6b6} ",                             //
261
262        // Web Backend / Scripting
263        "python" | "py" | "pyw" | "pyi" => "\u{f0320} ",
264        "ruby" | "rb" | "erb" => "\u{e739} ", //
265        "php" => "\u{e73d} ",                 //
266        "perl" | "pl" | "pm" => "\u{e769} ",  //
267        "lua" => "\u{e620} ",                 //
268        "r" | "rmd" => "\u{e68a} ",           //
269        "julia" => "\u{e624} ",               //
270
271        // JVM Languages
272        "java" => "\u{e738} ",                              //
273        "kotlin" | "kt" | "kts" => "\u{e634} ",             //
274        "scala" => "\u{e737} ",                             //
275        "groovy" => "\u{e775} ",                            //
276        "clojure" | "clj" | "cljs" | "cljc" => "\u{e76a} ", //
277
278        // .NET Languages
279        "csharp" | "cs" | "c#" => "\u{f031b} ",
280        "fsharp" | "fs" | "f#" => "\u{e7a7} ", //
281        "vb" | "vbnet" | "visualbasic" => "\u{f06e4} ",
282
283        // Functional Languages
284        "haskell" | "hs" => "\u{e777} ",        //
285        "elixir" | "ex" | "exs" => "\u{e62d} ", //
286        "erlang" | "erl" => "\u{e7b1} ",        //
287        "ocaml" | "ml" => "\u{e67a} ",          //
288        "elm" => "\u{e62c} ",                   //
289        "purescript" | "purs" => "\u{e630} ",   //
290        "racket" | "rkt" => "\u{f0627} ",
291        "scheme" | "scm" => "\u{f0627} ",
292        "lisp" | "cl" | "el" => "\u{f0172} ",
293
294        // Mobile
295        "swift" => "\u{e755} ",                     //
296        "objectivec" | "objc" | "m" => "\u{e61e} ", //
297        "dart" => "\u{e798} ",                      //
298
299        // Go & Modern
300        "go" | "golang" => "\u{e626} ", //
301        "v" | "vlang" => "\u{e6ac} ",   //
302        "crystal" => "\u{e7a3} ",       //
303        "odin" => "\u{f0b94} ",
304
305        // Shell & Scripting
306        "bash" | "sh" | "shell" | "zsh" | "fish" | "ksh" | "csh" => "\u{e795} ", //
307        "powershell" | "ps1" | "psm1" => "\u{f0a0a} ",
308        "batch" | "bat" | "cmd" => "\u{e629} ", //
309        "nushell" | "nu" => "\u{e795} ",        //
310
311        // Data & Config
312        "json" | "jsonc" | "json5" => "\u{e60b} ", //
313        "yaml" | "yml" => "\u{e6a8} ",             //
314        "toml" => "\u{e6b2} ",                     //
315        "xml" | "xsl" | "xslt" => "\u{f05c0} ",
316        "ini" | "conf" | "cfg" => "\u{e615} ", //
317        "env" | "dotenv" => "\u{e615} ",       //
318        "csv" => "\u{e64a} ",                  //
319
320        // Markup & Docs
321        "markdown" | "md" | "mdx" => "\u{e73e} ",  //
322        "latex" | "tex" => "\u{e69b} ",            //
323        "rst" | "restructuredtext" => "\u{e6a5} ", //
324        "asciidoc" | "adoc" => "\u{e6a5} ",        //
325        "org" => "\u{e633} ",                      //
326
327        // Database
328        "sql" | "mysql" | "postgresql" | "postgres" | "sqlite" => "\u{e706} ", //
329        "plsql" | "plpgsql" => "\u{e706} ",                                    //
330        "mongodb" | "mongo" => "\u{e7a4} ",                                    //
331        "redis" => "\u{e76d} ",                                                //
332        "graphql" | "gql" => "\u{e662} ",                                      //
333        "prisma" => "\u{e684} ",                                               //
334
335        // DevOps & Infrastructure
336        "docker" | "dockerfile" | "containerfile" => "\u{e7b0} ", //
337        "kubernetes" | "k8s" => "\u{f10fe} ",
338        "terraform" | "tf" | "hcl" => "\u{f1062} ",
339        "ansible" => "\u{e7b0} ", //
340        "vagrant" => "\u{e7b0} ", //
341        "nix" => "\u{e779} ",     //
342        "nginx" => "\u{e776} ",   //
343        "apache" => "\u{e769} ",  //
344
345        // Build & Package
346        "makefile" | "make" | "mk" => "\u{e779} ", //
347        "cmake" => "\u{e615} ",                    //
348        "gradle" => "\u{e660} ",                   //
349        "maven" | "pom" => "\u{e674} ",            //
350        "cargo" => "\u{e7a8} ",                    //  (Rust)
351        "npm" | "package.json" => "\u{e71e} ",     //
352        "yarn" => "\u{e6a7} ",                     //
353        "pnpm" => "\u{e71e} ",                     //
354        "pip" | "requirements" => "\u{f0320} ",
355
356        // Version Control
357        "git" | "gitignore" | "gitconfig" | "gitattributes" => "\u{e702} ", //
358        "diff" | "patch" => "\u{e728} ",                                    //
359
360        // Editor & IDE
361        "vim" | "vimrc" | "neovim" | "nvim" => "\u{e62b} ", //
362        "emacs" | "elisp" => "\u{e632} ",                   //
363        "vscode" | "code" => "\u{f0a1e} ",
364
365        // Other Languages
366        "solidity" | "sol" => "\u{e6ac} ", //
367        "move" => "\u{f01a7} ",
368        "cairo" => "\u{f0725} ",
369        "wasm" | "wat" | "webassembly" => "\u{e6a1} ", //
370        "llvm" | "ir" => "\u{e61e} ",                  //
371        "cuda" | "cu" => "\u{e61e} ",                  //
372        "opencl" => "\u{e61e} ",                       //
373        "glsl" | "hlsl" | "shader" => "\u{e6ad} ",     //
374        "proto" | "protobuf" => "\u{e6ae} ",           //
375        "thrift" => "\u{e6ae} ",                       //
376        "avro" => "\u{e6ae} ",                         //
377        "capnp" => "\u{e6ae} ",                        //
378        "flatbuffers" | "fbs" => "\u{e6ae} ",          //
379
380        // Misc
381        "regex" | "regexp" => "\u{e656} ",       //
382        "http" | "rest" => "\u{e60c} ",          //
383        "binary" | "hex" => "\u{e7a3} ",         //
384        "log" | "logs" => "\u{e714} ",           //
385        "text" | "txt" | "plain" => "\u{e612} ", //
386
387        // Default
388        _ => "\u{e612} ", //
389    }
390}
391
392/// Background colors for heading levels.
393pub fn heading_bg_color(level: u8) -> Color {
394    match level {
395        1 => Color::Rgb(80, 40, 80), // Purple-ish
396        2 => Color::Rgb(40, 60, 80), // Blue-ish
397        3 => Color::Rgb(40, 80, 60), // Green-ish
398        4 => Color::Rgb(80, 60, 40), // Orange-ish
399        5 => Color::Rgb(60, 60, 60), // Gray
400        6 => Color::Rgb(50, 50, 50), // Darker gray
401        _ => Color::Rgb(50, 50, 50),
402    }
403}
404
405/// Foreground colors for heading levels.
406pub fn heading_fg_color(level: u8) -> Color {
407    match level {
408        1 => Color::Rgb(255, 180, 255), // Bright magenta
409        2 => Color::Rgb(130, 180, 255), // Bright blue
410        3 => Color::Rgb(130, 255, 180), // Bright cyan
411        4 => Color::Rgb(255, 200, 130), // Bright orange
412        5 => Color::Rgb(200, 200, 200), // Light gray
413        6 => Color::Rgb(170, 170, 170), // Gray
414        _ => Color::White,
415    }
416}