Add syntax highlighter.

This commit is contained in:
2026-02-28 22:53:15 -05:00
parent 7a0b2d197f
commit a92844b8e5
6 changed files with 96 additions and 48 deletions

1
Cargo.lock generated
View File

@@ -1174,6 +1174,7 @@ dependencies = [
"libclide-macros", "libclide-macros",
"log", "log",
"strum", "strum",
"syntect",
] ]
[[package]] [[package]]

View File

@@ -9,3 +9,4 @@ strum = { workspace = true }
log = { workspace = true } log = { workspace = true }
devicons = { workspace = true } devicons = { workspace = true }
libclide-macros = { path = "../libclide-macros" } libclide-macros = { path = "../libclide-macros" }
syntect = "5.3.0"

View File

@@ -4,4 +4,21 @@
pub mod entry_meta; pub mod entry_meta;
use anyhow::Context;
pub use entry_meta::icon; pub use entry_meta::icon;
use std::fs;
use std::path::Path;
pub fn read_file<P: AsRef<Path>>(p: P) -> anyhow::Result<String> {
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:?}"))
}

View File

@@ -3,3 +3,4 @@
// SPDX-License-Identifier: GNU General Public License v3.0 or later // SPDX-License-Identifier: GNU General Public License v3.0 or later
pub mod colors; pub mod colors;
pub mod highlighter;

View File

@@ -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: AsRef<Path>>(p: P) -> anyhow::Result<Highlighter> {
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<P: AsRef<str>>(&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("<pre>\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(
&regions[..],
IncludeBackground::No,
&mut output,
)
.expect("Failed to insert highlighted html");
}
output.push_str("</pre>\n");
output
}
}

View File

@@ -4,13 +4,9 @@
use cxx_qt_lib::{QModelIndex, QString}; use cxx_qt_lib::{QModelIndex, QString};
use dirs; use dirs;
use libclide::error;
use libclide::theme::highlighter::Highlighter;
use std::fs; 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] #[cxx_qt::bridge]
pub mod qobject { pub mod qobject {
@@ -70,49 +66,15 @@ impl Default for FileSystemImpl {
impl qobject::FileSystem { impl qobject::FileSystem {
fn read_file(&self, path: &QString) -> QString { fn read_file(&self, path: &QString) -> QString {
if path.is_empty() { let text = libclide::fs::read_file(path.to_string()).unwrap_or_else(|_| {
return QString::default(); error!(target: "qobject::FileSystem", "Failed to read file at path {path:?}");
} String::default()
let meta = fs::metadata(path.to_string()) });
.unwrap_or_else(|_| panic!("Failed to get file metadata {path:?}")); if let Ok(highlighter) = Highlighter::new(path.to_string()) {
if !meta.is_file() { QString::from(highlighter.syntax_highlight_text(text))
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("<pre>\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(
&regions[..],
IncludeBackground::No,
&mut output,
)
.expect("Failed to insert highlighted html");
}
output.push_str("</pre>\n");
QString::from(output)
} else { } else {
QString::default() error!(target: "qobject::FileSystem", "Failed to create highlighter");
QString::from(text)
} }
} }