diff --git a/src/main.rs b/src/main.rs index 53e1676..37df30b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,6 +10,7 @@ use mime_guess::from_path; use notify::{Config, EventKind, RecommendedWatcher, RecursiveMode, Watcher}; use rand::seq::SliceRandom; use std::convert::Infallible; +use std::ffi::OsStr; use std::fmt::Write; use std::net::{SocketAddr, ToSocketAddrs}; use std::path::PathBuf; @@ -30,6 +31,9 @@ use std::io; mod markdown; use markdown::markdown_to_html; +mod other; +use other::code_to_html; + const TEMPLATE_FILE: &str = include_str!("../templates/file.html"); const TEMPLATE_DIR: &str = include_str!("../templates/dir.html"); @@ -246,7 +250,17 @@ async fn serve_file( "/".to_string() }; - let html_content = markdown_to_html(&content, &state.syntax_set, &state.theme_set, &full_path); + let html_content = if let Some(extension) = safe_path.extension() { + if extension == OsStr::new("md") { + markdown_to_html(&content, &state.syntax_set, &state.theme_set, &full_path) + } else if let Some(ext_str) = extension.to_str() { + code_to_html(&content, ext_str, &state.syntax_set, &state.theme_set) + } else { + markdown_to_html(&content, &state.syntax_set, &state.theme_set, &full_path) + } + } else { + markdown_to_html(&content, &state.syntax_set, &state.theme_set, &full_path) + }; let filename: String = if let Some(filename) = PathBuf::from(&full_path).file_name() { filename.to_str().unwrap_or("Markdown Preview").to_string() diff --git a/src/other.rs b/src/other.rs new file mode 100644 index 0000000..02f279c --- /dev/null +++ b/src/other.rs @@ -0,0 +1,90 @@ +use syntect::easy::HighlightLines; +use syntect::highlighting::ThemeSet; +use syntect::html::{IncludeBackground, styled_line_to_highlighted_html}; +use syntect::parsing::SyntaxSet; + +/// Преобразует исходный код и его язык в подсвеченный HTML. +/// +/// # Аргументы +/// * `code` - Исходный код как строка. +/// * `lang` - Идентификатор языка (например, "rust", "python", "mermaid"). +/// * `ss` - Набор синтаксисов (SyntaxSet). +/// * `ts` - Набор тем (ThemeSet). +/// +/// # Возвращает +/// Строку HTML, содержащую обертку блока кода с заголовком и кнопкой копирования. +pub fn code_to_html(code: &str, lang: &str, ss: &SyntaxSet, ts: &ThemeSet) -> String { + // Используем тему по умолчанию из вашего примера + let theme = &ts.themes["base16-ocean.dark"]; + + // Проверка на Mermaid диаграммы + if lang == "mermaid" { + let escaped_code = escape_html(code); + return format!( + r#"
+
+ Mermaid Diagram + +
+
{escaped_code}
+
"# + ); + } + + // Логика подсветки синтаксиса + let lang_display = if lang.is_empty() { "text" } else { lang }; + let lang_escaped = escape_html(lang_display); + + let highlighted_html = if !lang.is_empty() { + if let Some(syntax) = ss.find_syntax_by_token(lang) { + let mut h = HighlightLines::new(syntax, theme); + let mut result_html = String::new(); + + // Разбиваем код на строки для построчной подсветки + for line in code.lines() { + // Добавляем перевод строки обратно, так как highlight_line ожидает полную строку + let line_with_newline = format!("{line}\n"); + + match h.highlight_line(&line_with_newline, ss) { + Ok(regions) => { + // styled_line_to_highlighted_html может вернуть ошибку, если что-то пойдет не так + match styled_line_to_highlighted_html(®ions[..], IncludeBackground::No) { + Ok(html_line) => result_html.push_str(&html_line), + Err(_) => result_html.push_str(&escape_html(&line_with_newline)), + } + } + Err(_) => { + // Если ошибка подсветки, просто экранируем строку + result_html.push_str(&escape_html(&line_with_newline)); + } + } + } + result_html + } else { + // Если язык не найден в наборе синтаксисов, просто экранируем весь код + escape_html(code) + } + } else { + // Если язык не указан, просто экранируем + escape_html(code) + }; + + // Формирование финального HTML контейнера + format!( + r#"
+
+ {lang_escaped} + +
+
{highlighted_html}
+
"# + ) +} + +fn escape_html(text: &str) -> String { + text.replace('&', "&") + .replace('<', "<") + .replace('>', ">") + .replace('"', """) + .replace('\'', "'") +}