From a92844b8e50145cd8ce9feedfb7935e044096ec2 Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Sat, 28 Feb 2026 22:53:15 -0500 Subject: [PATCH] Add syntax highlighter. --- Cargo.lock | 1 + libclide/Cargo.toml | 1 + libclide/src/fs.rs | 17 ++++++++ libclide/src/theme.rs | 1 + libclide/src/theme/highlighter.rs | 66 +++++++++++++++++++++++++++++++ src/gui/filesystem.rs | 58 +++++---------------------- 6 files changed, 96 insertions(+), 48 deletions(-) create mode 100644 libclide/src/theme/highlighter.rs diff --git a/Cargo.lock b/Cargo.lock index 2bb2160..4b6c437 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1174,6 +1174,7 @@ dependencies = [ "libclide-macros", "log", "strum", + "syntect", ] [[package]] diff --git a/libclide/Cargo.toml b/libclide/Cargo.toml index f5014f3..2b22fae 100644 --- a/libclide/Cargo.toml +++ b/libclide/Cargo.toml @@ -9,3 +9,4 @@ strum = { workspace = true } log = { workspace = true } devicons = { workspace = true } libclide-macros = { path = "../libclide-macros" } +syntect = "5.3.0" diff --git a/libclide/src/fs.rs b/libclide/src/fs.rs index aaee11e..ccbf406 100644 --- a/libclide/src/fs.rs +++ b/libclide/src/fs.rs @@ -4,4 +4,21 @@ pub mod entry_meta; +use anyhow::Context; pub use entry_meta::icon; +use std::fs; +use std::path::Path; + +pub fn read_file>(p: P) -> anyhow::Result { + let path = p.as_ref(); + let meta = + fs::metadata(path).unwrap_or_else(|_| panic!("Failed to get file metadata {path:?}")); + if !meta.is_file() { + crate::warn!(target:"FileSystem", "Attempted to open file {path:?} that is not a valid file"); + Err(anyhow::anyhow!( + "Attempted to open file {path:?} that is not a valid file" + ))?; + } + let path_str = path.to_string_lossy().to_string(); + fs::read_to_string(path_str.as_str()).context(format!("Failed to read file {path:?}")) +} diff --git a/libclide/src/theme.rs b/libclide/src/theme.rs index 645977c..61909fd 100644 --- a/libclide/src/theme.rs +++ b/libclide/src/theme.rs @@ -3,3 +3,4 @@ // SPDX-License-Identifier: GNU General Public License v3.0 or later pub mod colors; +pub mod highlighter; diff --git a/libclide/src/theme/highlighter.rs b/libclide/src/theme/highlighter.rs new file mode 100644 index 0000000..a4bed16 --- /dev/null +++ b/libclide/src/theme/highlighter.rs @@ -0,0 +1,66 @@ +use std::fs; +use std::path::Path; +use syntect::easy::HighlightLines; +use syntect::highlighting::ThemeSet; +use syntect::html::{IncludeBackground, append_highlighted_html_for_styled_line}; +use syntect::parsing::SyntaxSet; +use syntect::util::LinesWithEndings; + +pub struct Highlighter { + path: String, + ss: SyntaxSet, + ts: ThemeSet, +} + +impl Highlighter { + pub fn new>(p: P) -> anyhow::Result { + let path = p.as_ref(); + let meta = + fs::metadata(path).unwrap_or_else(|_| panic!("Failed to get file metadata {path:?}")); + let ss = SyntaxSet::load_defaults_nonewlines(); + let ts = ThemeSet::load_defaults(); + if !meta.is_file() { + crate::error!(target:"FileSystem", "Attempted to open file {path:?} that is not a valid file"); + Err(anyhow::anyhow!( + "Attempted to open file {path:?} that is not a valid file" + ))?; + } + Ok(Highlighter { + path: path.to_string_lossy().to_string(), + ss, + ts, + }) + } + + pub fn syntax_highlight_text>(&self, p: P) -> String { + let text = p.as_ref(); + let theme = &self.ts.themes["base16-ocean.dark"]; + let lang = self + .ss + .find_syntax_by_extension( + Path::new(self.path.as_str()) + .extension() + .map(|s| s.to_str()) + .unwrap_or_else(|| Some("md")) + .expect("Failed to get file extension"), + ) + .unwrap_or_else(|| self.ss.find_syntax_plain_text()); + let mut highlighter = HighlightLines::new(lang, theme); + // If you care about the background, see `start_highlighted_html_snippet(theme);`. + let mut output = String::from("
\n");
+        for line in LinesWithEndings::from(text) {
+            let regions = highlighter
+                .highlight_line(line, &self.ss)
+                .expect("Failed to highlight");
+
+            append_highlighted_html_for_styled_line(
+                ®ions[..],
+                IncludeBackground::No,
+                &mut output,
+            )
+            .expect("Failed to insert highlighted html");
+        }
+        output.push_str("
\n"); + output + } +} diff --git a/src/gui/filesystem.rs b/src/gui/filesystem.rs index 1f58fd0..2e0f014 100644 --- a/src/gui/filesystem.rs +++ b/src/gui/filesystem.rs @@ -4,13 +4,9 @@ use cxx_qt_lib::{QModelIndex, QString}; use dirs; +use libclide::error; +use libclide::theme::highlighter::Highlighter; use std::fs; -use std::path::Path; -use syntect::easy::HighlightLines; -use syntect::highlighting::ThemeSet; -use syntect::html::{IncludeBackground, append_highlighted_html_for_styled_line}; -use syntect::parsing::SyntaxSet; -use syntect::util::LinesWithEndings; #[cxx_qt::bridge] pub mod qobject { @@ -70,49 +66,15 @@ impl Default for FileSystemImpl { impl qobject::FileSystem { fn read_file(&self, path: &QString) -> QString { - if path.is_empty() { - return QString::default(); - } - let meta = fs::metadata(path.to_string()) - .unwrap_or_else(|_| panic!("Failed to get file metadata {path:?}")); - if !meta.is_file() { - libclide::warn!(target:"FileSystem", "Attempted to open file {path:?} that is not a valid file"); - return QString::default(); - } - let path_str = path.to_string(); - if let Ok(lines) = fs::read_to_string(path_str.as_str()) { - let ss = SyntaxSet::load_defaults_nonewlines(); - let ts = ThemeSet::load_defaults(); - let theme = &ts.themes["base16-ocean.dark"]; - let lang = ss - .find_syntax_by_extension( - Path::new(path_str.as_str()) - .extension() - .map(|s| s.to_str()) - .unwrap_or_else(|| Some("md")) - .expect("Failed to get file extension"), - ) - .unwrap_or_else(|| ss.find_syntax_plain_text()); - let mut highlighter = HighlightLines::new(lang, theme); - // If you care about the background, see `start_highlighted_html_snippet(theme);`. - let mut output = String::from("
\n");
-            for line in LinesWithEndings::from(lines.as_str()) {
-                let regions = highlighter
-                    .highlight_line(line, &ss)
-                    .expect("Failed to highlight");
-
-                append_highlighted_html_for_styled_line(
-                    ®ions[..],
-                    IncludeBackground::No,
-                    &mut output,
-                )
-                .expect("Failed to insert highlighted html");
-            }
-
-            output.push_str("
\n"); - QString::from(output) + let text = libclide::fs::read_file(path.to_string()).unwrap_or_else(|_| { + error!(target: "qobject::FileSystem", "Failed to read file at path {path:?}"); + String::default() + }); + if let Ok(highlighter) = Highlighter::new(path.to_string()) { + QString::from(highlighter.syntax_highlight_text(text)) } else { - QString::default() + error!(target: "qobject::FileSystem", "Failed to create highlighter"); + QString::from(text) } }