clide/src/gui/filesystem.rs
Shaun Reed 325cf285fc Try to use QSortFilterProxyModel via cxx-qt.
This may be easier if I just wrap a QAbstractItemModel in C++ and use
cxx-qt to pull it into QML? That way I could do C++ pointer things in
C++ and rust things in rust.
2026-01-31 21:47:19 -05:00

242 lines
7.4 KiB
Rust

// SPDX-FileCopyrightText: 2026, Shaun Reed <shaunrd0@gmail.com>
//
// SPDX-License-Identifier: GNU General Public License v3.0 or later
use crate::gui::filesystem::qobject::{QAbstractItemModel};
use cxx_qt_lib::{QModelIndex, QString};
use dirs;
use log::warn;
use std::io::BufRead;
use std::{fs};
use std::pin::Pin;
use syntect::easy::HighlightFile;
use syntect::highlighting::ThemeSet;
use syntect::html::{
IncludeBackground, append_highlighted_html_for_styled_line, start_highlighted_html_snippet,
};
use syntect::parsing::SyntaxSet;
#[cxx_qt::bridge]
pub mod qobject {
// Import Qt Types from C++
unsafe extern "C++" {
include!("cxx-qt-lib/qstring.h");
type QString = cxx_qt_lib::QString;
include!("cxx-qt-lib/qmodelindex.h");
type QModelIndex = cxx_qt_lib::QModelIndex;
include!(<QtGui/QFileSystemModel>);
type QFileSystemModel;
include!(<QSortFilterProxyModel>);
type QSortFilterProxyModel;
include!(<QAbstractItemModel>);
type QAbstractItemModel;
}
// Export QML classes from Rust
unsafe extern "RustQt" {
#[qobject]
#[qml_element]
#[base = QSortFilterProxyModel]
#[qproperty(*mut FileSystem, inner)]
type FileSystemSortProxyModel = super::FileSystemSortProxyModelImpl;
#[qobject]
#[base = QFileSystemModel]
#[qml_element]
#[qml_singleton]
#[qproperty(QString, file_path, cxx_name = "filePath")]
#[qproperty(QModelIndex, root_index, cxx_name = "rootIndex")]
type FileSystem = super::FileSystemImpl;
}
// Export QSortFilterProxyModel functions from Rust
// https://doc.qt.io/qt-6/qsortfilterproxymodel.html
unsafe extern "RustQt" {
#[inherit]
#[cxx_name = "setSourceModel"]
unsafe fn set_source_model(
self: Pin<&mut FileSystemSortProxyModel>,
source: *mut QAbstractItemModel,
);
#[cxx_override]
#[cxx_name = "filterAcceptsRow"]
const fn filter_accepts_row(
self: &FileSystemSortProxyModel,
source_row: i32,
source_parent: &QModelIndex,
) -> bool;
#[qinvokable]
#[cxx_name = "setDirectory"]
fn set_directory(self: Pin<&mut FileSystemSortProxyModel>, path: &QString) -> QModelIndex;
}
// Custom initialization logic.
impl cxx_qt::Initialize for FileSystemSortProxyModel {}
// Export QFileSystemModel functions from Rust
// https://doc.qt.io/qt-6/qfilesystemmodel.html
unsafe extern "RustQt" {
#[inherit]
#[cxx_name = "setRootPath"]
fn set_root_path(self: Pin<&mut FileSystem>, path: &QString) -> QModelIndex;
#[qinvokable]
#[cxx_override]
#[cxx_name = "columnCount"]
fn column_count(self: &FileSystem, _index: &QModelIndex) -> i32;
#[qinvokable]
#[cxx_name = "readFile"]
fn read_file(self: &FileSystem, path: &QString) -> QString;
//
// #[qinvokable]
// #[cxx_name = "setDirectory"]
// fn set_directory(self: Pin<&mut FileSystem>, path: &QString) -> QModelIndex;
}
}
pub struct FileSystemSortProxyModelImpl {
inner: *mut qobject::FileSystem,
}
impl Default for FileSystemSortProxyModelImpl {
fn default() -> Self {
let model = Self {
inner: std::ptr::null_mut(),
};
model
}
}
impl qobject::FileSystemSortProxyModel {
pub const fn filter_accepts_row(&self, _source_row: i32, _source_parent: &QModelIndex) -> bool {
false
}
fn read_file(&self, path: &QString) -> QString {
if let Some(inner) = unsafe { self.inner().as_ref() } {
let pinned_inner = unsafe { Pin::new_unchecked(inner) };
return pinned_inner.read_file(path)
} else {
panic!("Can't get inner()")
}
QString::default()
}
// There will never be more than one column.
fn column_count(&self, _index: &QModelIndex) -> i32 {
1
}
fn set_directory(self: Pin<&mut Self>, path: &QString) -> QModelIndex {
if let Some(inner) = unsafe { self.inner().as_mut() } {
let pinned_inner = unsafe { Pin::new_unchecked(inner) };
return pinned_inner.set_directory(path)
} else {
panic!("Can't get inner()")
}
QModelIndex::default()
}
}
impl cxx_qt::Initialize for qobject::FileSystemSortProxyModel {
fn initialize(self: core::pin::Pin<&mut Self>) {
let mut fs = FileSystemImpl::default();
unsafe {
let model: *mut FileSystemImpl = std::ptr::from_mut(&mut fs);
let m: *mut QAbstractItemModel = model as *mut QAbstractItemModel;
self.set_source_model(m);
}
}
}
// TODO: Impleent a provider for QFileSystemModel::setIconProvider for icons.
pub struct FileSystemImpl {
file_path: QString,
root_index: QModelIndex,
}
// Default is explicit to make the editor open this source file initially.
impl Default for FileSystemImpl {
fn default() -> Self {
Self {
file_path: QString::from(file!()),
root_index: Default::default(),
}
}
}
impl qobject::FileSystem {
fn read_file(&self, path: &QString) -> QString {
if path.is_empty() {
return QString::default();
}
if !fs::metadata(path.to_string())
.expect(format!("Failed to get file metadata {path:?}").as_str())
.is_file()
{
warn!(target:"FileSystem", "Attempted to open file {path:?} that is not a valid file");
return QString::default();
}
let ss = SyntaxSet::load_defaults_nonewlines();
let ts = ThemeSet::load_defaults();
let theme = &ts.themes["base16-ocean.dark"];
let mut highlighter =
HighlightFile::new(path.to_string(), &ss, theme).expect("Failed to create highlighter");
let (mut output, _bg) = start_highlighted_html_snippet(theme);
let mut line = String::new();
while highlighter
.reader
.read_line(&mut line)
.expect("Failed to read file.")
> 0
{
let regions = highlighter
.highlight_lines
.highlight_line(&line, &ss)
.expect("Failed to highlight");
append_highlighted_html_for_styled_line(
&regions[..],
IncludeBackground::Yes,
&mut output,
)
.expect("Failed to insert highlighted html");
line.clear();
}
output.push_str("</pre>\n");
QString::from(output)
}
// There will never be more than one column.
fn column_count(&self, _index: &QModelIndex) -> i32 {
1
}
fn set_directory(self: std::pin::Pin<&mut Self>, path: &QString) -> QModelIndex {
if !path.is_empty()
&& fs::metadata(path.to_string())
.expect(format!("Failed to get metadata for path {path:?}").as_str())
.is_dir()
{
self.set_root_path(path)
} else {
// If the initial directory can't be opened, attempt to find the home directory.
self.set_root_path(&QString::from(
dirs::home_dir()
.expect("Failed to get home directory")
.as_path()
.to_str()
.unwrap()
.to_string(),
))
}
}
}