5 Commits

Author SHA1 Message Date
ae5d1c7e22 Align tui and gui menubars. 2026-03-13 17:46:15 -04:00
a92844b8e5 Add syntax highlighter. 2026-02-28 23:00:01 -05:00
7a0b2d197f Fix logger scrolling. 2026-02-28 21:45:42 -05:00
a9564493cb Remove unused fonts. 2026-02-28 20:10:11 -05:00
dd11aeca54 Format. 2026-02-28 19:51:11 -05:00
14 changed files with 112 additions and 108 deletions

1
Cargo.lock generated
View File

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

View File

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

View File

@@ -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: 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
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

@@ -14,7 +14,6 @@ Rectangle {
ListModel {
id: model
}
ListView {
id: listView
@@ -38,10 +37,10 @@ Rectangle {
anchors.fill: parent
model: model
verticalLayoutDirection: ListView.BottomToTop
delegate: Text {
color: listView.getLogColor(level)
font.family: "monospace"
text: `[${level}] ${message}`
}

View File

@@ -18,22 +18,6 @@ MenuBar {
ClideMenu {
title: qsTr("&File")
ClideMenuItem {
action: Action {
id: actionNewProject
text: qsTr("&New Project...")
}
}
ClideMenuItem {
action: Action {
id: actionOpen
text: qsTr("&Open...")
}
onTriggered: FileSystem.setDirectory(FileSystem.filePath)
}
ClideMenuItem {
action: Action {
id: actionSave
@@ -41,6 +25,13 @@ MenuBar {
text: qsTr("&Save")
}
}
ClideMenuItem {
action: Action {
id: actionReload
text: qsTr("&Reload")
}
}
MenuSeparator {
background: Rectangle {
border.color: color
@@ -67,37 +58,9 @@ MenuBar {
ClideMenuItem {
action: Action {
id: actionUndo
id: actionCloseTab
text: qsTr("&Undo")
}
}
ClideMenuItem {
action: Action {
id: actionRedo
text: qsTr("&Redo")
}
}
ClideMenuItem {
action: Action {
id: actionCut
text: qsTr("&Cut")
}
}
ClideMenuItem {
action: Action {
id: actionCopy
text: qsTr("&Copy")
}
}
ClideMenuItem {
action: Action {
id: actionPaste
text: qsTr("&Paste")
text: qsTr("&Close Tab")
}
}
}
@@ -132,13 +95,6 @@ MenuBar {
ClideMenu {
title: qsTr("&Help")
ClideMenuItem {
action: Action {
id: actionDocumentation
text: qsTr("&Documentation")
}
}
ClideMenuItem {
action: Action {
id: actionAbout

View File

@@ -3,8 +3,6 @@
<file alias="kilroy.png">resources/images/kilroy-256.png</file>
</qresource>
<qresource prefix="/fonts">
<file alias="saucecodepro.ttf">resources/SauceCodeProNerdFont-Black.ttf</file>
<file alias="saucecodepro-light.ttf">resources/SauceCodeProNerdFont-Light.ttf</file>
<file alias="saucecodepro-xlight.ttf">resources/SauceCodeProNerdFont-ExtraLight.ttf</file>
</qresource>
</RCC>

View File

@@ -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("<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)
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)
}
}

View File

@@ -13,9 +13,9 @@ mod menu_bar;
use crate::AppContext;
use anyhow::{Context, Result};
use libclide::log::Loggable;
use log::LevelFilter;
use ratatui::Terminal;
use libclide::log::Loggable;
use ratatui::backend::CrosstermBackend;
use ratatui::crossterm::event::{
DisableBracketedPaste, DisableMouseCapture, EnableBracketedPaste, EnableMouseCapture,

View File

@@ -7,8 +7,8 @@
use crate::tui::component::Focus::Inactive;
use Focus::Active;
use anyhow::Result;
use libclide::theme::colors::Colors;
use libclide::log::Loggable;
use libclide::theme::colors::Colors;
use ratatui::crossterm::event::{Event, KeyEvent, MouseEvent};
use ratatui::style::Color;

View File

@@ -21,6 +21,7 @@ use strum::{EnumIter, FromRepr, IntoEnumIterator};
#[derive(Debug, Clone, Copy, PartialEq, Eq, FromRepr, EnumIter)]
enum MenuBarItem {
File,
Edit,
View,
Help,
}
@@ -68,12 +69,14 @@ impl MenuBarItem {
MenuBarItem::File => "File",
MenuBarItem::View => "View",
MenuBarItem::Help => "Help",
MenuBarItem::Edit => "Edit",
}
}
pub fn options(&self) -> &[MenuBarItemOption] {
match self {
MenuBarItem::File => &[Save, CloseTab, Reload, Exit],
MenuBarItem::File => &[Save, Reload, Exit],
MenuBarItem::Edit => &[CloseTab],
MenuBarItem::View => &[ShowHideExplorer, ShowHideLogger],
MenuBarItem::Help => &[About],
}