From 8b71af06a82a2247eb0d0ae2f6f400313dd9159c Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Sat, 29 Mar 2025 09:55:09 -0400 Subject: [PATCH 01/73] Add placeholders for side panel and console. --- README.md | 2 +- qml/Menu/ClideMenu.qml | 2 +- qml/Menu/ClideMenuBar.qml | 3 +- qml/Menu/ClideMenuBarItem.qml | 2 +- qml/Menu/ClideMenuBarItem.qml.autosave | 16 +++ qml/main.qml | 145 ++++++++++++++++++++++++- 6 files changed, 165 insertions(+), 5 deletions(-) create mode 100644 qml/Menu/ClideMenuBarItem.qml.autosave diff --git a/README.md b/README.md index dcf168c..fce61ed 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ CLIDE is an IDE written in Rust that supports both full and headless Linux envir The following packages must be installed before the application will build. ```bash -sudo apt install qt6-base-dev qt6-declarative-dev qt6-tools-dev qml6-module-qtquick-controls qml6-module-qtquick-layouts qml6-module-qtquick-window qml6-module-qtqml-workerscript qml6-module-qtquick-templates qml6-module-qtquick +sudo apt install qt6-base-dev qt6-declarative-dev qt6-tools-dev qml6-module-qtquick-controls qml6-module-qtquick-layouts qml6-module-qtquick-window qml6-module-qtqml-workerscript qml6-module-qtquick-templates qml6-module-qtquick qml6-module-qtquick-dialogs ``` And of course, [Rust](https://www.rust-lang.org/tools/install). diff --git a/qml/Menu/ClideMenu.qml b/qml/Menu/ClideMenu.qml index 6616882..8b89d96 100644 --- a/qml/Menu/ClideMenu.qml +++ b/qml/Menu/ClideMenu.qml @@ -4,7 +4,7 @@ import QtQuick.Controls Menu { background: Rectangle { color: "#3c3f41" - implicitWidth: 200 + implicitWidth: 100 radius: 2 } } diff --git a/qml/Menu/ClideMenuBar.qml b/qml/Menu/ClideMenuBar.qml index 66356bd..9a7a58c 100644 --- a/qml/Menu/ClideMenuBar.qml +++ b/qml/Menu/ClideMenuBar.qml @@ -3,7 +3,7 @@ import QtQuick.Controls MenuBar { background: Rectangle { - color: "#3b3e40" // Dark background like CLion + color: "#3c3f41" } Action { @@ -25,6 +25,7 @@ MenuBar { id: actionExit text: qsTr("&Exit") + onTriggered: Qt.quit() } ClideMenu { diff --git a/qml/Menu/ClideMenuBarItem.qml b/qml/Menu/ClideMenuBarItem.qml index f740727..15f049f 100644 --- a/qml/Menu/ClideMenuBarItem.qml +++ b/qml/Menu/ClideMenuBarItem.qml @@ -9,7 +9,7 @@ MenuItem { radius: 2.5 } contentItem: IconLabel { - color: "white" + color: "black" font.family: "Helvetica" text: root.text } diff --git a/qml/Menu/ClideMenuBarItem.qml.autosave b/qml/Menu/ClideMenuBarItem.qml.autosave new file mode 100644 index 0000000..15f049f --- /dev/null +++ b/qml/Menu/ClideMenuBarItem.qml.autosave @@ -0,0 +1,16 @@ +import QtQuick +import QtQuick.Controls + +MenuItem { + id: root + + background: Rectangle { + color: root.hovered ? "#4b4f51" : "#3c3f41" // Hover effect + radius: 2.5 + } + contentItem: IconLabel { + color: "black" + font.family: "Helvetica" + text: root.text + } +} diff --git a/qml/main.qml b/qml/main.qml index 9345f21..efa1836 100644 --- a/qml/main.qml +++ b/qml/main.qml @@ -1,10 +1,13 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts +import QtQuick.Dialogs import "Menu" ApplicationWindow { + id: appWindow + height: 800 title: "CLIDE" visible: true @@ -15,6 +18,146 @@ ApplicationWindow { Rectangle { anchors.fill: parent - color: "#1e1f22" // Dark background + color: "#1e1f22" + } + MessageDialog { + id: errorDialog + + title: qsTr("Error") + } + RowLayout { + anchors.fill: parent + spacing: 0 + + SplitView { + Layout.fillHeight: true + Layout.fillWidth: true + + // Customized handle to drag between the Navigation and the Editor. + handle: Rectangle { + border.color: SplitHandle.hovered ? "#303234" : "#3c3f41" + color: SplitHandle.pressed ? "#4b4f51" : "#3c3f41" + implicitWidth: 8 + opacity: SplitHandle.hovered || navigationView.width < 15 ? 1.0 : 0.0 + + Behavior on opacity { + OpacityAnimator { + duration: 1400 + } + } + } + + Rectangle { + id: navigationView + + SplitView.fillHeight: true + SplitView.preferredWidth: 250 + color: "#303234" + + StackLayout { + anchors.fill: parent + + // Shows the help text. + Text { + color: "white" + text: qsTr("File system view placeholder") + wrapMode: TextArea.Wrap + } + + // TODO: Shows the files on the file system. + // ClideProjectView { + // id: fileSystemView + // color: Colors.surface1 + // onFileClicked: path => root.currentFilePath = path + // } + } + } + SplitView { + Layout.fillHeight: true + Layout.fillWidth: true + orientation: Qt.Vertical + + // Customized handle to drag between the Navigation and the Editor. + handle: Rectangle { + border.color: SplitHandle.hovered ? "#2b2b2b" : "#3c3f41" + color: SplitHandle.pressed ? "#4b4f51" : "#3c3f41" + implicitHeight: 8 + opacity: SplitHandle.hovered || navigationView.width < 15 ? 1.0 : 0.0 + + Behavior on opacity { + OpacityAnimator { + duration: 1400 + } + } + } + + TextArea { + id: areaText + + color: "#ccced3" + focus: true + persistentSelection: true + selectByMouse: true + // selectedTextColor: control.palette.highlightedText + // selectionColor: control.palette.highlight + textFormat: Qt.AutoText + wrapMode: TextArea.Wrap + + background: Rectangle { + color: "#2b2b2b" + implicitHeight: 650 + } + + onLinkActivated: function (link) { + Qt.openUrlExternally(link); + } + + // TODO: Handle saving + // Component.onCompleted: { + // if (Qt.application.arguments.length === 2) + // textDocument.source = "file:" + Qt.application.arguments[1] + // else + // textDocument.source = "qrc:/texteditor.html" + // } + // textDocument.onStatusChanged: { + // // a message lookup table using computed properties: + // // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer + // const statusMessages = { + // [ TextDocument.ReadError ]: qsTr("Failed to load “%1”"), + // [ TextDocument.WriteError ]: qsTr("Failed to save “%1”"), + // [ TextDocument.NonLocalFileError ]: qsTr("Not a local file: “%1”"), + // } + // const err = statusMessages[textDocument.status] + // if (err) { + // errorDialog.text = err.arg(textDocument.source) + // errorDialog.open() + // } + // } + } + TextArea { + id: areaConsole + + bottomPadding: 0 + leftPadding: 6 + persistentSelection: true + placeholderText: qsTr("Placeholder for bash terminal.") + placeholderTextColor: "gray" + readOnly: true + rightPadding: 6 + selectByMouse: true + // selectedTextColor: control.palette.highlightedText + // selectionColor: control.palette.highlight + textFormat: Qt.AutoText + topPadding: 6 + wrapMode: TextArea.Wrap + + background: Rectangle { + color: "#2b2b2b" + implicitHeight: 20 + // border.color: control.enabled ? "#21be2b" : "transparent" + } + } + } + } } } -- 2.47.2 From a6d2fb9e31b23c6ce6c9324d593aa6fd5aeefbe6 Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Sat, 29 Mar 2025 10:26:39 -0400 Subject: [PATCH 02/73] Change colors. --- qml/Menu/ClideMenuBar.qml | 1 + qml/main.qml | 21 +++++++-------------- 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/qml/Menu/ClideMenuBar.qml b/qml/Menu/ClideMenuBar.qml index 9a7a58c..f252803 100644 --- a/qml/Menu/ClideMenuBar.qml +++ b/qml/Menu/ClideMenuBar.qml @@ -4,6 +4,7 @@ import QtQuick.Controls MenuBar { background: Rectangle { color: "#3c3f41" + border.color: "#575757" } Action { diff --git a/qml/main.qml b/qml/main.qml index efa1836..041794a 100644 --- a/qml/main.qml +++ b/qml/main.qml @@ -58,9 +58,10 @@ ApplicationWindow { anchors.fill: parent // Shows the help text. - Text { - color: "white" - text: qsTr("File system view placeholder") + TextArea { + readOnly: true + placeholderText: qsTr("File system view placeholder") + placeholderTextColor: "white" wrapMode: TextArea.Wrap } @@ -137,23 +138,15 @@ ApplicationWindow { TextArea { id: areaConsole - bottomPadding: 0 - leftPadding: 6 - persistentSelection: true placeholderText: qsTr("Placeholder for bash terminal.") - placeholderTextColor: "gray" + placeholderTextColor: "white" + height: 100 readOnly: true - rightPadding: 6 - selectByMouse: true - // selectedTextColor: control.palette.highlightedText - // selectionColor: control.palette.highlight - textFormat: Qt.AutoText - topPadding: 6 wrapMode: TextArea.Wrap background: Rectangle { color: "#2b2b2b" - implicitHeight: 20 + implicitHeight: 100 // border.color: control.enabled ? "#21be2b" : "transparent" } } -- 2.47.2 From 13a405a80196a76c4a0af81e65da32c6c169d6aa Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Sat, 29 Mar 2025 16:55:26 -0400 Subject: [PATCH 03/73] Add LineCount module. --- .gitignore | 7 +- Cargo.lock | 7 ++ Cargo.toml | 1 + build.rs | 2 +- qml/Menu/ClideMenuBar.qml.autosave | 140 +++++++++++++++++++++ qml/main.qml | 193 +++++++++++++++++++++++------ src/main.rs | 134 ++++++++++++-------- 7 files changed, 386 insertions(+), 98 deletions(-) create mode 100644 qml/Menu/ClideMenuBar.qml.autosave diff --git a/.gitignore b/.gitignore index e873887..8e9b114 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ -/target -.qtcreator -.idea +**/target/** +**/.qtcreator/** +**/.idea/** +**/*.autosave/** \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 10a9bbf..3574567 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -62,6 +62,7 @@ dependencies = [ "cxx-qt", "cxx-qt-build", "cxx-qt-lib", + "log", ] [[package]] @@ -266,6 +267,12 @@ dependencies = [ "cc", ] +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + [[package]] name = "memchr" version = "2.7.4" diff --git a/Cargo.toml b/Cargo.toml index a10ab35..8b7c81c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,7 @@ edition = "2024" cxx = "1.0.95" cxx-qt = "0.7" cxx-qt-lib = { version="0.7", features = ["qt_full"] } +log = { version = "0.4.27", features = [] } [build-dependencies] # The link_qt_object_files feature is required for statically linking Qt 6. diff --git a/build.rs b/build.rs index e14579a..4ddd535 100644 --- a/build.rs +++ b/build.rs @@ -9,7 +9,7 @@ fn main() { // - Qt Qml requires linking Qt Network on macOS .qt_module("Network") .qml_module(QmlModule { - uri: "test", + uri: "clide.module", rust_files: &["src/main.rs"], qml_files: &["qml/main.qml", "qml/Menu/ClideMenu.qml", diff --git a/qml/Menu/ClideMenuBar.qml.autosave b/qml/Menu/ClideMenuBar.qml.autosave new file mode 100644 index 0000000..f252803 --- /dev/null +++ b/qml/Menu/ClideMenuBar.qml.autosave @@ -0,0 +1,140 @@ +import QtQuick +import QtQuick.Controls + +MenuBar { + background: Rectangle { + color: "#3c3f41" + border.color: "#575757" + } + + Action { + id: actionNewProject + + text: qsTr("&New Project...") + } + Action { + id: actionOpen + + text: qsTr("&Open...") + } + Action { + id: actionSave + + text: qsTr("&Save") + } + Action { + id: actionExit + + text: qsTr("&Exit") + + onTriggered: Qt.quit() + } + ClideMenu { + title: qsTr("&File") + + ClideMenuBarItem { + action: actionNewProject + } + ClideMenuBarItem { + action: actionOpen + } + ClideMenuBarItem { + action: actionSave + } + MenuSeparator { + background: Rectangle { + border.color: color + color: "#3c3f41" + implicitHeight: 3 + implicitWidth: 200 + } + } + ClideMenuBarItem { + action: actionExit + } + } + Action { + id: actionUndo + + text: qsTr("&Undo") + } + Action { + id: actionRedo + + text: qsTr("&Redo") + } + Action { + id: actionCut + + text: qsTr("&Cut") + } + Action { + id: actionCopy + + text: qsTr("&Copy") + } + Action { + id: actionPaste + + text: qsTr("&Paste") + } + ClideMenu { + title: qsTr("&Edit") + + ClideMenuBarItem { + action: actionUndo + } + ClideMenuBarItem { + action: actionRedo + } + ClideMenuBarItem { + action: actionCut + } + ClideMenuBarItem { + action: actionCopy + } + ClideMenuBarItem { + action: actionPaste + } + } + Action { + id: actionToolWindows + + text: qsTr("&Tool Windows") + } + Action { + id: actionAppearance + + text: qsTr("&Appearance") + } + ClideMenu { + title: qsTr("&View") + + ClideMenuBarItem { + action: actionToolWindows + } + ClideMenuBarItem { + action: actionAppearance + } + } + Action { + id: actionDocumentation + + text: qsTr("&Documentation") + } + Action { + id: actionAbout + + text: qsTr("&About") + } + ClideMenu { + title: qsTr("&Help") + + ClideMenuBarItem { + action: actionDocumentation + } + ClideMenuBarItem { + action: actionAbout + } + } +} diff --git a/qml/main.qml b/qml/main.qml index 041794a..5f4f592 100644 --- a/qml/main.qml +++ b/qml/main.qml @@ -5,6 +5,8 @@ import QtQuick.Dialogs import "Menu" +import clide.module 1.0 + ApplicationWindow { id: appWindow @@ -51,7 +53,7 @@ ApplicationWindow { id: navigationView SplitView.fillHeight: true - SplitView.preferredWidth: 250 + SplitView.preferredWidth: 200 color: "#303234" StackLayout { @@ -59,9 +61,9 @@ ApplicationWindow { // Shows the help text. TextArea { - readOnly: true placeholderText: qsTr("File system view placeholder") placeholderTextColor: "white" + readOnly: true wrapMode: TextArea.Wrap } @@ -92,55 +94,134 @@ ApplicationWindow { } } - TextArea { - id: areaText + RowLayout { + // We use a flickable to synchronize the position of the editor and + // the line numbers. This is necessary because the line numbers can + // extend the available height. + Flickable { + id: lineNumbers - color: "#ccced3" - focus: true - persistentSelection: true - selectByMouse: true - // selectedTextColor: control.palette.highlightedText - // selectionColor: control.palette.highlight - textFormat: Qt.AutoText - wrapMode: TextArea.Wrap + Layout.fillHeight: true + Layout.fillWidth: false - background: Rectangle { - color: "#2b2b2b" - implicitHeight: 650 + // Calculate the width based on the logarithmic scale. + Layout.preferredWidth: fontMetrics.averageCharacterWidth * (Math.floor(Math.log10(areaText.lineCount)) + 1) + 10 + contentY: editorFlickable.contentY + interactive: false + visible: true + + Column { + anchors.fill: parent + + Repeater { + id: repeatedLineNumbers + + delegate: Item { + required property int index + + height: Math.ceil(fontMetrics.lineSpacing) + width: parent.width + + Label { + id: numbers + + color: "white" + font: areaText.font + height: parent.height + horizontalAlignment: Text.AlignLeft + text: parent.index + 1 + verticalAlignment: Text.AlignVCenter + width: parent.width + } + Rectangle { + id: indicator + + anchors.left: numbers.right + color: Qt.darker("#FFF", 3) + height: parent.height + width: 1 + } + } + model: LineCount { + id: lineCountModel + + // This count sets the max line numbers shown in the gutter. + count: areaText.lineCount + } + } + } } + Flickable { + id: editorFlickable - onLinkActivated: function (link) { - Qt.openUrlExternally(link); + height: 650 + + property alias areaText: areaText + + Layout.fillHeight: true + Layout.fillWidth: true + boundsBehavior: Flickable.StopAtBounds + + ScrollBar.horizontal: MyScrollBar { + } + ScrollBar.vertical: MyScrollBar { + } + TextArea.flickable: TextArea { + id: areaText + + color: "#ccced3" + focus: true + persistentSelection: true + selectByMouse: true + // selectedTextColor: control.palette.highlightedText + // selectionColor: control.palette.highlight + textFormat: Qt.AutoText + wrapMode: TextArea.Wrap + + background: Rectangle { + color: "#2b2b2b" + } + + onLinkActivated: function (link) { + Qt.openUrlExternally(link); + } + + // TODO: Handle saving + // Component.onCompleted: { + // if (Qt.application.arguments.length === 2) + // textDocument.source = "file:" + Qt.application.arguments[1] + // else + // textDocument.source = "qrc:/texteditor.html" + // } + // textDocument.onStatusChanged: { + // // a message lookup table using computed properties: + // // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer + // const statusMessages = { + // [ TextDocument.ReadError ]: qsTr("Failed to load “%1”"), + // [ TextDocument.WriteError ]: qsTr("Failed to save “%1”"), + // [ TextDocument.NonLocalFileError ]: qsTr("Not a local file: “%1”"), + // } + // const err = statusMessages[textDocument.status] + // if (err) { + // errorDialog.text = err.arg(textDocument.source) + // errorDialog.open() + // } + // } + } + + FontMetrics { + id: fontMetrics + + font: areaText.font + } } - - // TODO: Handle saving - // Component.onCompleted: { - // if (Qt.application.arguments.length === 2) - // textDocument.source = "file:" + Qt.application.arguments[1] - // else - // textDocument.source = "qrc:/texteditor.html" - // } - // textDocument.onStatusChanged: { - // // a message lookup table using computed properties: - // // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer - // const statusMessages = { - // [ TextDocument.ReadError ]: qsTr("Failed to load “%1”"), - // [ TextDocument.WriteError ]: qsTr("Failed to save “%1”"), - // [ TextDocument.NonLocalFileError ]: qsTr("Not a local file: “%1”"), - // } - // const err = statusMessages[textDocument.status] - // if (err) { - // errorDialog.text = err.arg(textDocument.source) - // errorDialog.open() - // } - // } } TextArea { id: areaConsole + height: 100 placeholderText: qsTr("Placeholder for bash terminal.") placeholderTextColor: "white" - height: 100 readOnly: true wrapMode: TextArea.Wrap @@ -153,4 +234,36 @@ ApplicationWindow { } } } + + // We use an inline component to customize the horizontal and vertical + // scroll-bars. This is convenient when the component is only used in one file. + component MyScrollBar: ScrollBar { + id: scrollBar + + background: Rectangle { + color: "#2b2b2b" + implicitHeight: scrollBar.interactive ? 8 : 4 + implicitWidth: scrollBar.interactive ? 8 : 4 + opacity: scrollBar.active && scrollBar.size < 1.0 ? 1.0 : 0.0 + + Behavior on opacity { + OpacityAnimator { + duration: 500 + } + } + } + contentItem: Rectangle { + color: "#4b4f51" + implicitHeight: scrollBar.interactive ? 8 : 4 + implicitWidth: scrollBar.interactive ? 8 : 4 + opacity: scrollBar.active && scrollBar.size < 1.0 ? 1.0 : 0.0 + + Behavior on opacity { + OpacityAnimator { + duration: 1000 + } + } + } + } } + diff --git a/src/main.rs b/src/main.rs index 50edc97..4239053 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,77 +1,103 @@ -// SPDX-FileCopyrightText: 2025 Klarälvdalens Datakonsult AB, a KDAB Group company -// SPDX-FileContributor: Leon Matthes -// -// SPDX-License-Identifier: MIT OR Apache-2.0 +// TODO: Header #[cxx_qt::bridge] -mod qobject { +pub mod qobject { unsafe extern "C++" { - include!("cxx-qt-lib/qstring.h"); - type QString = cxx_qt_lib::QString; - } - - #[qenum(Greeter)] - pub enum Language { - English, - German, - French, - } - - #[qenum(Greeter)] - pub enum Greeting { - Hello, - Bye, + include!("cxx-qt-lib/qvariant.h"); + type QVariant = cxx_qt_lib::QVariant; + include!(); + type QModelIndex = cxx_qt_lib::QModelIndex; + type QAbstractListModel; } unsafe extern "RustQt" { #[qobject] + #[base = QAbstractListModel] + type AbstractListModel = super::AbstractListModelRust; + + #[qobject] + #[base = AbstractListModel] #[qml_element] - #[qproperty(Greeting, greeting)] - #[qproperty(Language, language)] - type Greeter = super::GreeterRust; + #[qproperty(i32, count)] + type LineCount = super::LineCountRust; + + #[cxx_name = "beginInsertRows"] + #[inherit] + fn beginInsertRows(self: Pin<&mut LineCount>, parent: &QModelIndex, first: i32, last: i32); + + #[cxx_name = "endInsertRows"] + #[inherit] + fn endInsertRows(self: Pin<&mut LineCount>); + + #[cxx_name = "beginRemoveRows"] + #[inherit] + fn beginRemoveRows(self: Pin<&mut LineCount>, parent: &QModelIndex, first: i32, last: i32); + + #[cxx_name = "endRemoveRows"] + #[inherit] + fn endRemoveRows(self: Pin<&mut LineCount>); #[qinvokable] - fn greet(self: &Greeter) -> QString; + pub fn set_line_count(self: Pin<&mut LineCount>, line_count: i32); + + #[qinvokable] + #[cxx_override] + fn data(self: &LineCount, index: &QModelIndex, role: i32) -> QVariant; + + #[qinvokable] + #[cxx_override] + #[cxx_name = "rowCount"] + fn row_count(self: &LineCount, _parent: &QModelIndex) -> i32; } } -use qobject::*; +use cxx_qt::CxxQtType; +use cxx_qt_lib::{QModelIndex, QVariant}; -impl Greeting { - fn translate(&self, language: Language) -> String { - match (self, language) { - (&Greeting::Hello, Language::English) => "Hello, World!", - (&Greeting::Hello, Language::German) => "Hallo, Welt!", - (&Greeting::Hello, Language::French) => "Bonjour, le monde!", - (&Greeting::Bye, Language::English) => "Bye!", - (&Greeting::Bye, Language::German) => "Auf Wiedersehen!", - (&Greeting::Bye, Language::French) => "Au revoir!", - _ => "🤯", +impl qobject::LineCount { + pub fn set_line_count(mut self: std::pin::Pin<&mut Self>, line_count: i32) { + let current_count = self.as_mut().rust_mut().count; + if line_count < 0 || current_count == line_count { + log::warn!( + "Can't set line count: {}; Current count: {}", + line_count, + current_count + ); + return; } - .to_string() - } -} - -pub struct GreeterRust { - greeting: Greeting, - language: Language, -} - -impl Default for GreeterRust { - fn default() -> Self { - Self { - greeting: Greeting::Hello, - language: Language::English, + if current_count < line_count { + self.as_mut() + .beginInsertRows(&QModelIndex::default(), current_count, line_count - 1); + self.as_mut().endInsertRows(); + } else if current_count > line_count { + self.as_mut() + .beginRemoveRows(&QModelIndex::default(), line_count, current_count - 1); + self.as_mut().endRemoveRows(); } + self.as_mut().rust_mut().count = line_count; + log::warn!( + "Line count changed from {} to {}", + current_count, + line_count + ); + } + + pub fn row_count(self: &Self, _parent: &QModelIndex) -> i32 { + *self.count() + } + + pub fn data(self: &Self, _index: &QModelIndex, _role: i32) -> QVariant { + QVariant::default() } } -use cxx_qt_lib::QString; +/// A struct which inherits from QAbstractListModel +#[derive(Default)] +pub struct AbstractListModelRust {} -impl qobject::Greeter { - fn greet(&self) -> QString { - QString::from(self.greeting.translate(self.language)) - } +#[derive(Default)] +pub struct LineCountRust { + pub count: i32, } fn main() { -- 2.47.2 From 70e9f79c8a4ada6923c5a3fa0d93d6e84c37e2b1 Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Sat, 29 Mar 2025 17:59:17 -0400 Subject: [PATCH 04/73] Separate ProjectView and Editor. --- build.rs | 2 + qml/Editor/ClideEditor.qml | 200 ++++++++++++++++++++++ qml/ProjectView/ClideProjectView.qml | 54 ++++++ qml/main.qml | 239 +-------------------------- 4 files changed, 258 insertions(+), 237 deletions(-) create mode 100644 qml/Editor/ClideEditor.qml create mode 100644 qml/ProjectView/ClideProjectView.qml diff --git a/build.rs b/build.rs index 4ddd535..83e59a6 100644 --- a/build.rs +++ b/build.rs @@ -12,6 +12,8 @@ fn main() { uri: "clide.module", rust_files: &["src/main.rs"], qml_files: &["qml/main.qml", + "qml/ProjectView/ClideProjectView.qml", + "qml/Editor/ClideEditor.qml", "qml/Menu/ClideMenu.qml", "qml/Menu/ClideMenuBar.qml", "qml/Menu/ClideMenuBarItem.qml"], diff --git a/qml/Editor/ClideEditor.qml b/qml/Editor/ClideEditor.qml new file mode 100644 index 0000000..c91bd04 --- /dev/null +++ b/qml/Editor/ClideEditor.qml @@ -0,0 +1,200 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import clide.module 1.0 + +SplitView { + Layout.fillHeight: true + Layout.fillWidth: true + orientation: Qt.Vertical + + // Customized handle to drag between the Editor and the Console. + handle: Rectangle { + border.color: SplitHandle.hovered ? "#2b2b2b" : "#3c3f41" + color: SplitHandle.pressed ? "#4b4f51" : "#3c3f41" + implicitHeight: 8 + opacity: SplitHandle.hovered || areaConsole.height < 15 ? 1.0 : 0.0 + + Behavior on opacity { + OpacityAnimator { + duration: 1400 + } + } + } + + RowLayout { + // We use a flickable to synchronize the position of the editor and + // the line numbers. This is necessary because the line numbers can + // extend the available height. + Flickable { + id: lineNumbers + + Layout.fillHeight: true + Layout.fillWidth: false + + // Calculate the width based on the logarithmic scale. + Layout.preferredWidth: fontMetrics.averageCharacterWidth * (Math.floor(Math.log10(areaText.lineCount)) + 1) + 10 + contentY: editorFlickable.contentY + interactive: false + visible: true + + Column { + anchors.fill: parent + + Repeater { + id: repeatedLineNumbers + + delegate: Item { + required property int index + + height: Math.ceil(fontMetrics.lineSpacing) + width: parent.width + + Label { + id: numbers + + color: "white" + font: areaText.font + height: parent.height + horizontalAlignment: Text.AlignLeft + text: parent.index + 1 + verticalAlignment: Text.AlignVCenter + width: parent.width + } + Rectangle { + id: indicator + + anchors.left: numbers.right + color: Qt.darker("#FFF", 3) + height: parent.height + width: 1 + } + } + model: LineCount { + id: lineCountModel + + // This count sets the max line numbers shown in the gutter. + count: areaText.lineCount + } + } + } + } + Flickable { + id: editorFlickable + + property alias areaText: areaText + + Layout.fillHeight: true + Layout.fillWidth: true + boundsBehavior: Flickable.StopAtBounds + height: 650 + + ScrollBar.horizontal: MyScrollBar { + } + ScrollBar.vertical: MyScrollBar { + } + TextArea.flickable: TextArea { + id: areaText + + + color: "#ccced3" + focus: true + persistentSelection: true + selectByMouse: true + // selectedTextColor: control.palette.highlightedText + // selectionColor: control.palette.highlight + textFormat: Qt.AutoText + wrapMode: TextArea.Wrap + + onTextChanged: { + console.log("Updated line count: " + areaText.lineCount) + } + + background: Rectangle { + color: "#2b2b2b" + } + + onLinkActivated: function (link) { + Qt.openUrlExternally(link); + } + + // TODO: Handle saving + // Component.onCompleted: { + // if (Qt.application.arguments.length === 2) + // textDocument.source = "file:" + Qt.application.arguments[1] + // else + // textDocument.source = "qrc:/texteditor.html" + // } + // textDocument.onStatusChanged: { + // // a message lookup table using computed properties: + // // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer + // const statusMessages = { + // [ TextDocument.ReadError ]: qsTr("Failed to load “%1”"), + // [ TextDocument.WriteError ]: qsTr("Failed to save “%1”"), + // [ TextDocument.NonLocalFileError ]: qsTr("Not a local file: “%1”"), + // } + // const err = statusMessages[textDocument.status] + // if (err) { + // errorDialog.text = err.arg(textDocument.source) + // errorDialog.open() + // } + // } + } + + FontMetrics { + id: fontMetrics + + font: areaText.font + } + } + } + TextArea { + id: areaConsole + + height: 100 + placeholderText: qsTr("Placeholder for bash terminal.") + placeholderTextColor: "white" + readOnly: true + wrapMode: TextArea.Wrap + background: Rectangle { + color: "#2b2b2b" + implicitHeight: 100 + // border.color: control.enabled ? "#21be2b" : "transparent" + } + } + + // We use an inline component to customize the horizontal and vertical + // scroll-bars. This is convenient when the component is only used in one file. + component MyScrollBar: ScrollBar { + id: scrollBar + + // Scroll bar gutter + background: Rectangle { + color: "#3b3b3b" + implicitHeight: scrollBar.interactive ? 8 : 4 + implicitWidth: scrollBar.interactive ? 8 : 4 + opacity: scrollBar.active && scrollBar.size < 1.0 ? 1.0 : 0.0 + + Behavior on opacity { + OpacityAnimator { + duration: 500 + } + } + } + + // Scroll bar + contentItem: Rectangle { + color: "#4b4f51" + implicitHeight: scrollBar.interactive ? 8 : 4 + implicitWidth: scrollBar.interactive ? 8 : 4 + opacity: scrollBar.active && scrollBar.size < 1.0 ? 1.0 : 0.2 + + Behavior on opacity { + OpacityAnimator { + duration: 1000 + } + } + } + } +} diff --git a/qml/ProjectView/ClideProjectView.qml b/qml/ProjectView/ClideProjectView.qml new file mode 100644 index 0000000..abee55a --- /dev/null +++ b/qml/ProjectView/ClideProjectView.qml @@ -0,0 +1,54 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import "../Editor" + +SplitView { + Layout.fillHeight: true + Layout.fillWidth: true + anchors.fill: parent + + // Customized handle to drag between the Navigation and the Editor. + handle: Rectangle { + border.color: SplitHandle.hovered ? "#303234" : "#3c3f41" + color: SplitHandle.pressed ? "#4b4f51" : "#3c3f41" + implicitWidth: 8 + opacity: SplitHandle.hovered || navigationView.width < 15 ? 1.0 : 0.0 + + Behavior on opacity { + OpacityAnimator { + duration: 1400 + } + } + } + + Rectangle { + id: navigationView + + SplitView.fillHeight: true + SplitView.preferredWidth: 200 + color: "#303234" + + StackLayout { + anchors.fill: parent + + // Shows the help text. + TextArea { + placeholderText: qsTr("File system view placeholder") + placeholderTextColor: "white" + readOnly: true + wrapMode: TextArea.Wrap + } + + // TODO: Shows the files on the file system. + // ClideTreeView { + // id: fileSystemView + // color: Colors.surface1 + // onFileClicked: path => root.currentFilePath = path + // } + } + } + ClideEditor { + } +} diff --git a/qml/main.qml b/qml/main.qml index 5f4f592..2632981 100644 --- a/qml/main.qml +++ b/qml/main.qml @@ -4,6 +4,7 @@ import QtQuick.Layouts import QtQuick.Dialogs import "Menu" +import "ProjectView" import clide.module 1.0 @@ -27,243 +28,7 @@ ApplicationWindow { title: qsTr("Error") } - RowLayout { - anchors.fill: parent - spacing: 0 - - SplitView { - Layout.fillHeight: true - Layout.fillWidth: true - - // Customized handle to drag between the Navigation and the Editor. - handle: Rectangle { - border.color: SplitHandle.hovered ? "#303234" : "#3c3f41" - color: SplitHandle.pressed ? "#4b4f51" : "#3c3f41" - implicitWidth: 8 - opacity: SplitHandle.hovered || navigationView.width < 15 ? 1.0 : 0.0 - - Behavior on opacity { - OpacityAnimator { - duration: 1400 - } - } - } - - Rectangle { - id: navigationView - - SplitView.fillHeight: true - SplitView.preferredWidth: 200 - color: "#303234" - - StackLayout { - anchors.fill: parent - - // Shows the help text. - TextArea { - placeholderText: qsTr("File system view placeholder") - placeholderTextColor: "white" - readOnly: true - wrapMode: TextArea.Wrap - } - - // TODO: Shows the files on the file system. - // ClideProjectView { - // id: fileSystemView - // color: Colors.surface1 - // onFileClicked: path => root.currentFilePath = path - // } - } - } - SplitView { - Layout.fillHeight: true - Layout.fillWidth: true - orientation: Qt.Vertical - - // Customized handle to drag between the Navigation and the Editor. - handle: Rectangle { - border.color: SplitHandle.hovered ? "#2b2b2b" : "#3c3f41" - color: SplitHandle.pressed ? "#4b4f51" : "#3c3f41" - implicitHeight: 8 - opacity: SplitHandle.hovered || navigationView.width < 15 ? 1.0 : 0.0 - - Behavior on opacity { - OpacityAnimator { - duration: 1400 - } - } - } - - RowLayout { - // We use a flickable to synchronize the position of the editor and - // the line numbers. This is necessary because the line numbers can - // extend the available height. - Flickable { - id: lineNumbers - - Layout.fillHeight: true - Layout.fillWidth: false - - // Calculate the width based on the logarithmic scale. - Layout.preferredWidth: fontMetrics.averageCharacterWidth * (Math.floor(Math.log10(areaText.lineCount)) + 1) + 10 - contentY: editorFlickable.contentY - interactive: false - visible: true - - Column { - anchors.fill: parent - - Repeater { - id: repeatedLineNumbers - - delegate: Item { - required property int index - - height: Math.ceil(fontMetrics.lineSpacing) - width: parent.width - - Label { - id: numbers - - color: "white" - font: areaText.font - height: parent.height - horizontalAlignment: Text.AlignLeft - text: parent.index + 1 - verticalAlignment: Text.AlignVCenter - width: parent.width - } - Rectangle { - id: indicator - - anchors.left: numbers.right - color: Qt.darker("#FFF", 3) - height: parent.height - width: 1 - } - } - model: LineCount { - id: lineCountModel - - // This count sets the max line numbers shown in the gutter. - count: areaText.lineCount - } - } - } - } - Flickable { - id: editorFlickable - - height: 650 - - property alias areaText: areaText - - Layout.fillHeight: true - Layout.fillWidth: true - boundsBehavior: Flickable.StopAtBounds - - ScrollBar.horizontal: MyScrollBar { - } - ScrollBar.vertical: MyScrollBar { - } - TextArea.flickable: TextArea { - id: areaText - - color: "#ccced3" - focus: true - persistentSelection: true - selectByMouse: true - // selectedTextColor: control.palette.highlightedText - // selectionColor: control.palette.highlight - textFormat: Qt.AutoText - wrapMode: TextArea.Wrap - - background: Rectangle { - color: "#2b2b2b" - } - - onLinkActivated: function (link) { - Qt.openUrlExternally(link); - } - - // TODO: Handle saving - // Component.onCompleted: { - // if (Qt.application.arguments.length === 2) - // textDocument.source = "file:" + Qt.application.arguments[1] - // else - // textDocument.source = "qrc:/texteditor.html" - // } - // textDocument.onStatusChanged: { - // // a message lookup table using computed properties: - // // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer - // const statusMessages = { - // [ TextDocument.ReadError ]: qsTr("Failed to load “%1”"), - // [ TextDocument.WriteError ]: qsTr("Failed to save “%1”"), - // [ TextDocument.NonLocalFileError ]: qsTr("Not a local file: “%1”"), - // } - // const err = statusMessages[textDocument.status] - // if (err) { - // errorDialog.text = err.arg(textDocument.source) - // errorDialog.open() - // } - // } - } - - FontMetrics { - id: fontMetrics - - font: areaText.font - } - } - } - TextArea { - id: areaConsole - - height: 100 - placeholderText: qsTr("Placeholder for bash terminal.") - placeholderTextColor: "white" - readOnly: true - wrapMode: TextArea.Wrap - - background: Rectangle { - color: "#2b2b2b" - implicitHeight: 100 - // border.color: control.enabled ? "#21be2b" : "transparent" - } - } - } - } - } - - // We use an inline component to customize the horizontal and vertical - // scroll-bars. This is convenient when the component is only used in one file. - component MyScrollBar: ScrollBar { - id: scrollBar - - background: Rectangle { - color: "#2b2b2b" - implicitHeight: scrollBar.interactive ? 8 : 4 - implicitWidth: scrollBar.interactive ? 8 : 4 - opacity: scrollBar.active && scrollBar.size < 1.0 ? 1.0 : 0.0 - - Behavior on opacity { - OpacityAnimator { - duration: 500 - } - } - } - contentItem: Rectangle { - color: "#4b4f51" - implicitHeight: scrollBar.interactive ? 8 : 4 - implicitWidth: scrollBar.interactive ? 8 : 4 - opacity: scrollBar.active && scrollBar.size < 1.0 ? 1.0 : 0.0 - - Behavior on opacity { - OpacityAnimator { - duration: 1000 - } - } - } + ClideProjectView { } } -- 2.47.2 From 500a329dea9e45e300fdeb491f47963f83d96856 Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Sat, 29 Mar 2025 20:14:42 -0400 Subject: [PATCH 05/73] Separate LineCount from main. --- build.rs | 2 +- qml/Editor/ClideEditor.qml | 16 ++---- src/line_count.rs | 103 +++++++++++++++++++++++++++++++++++++ src/main.rs | 100 +---------------------------------- 4 files changed, 109 insertions(+), 112 deletions(-) create mode 100644 src/line_count.rs diff --git a/build.rs b/build.rs index 83e59a6..5328c17 100644 --- a/build.rs +++ b/build.rs @@ -10,7 +10,7 @@ fn main() { .qt_module("Network") .qml_module(QmlModule { uri: "clide.module", - rust_files: &["src/main.rs"], + rust_files: &["src/line_count.rs"], qml_files: &["qml/main.qml", "qml/ProjectView/ClideProjectView.qml", "qml/Editor/ClideEditor.qml", diff --git a/qml/Editor/ClideEditor.qml b/qml/Editor/ClideEditor.qml index c91bd04..a527508 100644 --- a/qml/Editor/ClideEditor.qml +++ b/qml/Editor/ClideEditor.qml @@ -22,7 +22,6 @@ SplitView { } } } - RowLayout { // We use a flickable to synchronize the position of the editor and // the line numbers. This is necessary because the line numbers can @@ -41,14 +40,16 @@ SplitView { Column { anchors.fill: parent + topPadding: 6 Repeater { id: repeatedLineNumbers + // Each line number in the gutter. delegate: Item { required property int index - height: Math.ceil(fontMetrics.lineSpacing) + height: 17 width: parent.width Label { @@ -71,12 +72,7 @@ SplitView { width: 1 } } - model: LineCount { - id: lineCountModel - - // This count sets the max line numbers shown in the gutter. - count: areaText.lineCount - } + model: areaText.lineCount } } } @@ -107,10 +103,6 @@ SplitView { textFormat: Qt.AutoText wrapMode: TextArea.Wrap - onTextChanged: { - console.log("Updated line count: " + areaText.lineCount) - } - background: Rectangle { color: "#2b2b2b" } diff --git a/src/line_count.rs b/src/line_count.rs new file mode 100644 index 0000000..a66834e --- /dev/null +++ b/src/line_count.rs @@ -0,0 +1,103 @@ +// TODO: Header + +#[cxx_qt::bridge] +pub mod qobject { + unsafe extern "C++" { + include!("cxx-qt-lib/qvariant.h"); + type QVariant = cxx_qt_lib::QVariant; + include!(); + type QModelIndex = cxx_qt_lib::QModelIndex; + type QAbstractListModel; + } + + unsafe extern "RustQt" { + #[qobject] + #[base = QAbstractListModel] + type AbstractListModel = super::AbstractListModelRust; + + #[qobject] + #[base = AbstractListModel] + #[qml_element] + #[qproperty(i32, count)] + type LineCount = super::LineCountRust; + } + + unsafe extern "RustQt" { + #[cxx_name = "beginInsertRows"] + #[inherit] + fn beginInsertRows(self: Pin<&mut LineCount>, parent: &QModelIndex, first: i32, last: i32); + + #[cxx_name = "endInsertRows"] + #[inherit] + fn endInsertRows(self: Pin<&mut LineCount>); + + #[cxx_name = "beginRemoveRows"] + #[inherit] + fn beginRemoveRows(self: Pin<&mut LineCount>, parent: &QModelIndex, first: i32, last: i32); + + #[cxx_name = "endRemoveRows"] + #[inherit] + fn endRemoveRows(self: Pin<&mut LineCount>); + + #[qinvokable] + fn set_line_count(self: Pin<&mut LineCount>, line_count: i32); + + #[qinvokable] + #[cxx_override] + fn data(self: &LineCount, index: &QModelIndex, role: i32) -> QVariant; + + #[qinvokable] + #[cxx_override] + #[cxx_name = "rowCount"] + fn row_count(self: &LineCount, _parent: &QModelIndex) -> i32; + } +} + +use cxx_qt::CxxQtType; +use cxx_qt_lib::{QModelIndex, QVariant}; + +impl qobject::LineCount { + pub fn set_line_count(mut self: std::pin::Pin<&mut Self>, line_count: i32) { + let current_count = self.as_mut().rust_mut().count; + if line_count < 0 || current_count == line_count { + log::warn!( + "Can't set line count: {}; Current count: {}", + line_count, + current_count + ); + return; + } + if current_count < line_count { + self.as_mut() + .beginInsertRows(&QModelIndex::default(), current_count, line_count - 1); + self.as_mut().endInsertRows(); + } else if current_count > line_count { + self.as_mut() + .beginRemoveRows(&QModelIndex::default(), line_count, current_count - 1); + self.as_mut().endRemoveRows(); + } + self.as_mut().rust_mut().count = line_count; + log::warn!( + "Line count changed from {} to {}", + current_count, + line_count + ); + } + + pub fn row_count(self: &Self, _parent: &QModelIndex) -> i32 { + *self.count() + } + + pub fn data(self: &Self, _index: &QModelIndex, _role: i32) -> QVariant { + QVariant::default() + } +} + +/// A struct which inherits from QAbstractListModel +#[derive(Default)] +pub struct AbstractListModelRust {} + +#[derive(Default)] +pub struct LineCountRust { + pub count: i32, +} diff --git a/src/main.rs b/src/main.rs index 4239053..cdbdc57 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,104 +1,6 @@ // TODO: Header -#[cxx_qt::bridge] -pub mod qobject { - unsafe extern "C++" { - include!("cxx-qt-lib/qvariant.h"); - type QVariant = cxx_qt_lib::QVariant; - include!(); - type QModelIndex = cxx_qt_lib::QModelIndex; - type QAbstractListModel; - } - - unsafe extern "RustQt" { - #[qobject] - #[base = QAbstractListModel] - type AbstractListModel = super::AbstractListModelRust; - - #[qobject] - #[base = AbstractListModel] - #[qml_element] - #[qproperty(i32, count)] - type LineCount = super::LineCountRust; - - #[cxx_name = "beginInsertRows"] - #[inherit] - fn beginInsertRows(self: Pin<&mut LineCount>, parent: &QModelIndex, first: i32, last: i32); - - #[cxx_name = "endInsertRows"] - #[inherit] - fn endInsertRows(self: Pin<&mut LineCount>); - - #[cxx_name = "beginRemoveRows"] - #[inherit] - fn beginRemoveRows(self: Pin<&mut LineCount>, parent: &QModelIndex, first: i32, last: i32); - - #[cxx_name = "endRemoveRows"] - #[inherit] - fn endRemoveRows(self: Pin<&mut LineCount>); - - #[qinvokable] - pub fn set_line_count(self: Pin<&mut LineCount>, line_count: i32); - - #[qinvokable] - #[cxx_override] - fn data(self: &LineCount, index: &QModelIndex, role: i32) -> QVariant; - - #[qinvokable] - #[cxx_override] - #[cxx_name = "rowCount"] - fn row_count(self: &LineCount, _parent: &QModelIndex) -> i32; - } -} - -use cxx_qt::CxxQtType; -use cxx_qt_lib::{QModelIndex, QVariant}; - -impl qobject::LineCount { - pub fn set_line_count(mut self: std::pin::Pin<&mut Self>, line_count: i32) { - let current_count = self.as_mut().rust_mut().count; - if line_count < 0 || current_count == line_count { - log::warn!( - "Can't set line count: {}; Current count: {}", - line_count, - current_count - ); - return; - } - if current_count < line_count { - self.as_mut() - .beginInsertRows(&QModelIndex::default(), current_count, line_count - 1); - self.as_mut().endInsertRows(); - } else if current_count > line_count { - self.as_mut() - .beginRemoveRows(&QModelIndex::default(), line_count, current_count - 1); - self.as_mut().endRemoveRows(); - } - self.as_mut().rust_mut().count = line_count; - log::warn!( - "Line count changed from {} to {}", - current_count, - line_count - ); - } - - pub fn row_count(self: &Self, _parent: &QModelIndex) -> i32 { - *self.count() - } - - pub fn data(self: &Self, _index: &QModelIndex, _role: i32) -> QVariant { - QVariant::default() - } -} - -/// A struct which inherits from QAbstractListModel -#[derive(Default)] -pub struct AbstractListModelRust {} - -#[derive(Default)] -pub struct LineCountRust { - pub count: i32, -} +pub mod line_count; fn main() { use cxx_qt_lib::{QGuiApplication, QQmlApplicationEngine, QUrl}; -- 2.47.2 From be9981291cd722bdf0bfc50a88abc34475e03775 Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Sat, 29 Mar 2025 20:20:28 -0400 Subject: [PATCH 06/73] Speed up animation for dock handles. --- qml/Editor/ClideEditor.qml | 2 +- qml/ProjectView/ClideProjectView.qml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/qml/Editor/ClideEditor.qml b/qml/Editor/ClideEditor.qml index a527508..853df35 100644 --- a/qml/Editor/ClideEditor.qml +++ b/qml/Editor/ClideEditor.qml @@ -18,7 +18,7 @@ SplitView { Behavior on opacity { OpacityAnimator { - duration: 1400 + duration: 700 } } } diff --git a/qml/ProjectView/ClideProjectView.qml b/qml/ProjectView/ClideProjectView.qml index abee55a..c450f2c 100644 --- a/qml/ProjectView/ClideProjectView.qml +++ b/qml/ProjectView/ClideProjectView.qml @@ -18,7 +18,7 @@ SplitView { Behavior on opacity { OpacityAnimator { - duration: 1400 + duration: 700 } } } -- 2.47.2 From d2f5823594d9cb5fc26160ec2c4bfaf4d30d095f Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Sun, 30 Mar 2025 11:20:21 -0400 Subject: [PATCH 07/73] Add RustColors singleton helper. --- build.rs | 3 +- qml/Editor/ClideEditor.qml | 64 ++++++----- qml/Menu/ClideMenu.qml | 4 +- qml/Menu/ClideMenuBar.qml | 8 +- qml/Menu/ClideMenuBar.qml.autosave | 140 ------------------------- qml/Menu/ClideMenuBarItem.qml | 4 +- qml/Menu/ClideMenuBarItem.qml.autosave | 16 --- qml/ProjectView/ClideProjectView.qml | 17 +-- qml/main.qml | 3 +- src/colors.rs | 77 ++++++++++++++ src/main.rs | 6 ++ 11 files changed, 145 insertions(+), 197 deletions(-) delete mode 100644 qml/Menu/ClideMenuBar.qml.autosave delete mode 100644 qml/Menu/ClideMenuBarItem.qml.autosave create mode 100644 src/colors.rs diff --git a/build.rs b/build.rs index 5328c17..e44b48b 100644 --- a/build.rs +++ b/build.rs @@ -10,7 +10,8 @@ fn main() { .qt_module("Network") .qml_module(QmlModule { uri: "clide.module", - rust_files: &["src/line_count.rs"], + rust_files: &["src/line_count.rs", + "src/colors.rs"], qml_files: &["qml/main.qml", "qml/ProjectView/ClideProjectView.qml", "qml/Editor/ClideEditor.qml", diff --git a/qml/Editor/ClideEditor.qml b/qml/Editor/ClideEditor.qml index 853df35..26eea2e 100644 --- a/qml/Editor/ClideEditor.qml +++ b/qml/Editor/ClideEditor.qml @@ -11,14 +11,15 @@ SplitView { // Customized handle to drag between the Editor and the Console. handle: Rectangle { - border.color: SplitHandle.hovered ? "#2b2b2b" : "#3c3f41" - color: SplitHandle.pressed ? "#4b4f51" : "#3c3f41" + border.color: SplitHandle.pressed ? RustColors.pressed : SplitHandle.hovered ? RustColors.hovered : RustColors.gutter + color: SplitHandle.pressed ? RustColors.pressed : SplitHandle.hovered ? RustColors.hovered : RustColors.gutter implicitHeight: 8 - opacity: SplitHandle.hovered || areaConsole.height < 15 ? 1.0 : 0.0 + radius: 2.5 - Behavior on opacity { - OpacityAnimator { - duration: 700 + // Execute these behaviors when the color is changed. + Behavior on color { + ColorAnimation { + duration: 400 } } } @@ -33,7 +34,7 @@ SplitView { Layout.fillWidth: false // Calculate the width based on the logarithmic scale. - Layout.preferredWidth: fontMetrics.averageCharacterWidth * (Math.floor(Math.log10(areaText.lineCount)) + 1) + 10 + Layout.preferredWidth: fontMetrics.averageCharacterWidth * (Math.floor(Math.log10(textArea.lineCount)) + 1) + 10 contentY: editorFlickable.contentY interactive: false visible: true @@ -49,14 +50,15 @@ SplitView { delegate: Item { required property int index - height: 17 + // Calculate the height of each line in the text area. + height: textArea.contentHeight / textArea.lineCount width: parent.width Label { id: numbers color: "white" - font: areaText.font + font: textArea.font height: parent.height horizontalAlignment: Text.AlignLeft text: parent.index + 1 @@ -67,20 +69,19 @@ SplitView { id: indicator anchors.left: numbers.right - color: Qt.darker("#FFF", 3) + color: RustColors.linenumber height: parent.height width: 1 } } - model: areaText.lineCount + // TODO: Bug where text wrapping shows as new line number. + model: textArea.lineCount } } } Flickable { id: editorFlickable - property alias areaText: areaText - Layout.fillHeight: true Layout.fillWidth: true boundsBehavior: Flickable.StopAtBounds @@ -91,20 +92,19 @@ SplitView { ScrollBar.vertical: MyScrollBar { } TextArea.flickable: TextArea { - id: areaText + id: textArea - - color: "#ccced3" + color: RustColors.editor_text focus: true persistentSelection: true selectByMouse: true - // selectedTextColor: control.palette.highlightedText - // selectionColor: control.palette.highlight + // selectedTextColor: RustColors.editor_highlighted_text + // selectionColor: RustColors.editor_highlight textFormat: Qt.AutoText wrapMode: TextArea.Wrap background: Rectangle { - color: "#2b2b2b" + color: RustColors.editor_background } onLinkActivated: function (link) { @@ -137,7 +137,7 @@ SplitView { FontMetrics { id: fontMetrics - font: areaText.font + font: textArea.font } } } @@ -150,9 +150,9 @@ SplitView { readOnly: true wrapMode: TextArea.Wrap background: Rectangle { - color: "#2b2b2b" + color: RustColors.editor_background implicitHeight: 100 - // border.color: control.enabled ? "#21be2b" : "transparent" + // border.color: control.enabled ? RustColors.active : RustColors.inactive } } @@ -163,11 +163,12 @@ SplitView { // Scroll bar gutter background: Rectangle { - color: "#3b3b3b" implicitHeight: scrollBar.interactive ? 8 : 4 implicitWidth: scrollBar.interactive ? 8 : 4 - opacity: scrollBar.active && scrollBar.size < 1.0 ? 1.0 : 0.0 + color: RustColors.scrollbar_gutter + // Fade the scrollbar gutter when inactive. + opacity: scrollBar.active && scrollBar.size < 1.0 ? 1.0 : 0.2 Behavior on opacity { OpacityAnimator { duration: 500 @@ -177,14 +178,23 @@ SplitView { // Scroll bar contentItem: Rectangle { - color: "#4b4f51" implicitHeight: scrollBar.interactive ? 8 : 4 implicitWidth: scrollBar.interactive ? 8 : 4 - opacity: scrollBar.active && scrollBar.size < 1.0 ? 1.0 : 0.2 + // If we don't need a scrollbar, fallback to the gutter color. + // If the scrollbar is required change it's color based on activity. + color: scrollBar.size < 1.0 ? scrollBar.active ? RustColors.scrollbar_active : RustColors.scrollbar : RustColors.scrollbar_gutter + // Smooth transition between color changes based on the state above. + Behavior on color { + ColorAnimation { + duration: 1000 + } + } + // Fade the scrollbar when inactive. + opacity: scrollBar.active && scrollBar.size < 1.0 ? 1.0 : 0.35 Behavior on opacity { OpacityAnimator { - duration: 1000 + duration: 500 } } } diff --git a/qml/Menu/ClideMenu.qml b/qml/Menu/ClideMenu.qml index 8b89d96..c5e5a1e 100644 --- a/qml/Menu/ClideMenu.qml +++ b/qml/Menu/ClideMenu.qml @@ -1,9 +1,11 @@ import QtQuick import QtQuick.Controls +import clide.module 1.0 + Menu { background: Rectangle { - color: "#3c3f41" + color: RustColors.menubar implicitWidth: 100 radius: 2 } diff --git a/qml/Menu/ClideMenuBar.qml b/qml/Menu/ClideMenuBar.qml index f252803..a165f2d 100644 --- a/qml/Menu/ClideMenuBar.qml +++ b/qml/Menu/ClideMenuBar.qml @@ -1,10 +1,12 @@ import QtQuick import QtQuick.Controls +import clide.module 1.0 + MenuBar { background: Rectangle { - color: "#3c3f41" - border.color: "#575757" + color: RustColors.menubar + border.color: RustColors.menubar_border } Action { @@ -44,7 +46,7 @@ MenuBar { MenuSeparator { background: Rectangle { border.color: color - color: "#3c3f41" + color: RustColors.menubar_border implicitHeight: 3 implicitWidth: 200 } diff --git a/qml/Menu/ClideMenuBar.qml.autosave b/qml/Menu/ClideMenuBar.qml.autosave deleted file mode 100644 index f252803..0000000 --- a/qml/Menu/ClideMenuBar.qml.autosave +++ /dev/null @@ -1,140 +0,0 @@ -import QtQuick -import QtQuick.Controls - -MenuBar { - background: Rectangle { - color: "#3c3f41" - border.color: "#575757" - } - - Action { - id: actionNewProject - - text: qsTr("&New Project...") - } - Action { - id: actionOpen - - text: qsTr("&Open...") - } - Action { - id: actionSave - - text: qsTr("&Save") - } - Action { - id: actionExit - - text: qsTr("&Exit") - - onTriggered: Qt.quit() - } - ClideMenu { - title: qsTr("&File") - - ClideMenuBarItem { - action: actionNewProject - } - ClideMenuBarItem { - action: actionOpen - } - ClideMenuBarItem { - action: actionSave - } - MenuSeparator { - background: Rectangle { - border.color: color - color: "#3c3f41" - implicitHeight: 3 - implicitWidth: 200 - } - } - ClideMenuBarItem { - action: actionExit - } - } - Action { - id: actionUndo - - text: qsTr("&Undo") - } - Action { - id: actionRedo - - text: qsTr("&Redo") - } - Action { - id: actionCut - - text: qsTr("&Cut") - } - Action { - id: actionCopy - - text: qsTr("&Copy") - } - Action { - id: actionPaste - - text: qsTr("&Paste") - } - ClideMenu { - title: qsTr("&Edit") - - ClideMenuBarItem { - action: actionUndo - } - ClideMenuBarItem { - action: actionRedo - } - ClideMenuBarItem { - action: actionCut - } - ClideMenuBarItem { - action: actionCopy - } - ClideMenuBarItem { - action: actionPaste - } - } - Action { - id: actionToolWindows - - text: qsTr("&Tool Windows") - } - Action { - id: actionAppearance - - text: qsTr("&Appearance") - } - ClideMenu { - title: qsTr("&View") - - ClideMenuBarItem { - action: actionToolWindows - } - ClideMenuBarItem { - action: actionAppearance - } - } - Action { - id: actionDocumentation - - text: qsTr("&Documentation") - } - Action { - id: actionAbout - - text: qsTr("&About") - } - ClideMenu { - title: qsTr("&Help") - - ClideMenuBarItem { - action: actionDocumentation - } - ClideMenuBarItem { - action: actionAbout - } - } -} diff --git a/qml/Menu/ClideMenuBarItem.qml b/qml/Menu/ClideMenuBarItem.qml index 15f049f..899b22d 100644 --- a/qml/Menu/ClideMenuBarItem.qml +++ b/qml/Menu/ClideMenuBarItem.qml @@ -1,11 +1,13 @@ import QtQuick import QtQuick.Controls +import clide.module 1.0 + MenuItem { id: root background: Rectangle { - color: root.hovered ? "#4b4f51" : "#3c3f41" // Hover effect + color: root.hovered ? RustColors.hovered : RustColors.unhovered radius: 2.5 } contentItem: IconLabel { diff --git a/qml/Menu/ClideMenuBarItem.qml.autosave b/qml/Menu/ClideMenuBarItem.qml.autosave deleted file mode 100644 index 15f049f..0000000 --- a/qml/Menu/ClideMenuBarItem.qml.autosave +++ /dev/null @@ -1,16 +0,0 @@ -import QtQuick -import QtQuick.Controls - -MenuItem { - id: root - - background: Rectangle { - color: root.hovered ? "#4b4f51" : "#3c3f41" // Hover effect - radius: 2.5 - } - contentItem: IconLabel { - color: "black" - font.family: "Helvetica" - text: root.text - } -} diff --git a/qml/ProjectView/ClideProjectView.qml b/qml/ProjectView/ClideProjectView.qml index c450f2c..a1aea84 100644 --- a/qml/ProjectView/ClideProjectView.qml +++ b/qml/ProjectView/ClideProjectView.qml @@ -4,6 +4,8 @@ import QtQuick.Layouts import "../Editor" +import clide.module 1.0 + SplitView { Layout.fillHeight: true Layout.fillWidth: true @@ -11,14 +13,15 @@ SplitView { // Customized handle to drag between the Navigation and the Editor. handle: Rectangle { - border.color: SplitHandle.hovered ? "#303234" : "#3c3f41" - color: SplitHandle.pressed ? "#4b4f51" : "#3c3f41" + border.color: SplitHandle.pressed ? RustColors.pressed : SplitHandle.hovered ? RustColors.hovered : RustColors.gutter + color: SplitHandle.pressed ? RustColors.pressed : SplitHandle.hovered ? RustColors.hovered : RustColors.gutter implicitWidth: 8 - opacity: SplitHandle.hovered || navigationView.width < 15 ? 1.0 : 0.0 + radius: 2.5 - Behavior on opacity { - OpacityAnimator { - duration: 700 + // Execute these behaviors when the color is changed. + Behavior on color { + ColorAnimation { + duration: 400 } } } @@ -28,7 +31,7 @@ SplitView { SplitView.fillHeight: true SplitView.preferredWidth: 200 - color: "#303234" + color: RustColors.explorer_background StackLayout { anchors.fill: parent diff --git a/qml/main.qml b/qml/main.qml index 2632981..d031490 100644 --- a/qml/main.qml +++ b/qml/main.qml @@ -21,8 +21,9 @@ ApplicationWindow { Rectangle { anchors.fill: parent - color: "#1e1f22" + color: RustColors.gutter } + MessageDialog { id: errorDialog diff --git a/src/colors.rs b/src/colors.rs new file mode 100644 index 0000000..1b699b5 --- /dev/null +++ b/src/colors.rs @@ -0,0 +1,77 @@ +#[cxx_qt::bridge] +pub mod qobject { + unsafe extern "C++" { + include!("cxx-qt-lib/qcolor.h"); + type QColor = cxx_qt_lib::QColor; + } + + unsafe extern "RustQt" { + #[qobject] + #[qml_element] + #[qml_singleton] + #[qproperty(QColor, hovered)] + #[qproperty(QColor, unhovered)] + #[qproperty(QColor, pressed)] + #[qproperty(QColor, menubar)] + #[qproperty(QColor, menubar_border)] + #[qproperty(QColor, scrollbar)] + #[qproperty(QColor, scrollbar_active)] + #[qproperty(QColor, scrollbar_gutter)] + #[qproperty(QColor, linenumber)] + #[qproperty(QColor, active)] + #[qproperty(QColor, inactive)] + #[qproperty(QColor, editor_background)] + #[qproperty(QColor, editor_text)] + #[qproperty(QColor, editor_highlighted_text)] + #[qproperty(QColor, editor_highlight)] + #[qproperty(QColor, gutter)] + #[qproperty(QColor, explorer_background)] + type RustColors = super::RustColorsImpl; + } +} + +use cxx_qt_lib::QColor; + +pub struct RustColorsImpl { + hovered: QColor, + unhovered: QColor, + pressed: QColor, + menubar: QColor, + menubar_border: QColor, + scrollbar: QColor, + scrollbar_active: QColor, + scrollbar_gutter: QColor, + linenumber: QColor, + active: QColor, + inactive: QColor, + editor_background: QColor, + editor_text: QColor, + editor_highlighted_text: QColor, + editor_highlight: QColor, + gutter: QColor, + explorer_background: QColor, +} + +impl Default for RustColorsImpl { + fn default() -> Self { + Self { + hovered: QColor::try_from("#303234").unwrap(), + unhovered: QColor::try_from("#3c3f41").unwrap(), + pressed: QColor::try_from("#4b4f51").unwrap(), + menubar: QColor::try_from("#3c3f41").unwrap(), + menubar_border: QColor::try_from("#575757").unwrap(), + scrollbar: QColor::try_from("#4b4f51").unwrap(), + scrollbar_active: QColor::try_from("#4b4f51").unwrap(), + scrollbar_gutter: QColor::try_from("#3b3b3b").unwrap(), + linenumber: QColor::try_from("#FFF").unwrap(), + active: QColor::try_from("#a9acb0").unwrap(), + inactive: QColor::try_from("#FFF").unwrap(), + editor_background: QColor::try_from("#2b2b2b").unwrap(), + editor_text: QColor::try_from("#ccced3").unwrap(), + editor_highlighted_text: QColor::try_from("#ccced3").unwrap(), + editor_highlight: QColor::try_from("#ccced3").unwrap(), + gutter: QColor::try_from("#1e1f22").unwrap(), + explorer_background: QColor::try_from("#3c3f41").unwrap(), + } + } +} diff --git a/src/main.rs b/src/main.rs index cdbdc57..505c4cc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,9 @@ // TODO: Header +use cxx_qt_lib::QString; + pub mod line_count; +pub mod colors; fn main() { use cxx_qt_lib::{QGuiApplication, QQmlApplicationEngine, QUrl}; @@ -8,6 +11,9 @@ fn main() { let mut app = QGuiApplication::new(); let mut engine = QQmlApplicationEngine::new(); + if let Some(engine) = engine.as_mut() { + engine.add_import_path(&QString::from("qml/")); + } if let Some(engine) = engine.as_mut() { engine.load(&QUrl::from("qml/main.qml")); } -- 2.47.2 From 3b8e407632a088d293d839ecc9a35ea5b1ad8556 Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Sun, 30 Mar 2025 11:47:16 -0400 Subject: [PATCH 08/73] Consolidate source files. --- README.md | 7 ++++ build.rs | 4 +-- qml/Menu/ClideMenu.qml | 12 ------- qml/Menu/ClideMenuBar.qml | 63 +++++++++++++++++++++++++++-------- qml/Menu/ClideMenuBarItem.qml | 18 ---------- 5 files changed, 58 insertions(+), 46 deletions(-) delete mode 100644 qml/Menu/ClideMenu.qml delete mode 100644 qml/Menu/ClideMenuBarItem.qml diff --git a/README.md b/README.md index fce61ed..f081125 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,13 @@ The [Qt Installer](https://www.qt.io/download-qt-installer) will provide the lat If using RustRover be sure to set your QML binaries path in the settings menu. If Qt was installed to its default directory this will be `$HOME/Qt/6.8.3/gcc_64/bin/`. +Viewing documentation in the web browser is possible, but you will end up in a mess of tabs. +Using Qt Assistant is recommended. It comes with Qt6 when installed. Run the following command to start it. + +```bash +nohup $HOME/Qt/6.8.3/gcc_64/bin/assistant > /dev/null 2>&1 & +``` + ### Resources Some helpful links for reading up on QML if you're just getting started. diff --git a/build.rs b/build.rs index e44b48b..770879d 100644 --- a/build.rs +++ b/build.rs @@ -15,9 +15,7 @@ fn main() { qml_files: &["qml/main.qml", "qml/ProjectView/ClideProjectView.qml", "qml/Editor/ClideEditor.qml", - "qml/Menu/ClideMenu.qml", - "qml/Menu/ClideMenuBar.qml", - "qml/Menu/ClideMenuBarItem.qml"], + "qml/Menu/ClideMenuBar.qml"], ..Default::default() }) .build(); diff --git a/qml/Menu/ClideMenu.qml b/qml/Menu/ClideMenu.qml deleted file mode 100644 index c5e5a1e..0000000 --- a/qml/Menu/ClideMenu.qml +++ /dev/null @@ -1,12 +0,0 @@ -import QtQuick -import QtQuick.Controls - -import clide.module 1.0 - -Menu { - background: Rectangle { - color: RustColors.menubar - implicitWidth: 100 - radius: 2 - } -} diff --git a/qml/Menu/ClideMenuBar.qml b/qml/Menu/ClideMenuBar.qml index a165f2d..6c175cb 100644 --- a/qml/Menu/ClideMenuBar.qml +++ b/qml/Menu/ClideMenuBar.qml @@ -4,11 +4,39 @@ import QtQuick.Controls import clide.module 1.0 MenuBar { + // Base settings for each Menu. + component ClideMenu : Menu { + background: Rectangle { + color: RustColors.menubar + implicitWidth: 100 + radius: 2 + } + } + + // Base settings for each MenuItem. + component ClideMenuItem : MenuItem { + id: root + + background: Rectangle { + color: root.hovered ? RustColors.hovered : RustColors.unhovered + radius: 2.5 + } + contentItem: IconLabel { + color: "black" + font.family: "Helvetica" + text: root.text + } + } + + // Background for this MenuBar. background: Rectangle { color: RustColors.menubar border.color: RustColors.menubar_border } + + // + // File Menu Action { id: actionNewProject @@ -34,13 +62,13 @@ MenuBar { ClideMenu { title: qsTr("&File") - ClideMenuBarItem { + ClideMenuItem { action: actionNewProject } - ClideMenuBarItem { + ClideMenuItem { action: actionOpen } - ClideMenuBarItem { + ClideMenuItem { action: actionSave } MenuSeparator { @@ -51,10 +79,13 @@ MenuBar { implicitWidth: 200 } } - ClideMenuBarItem { + ClideMenuItem { action: actionExit } } + + // + // Edit Menu Action { id: actionUndo @@ -83,22 +114,25 @@ MenuBar { ClideMenu { title: qsTr("&Edit") - ClideMenuBarItem { + ClideMenuItem { action: actionUndo } - ClideMenuBarItem { + ClideMenuItem { action: actionRedo } - ClideMenuBarItem { + ClideMenuItem { action: actionCut } - ClideMenuBarItem { + ClideMenuItem { action: actionCopy } - ClideMenuBarItem { + ClideMenuItem { action: actionPaste } } + + // + // View Menu Action { id: actionToolWindows @@ -112,13 +146,16 @@ MenuBar { ClideMenu { title: qsTr("&View") - ClideMenuBarItem { + ClideMenuItem { action: actionToolWindows } - ClideMenuBarItem { + ClideMenuItem { action: actionAppearance } } + + // + // Help Menu Action { id: actionDocumentation @@ -132,10 +169,10 @@ MenuBar { ClideMenu { title: qsTr("&Help") - ClideMenuBarItem { + ClideMenuItem { action: actionDocumentation } - ClideMenuBarItem { + ClideMenuItem { action: actionAbout } } diff --git a/qml/Menu/ClideMenuBarItem.qml b/qml/Menu/ClideMenuBarItem.qml deleted file mode 100644 index 899b22d..0000000 --- a/qml/Menu/ClideMenuBarItem.qml +++ /dev/null @@ -1,18 +0,0 @@ -import QtQuick -import QtQuick.Controls - -import clide.module 1.0 - -MenuItem { - id: root - - background: Rectangle { - color: root.hovered ? RustColors.hovered : RustColors.unhovered - radius: 2.5 - } - contentItem: IconLabel { - color: "black" - font.family: "Helvetica" - text: root.text - } -} -- 2.47.2 From 094ac92fe42a1915cba9a453e80aca32cb1e20a3 Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Sun, 30 Mar 2025 11:49:00 -0400 Subject: [PATCH 09/73] Reorganize qml files. --- build.rs | 6 +++--- qml/{Editor => }/ClideEditor.qml | 0 qml/{Menu => }/ClideMenuBar.qml | 0 qml/{ProjectView => }/ClideProjectView.qml | 2 -- qml/main.qml | 3 --- 5 files changed, 3 insertions(+), 8 deletions(-) rename qml/{Editor => }/ClideEditor.qml (100%) rename qml/{Menu => }/ClideMenuBar.qml (100%) rename qml/{ProjectView => }/ClideProjectView.qml (98%) diff --git a/build.rs b/build.rs index 770879d..c3fc940 100644 --- a/build.rs +++ b/build.rs @@ -13,9 +13,9 @@ fn main() { rust_files: &["src/line_count.rs", "src/colors.rs"], qml_files: &["qml/main.qml", - "qml/ProjectView/ClideProjectView.qml", - "qml/Editor/ClideEditor.qml", - "qml/Menu/ClideMenuBar.qml"], + "qml/ClideProjectView.qml", + "qml/ClideEditor.qml", + "qml/ClideMenuBar.qml"], ..Default::default() }) .build(); diff --git a/qml/Editor/ClideEditor.qml b/qml/ClideEditor.qml similarity index 100% rename from qml/Editor/ClideEditor.qml rename to qml/ClideEditor.qml diff --git a/qml/Menu/ClideMenuBar.qml b/qml/ClideMenuBar.qml similarity index 100% rename from qml/Menu/ClideMenuBar.qml rename to qml/ClideMenuBar.qml diff --git a/qml/ProjectView/ClideProjectView.qml b/qml/ClideProjectView.qml similarity index 98% rename from qml/ProjectView/ClideProjectView.qml rename to qml/ClideProjectView.qml index a1aea84..5ceab61 100644 --- a/qml/ProjectView/ClideProjectView.qml +++ b/qml/ClideProjectView.qml @@ -2,8 +2,6 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts -import "../Editor" - import clide.module 1.0 SplitView { diff --git a/qml/main.qml b/qml/main.qml index d031490..cba1e07 100644 --- a/qml/main.qml +++ b/qml/main.qml @@ -3,9 +3,6 @@ import QtQuick.Controls import QtQuick.Layouts import QtQuick.Dialogs -import "Menu" -import "ProjectView" - import clide.module 1.0 ApplicationWindow { -- 2.47.2 From 0f055603a2ad5247d332b817ab876537307e9f64 Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Sun, 30 Mar 2025 11:51:23 -0400 Subject: [PATCH 10/73] Remove unused LineCount. --- build.rs | 3 +- src/line_count.rs | 103 ---------------------------------------------- src/main.rs | 1 - 3 files changed, 1 insertion(+), 106 deletions(-) delete mode 100644 src/line_count.rs diff --git a/build.rs b/build.rs index c3fc940..e5aea9e 100644 --- a/build.rs +++ b/build.rs @@ -10,8 +10,7 @@ fn main() { .qt_module("Network") .qml_module(QmlModule { uri: "clide.module", - rust_files: &["src/line_count.rs", - "src/colors.rs"], + rust_files: &["src/colors.rs"], qml_files: &["qml/main.qml", "qml/ClideProjectView.qml", "qml/ClideEditor.qml", diff --git a/src/line_count.rs b/src/line_count.rs deleted file mode 100644 index a66834e..0000000 --- a/src/line_count.rs +++ /dev/null @@ -1,103 +0,0 @@ -// TODO: Header - -#[cxx_qt::bridge] -pub mod qobject { - unsafe extern "C++" { - include!("cxx-qt-lib/qvariant.h"); - type QVariant = cxx_qt_lib::QVariant; - include!(); - type QModelIndex = cxx_qt_lib::QModelIndex; - type QAbstractListModel; - } - - unsafe extern "RustQt" { - #[qobject] - #[base = QAbstractListModel] - type AbstractListModel = super::AbstractListModelRust; - - #[qobject] - #[base = AbstractListModel] - #[qml_element] - #[qproperty(i32, count)] - type LineCount = super::LineCountRust; - } - - unsafe extern "RustQt" { - #[cxx_name = "beginInsertRows"] - #[inherit] - fn beginInsertRows(self: Pin<&mut LineCount>, parent: &QModelIndex, first: i32, last: i32); - - #[cxx_name = "endInsertRows"] - #[inherit] - fn endInsertRows(self: Pin<&mut LineCount>); - - #[cxx_name = "beginRemoveRows"] - #[inherit] - fn beginRemoveRows(self: Pin<&mut LineCount>, parent: &QModelIndex, first: i32, last: i32); - - #[cxx_name = "endRemoveRows"] - #[inherit] - fn endRemoveRows(self: Pin<&mut LineCount>); - - #[qinvokable] - fn set_line_count(self: Pin<&mut LineCount>, line_count: i32); - - #[qinvokable] - #[cxx_override] - fn data(self: &LineCount, index: &QModelIndex, role: i32) -> QVariant; - - #[qinvokable] - #[cxx_override] - #[cxx_name = "rowCount"] - fn row_count(self: &LineCount, _parent: &QModelIndex) -> i32; - } -} - -use cxx_qt::CxxQtType; -use cxx_qt_lib::{QModelIndex, QVariant}; - -impl qobject::LineCount { - pub fn set_line_count(mut self: std::pin::Pin<&mut Self>, line_count: i32) { - let current_count = self.as_mut().rust_mut().count; - if line_count < 0 || current_count == line_count { - log::warn!( - "Can't set line count: {}; Current count: {}", - line_count, - current_count - ); - return; - } - if current_count < line_count { - self.as_mut() - .beginInsertRows(&QModelIndex::default(), current_count, line_count - 1); - self.as_mut().endInsertRows(); - } else if current_count > line_count { - self.as_mut() - .beginRemoveRows(&QModelIndex::default(), line_count, current_count - 1); - self.as_mut().endRemoveRows(); - } - self.as_mut().rust_mut().count = line_count; - log::warn!( - "Line count changed from {} to {}", - current_count, - line_count - ); - } - - pub fn row_count(self: &Self, _parent: &QModelIndex) -> i32 { - *self.count() - } - - pub fn data(self: &Self, _index: &QModelIndex, _role: i32) -> QVariant { - QVariant::default() - } -} - -/// A struct which inherits from QAbstractListModel -#[derive(Default)] -pub struct AbstractListModelRust {} - -#[derive(Default)] -pub struct LineCountRust { - pub count: i32, -} diff --git a/src/main.rs b/src/main.rs index 505c4cc..6e5eac4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,7 +2,6 @@ use cxx_qt_lib::QString; -pub mod line_count; pub mod colors; fn main() { -- 2.47.2 From b0064c2f6978a35b15d617bd466c4c7fc6da9910 Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Sun, 30 Mar 2025 11:58:44 -0400 Subject: [PATCH 11/73] Update README. --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index f081125..6172640 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,9 @@ nohup $HOME/Qt/6.8.3/gcc_64/bin/assistant > /dev/null 2>&1 & Some helpful links for reading up on QML if you're just getting started. +* [Rust Crates - cxx-qt](https://docs.rs/releases/search?query=cxx_qt) * [QML Reference](https://doc.qt.io/qt-6/qmlreference.html) * [QML Coding Conventions](https://doc.qt.io/qt-6/qml-codingconventions.html) * [All QML Controls Types](https://doc.qt.io/qt-6/qtquick-controls-qmlmodule.html) +* [KDAB CXX-Qt Book](https://kdab.github.io/cxx-qt/book/) +* [github.com/KDAB/cxx-qt](https://github.com/KDAB/cxx-qt) -- 2.47.2 From 413500dad360b483c85802683e07db0edd26bc8f Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Sun, 30 Mar 2025 12:52:44 -0400 Subject: [PATCH 12/73] Add an about window. --- build.rs | 1 + icons/kilroy-256.png | Bin 0 -> 21561 bytes qml/ClideAboutWindow.qml | 73 +++++++++++++++++++++++++++++++++++++++ qml/ClideMenuBar.qml | 6 ++++ 4 files changed, 80 insertions(+) create mode 100644 icons/kilroy-256.png create mode 100644 qml/ClideAboutWindow.qml diff --git a/build.rs b/build.rs index e5aea9e..992b8c1 100644 --- a/build.rs +++ b/build.rs @@ -12,6 +12,7 @@ fn main() { uri: "clide.module", rust_files: &["src/colors.rs"], qml_files: &["qml/main.qml", + "qml/ClideAboutWindow.qml", "qml/ClideProjectView.qml", "qml/ClideEditor.qml", "qml/ClideMenuBar.qml"], diff --git a/icons/kilroy-256.png b/icons/kilroy-256.png new file mode 100644 index 0000000000000000000000000000000000000000..ef5590a4a9028a67e507e5ee405371ec73ee4fda GIT binary patch literal 21561 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 literal 0 HcmV?d00001 diff --git a/qml/ClideAboutWindow.qml b/qml/ClideAboutWindow.qml new file mode 100644 index 0000000..2922764 --- /dev/null +++ b/qml/ClideAboutWindow.qml @@ -0,0 +1,73 @@ +// TODO: Header + +import QtQuick +import QtQuick.Controls.Basic + +import clide.module 1.0 + +ApplicationWindow { + id: root + width: 450 + height: 350 + // Create the window with no frame and keep it on top. + flags: Qt.Window | Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint + color: RustColors.gutter + + // Hide the window when it loses focus. + onActiveChanged: { + if (!active) { + root.visible = false; + } + } + + // Kilroy logo. + Image { + id: logo + + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + anchors.margins: 20 + + source: "../icons/kilroy-256.png" + sourceSize.width: 80 + sourceSize.height: 80 + fillMode: Image.PreserveAspectFit + + smooth: true + antialiasing: true + asynchronous: true + } + + ScrollView { + anchors.top: logo.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + anchors.margins: 20 + + TextArea { + selectedTextColor: RustColors.editor_highlighted_text + selectionColor: RustColors.editor_highlight + horizontalAlignment: Text.AlignHCenter + textFormat: Text.RichText + + text: qsTr("

About CLIDE

" + + "

A simple text editor written in Rust and QML using CXX-Qt.

" + + "

Personal website shaunreed.com

" + + "

Project notes knoats.com

" + + "

This project is developed at git.shaunreed.com

" + + "

KDAB CXX-Qt repository

" + + "

Copyright (C) 2025 Shaun Reed, all rights reserved.

") + color: RustColors.editor_text + wrapMode: Text.WordWrap + readOnly: true + antialiasing: true + background: null + + onLinkActivated: function (link) { + Qt.openUrlExternally(link) + } + } + } +} diff --git a/qml/ClideMenuBar.qml b/qml/ClideMenuBar.qml index 6c175cb..9a9661c 100644 --- a/qml/ClideMenuBar.qml +++ b/qml/ClideMenuBar.qml @@ -156,6 +156,10 @@ MenuBar { // // Help Menu + ClideAboutWindow { + id: clideAbout + } + Action { id: actionDocumentation @@ -163,6 +167,8 @@ MenuBar { } Action { id: actionAbout + // Toggle the about window with the menu item is clicked. + onTriggered: clideAbout.visible = !clideAbout.visible text: qsTr("&About") } -- 2.47.2 From 4f3aebe64f19e63c1c24a042e5875621a6788839 Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Sun, 30 Mar 2025 13:14:58 -0400 Subject: [PATCH 13/73] Add basic stub for filesystem. --- build.rs | 38 ++++++++++++++++++++------------------ src/main.rs | 1 + 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/build.rs b/build.rs index 992b8c1..07367f3 100644 --- a/build.rs +++ b/build.rs @@ -1,22 +1,24 @@ use cxx_qt_build::{CxxQtBuilder, QmlModule}; fn main() { - CxxQtBuilder::new() - // Link Qt's Network library - // - Qt Core is always linked - // - Qt Gui is linked by enabling the qt_gui Cargo feature of cxx-qt-lib. - // - Qt Qml is linked by enabling the qt_qml Cargo feature of cxx-qt-lib. - // - Qt Qml requires linking Qt Network on macOS - .qt_module("Network") - .qml_module(QmlModule { - uri: "clide.module", - rust_files: &["src/colors.rs"], - qml_files: &["qml/main.qml", - "qml/ClideAboutWindow.qml", - "qml/ClideProjectView.qml", - "qml/ClideEditor.qml", - "qml/ClideMenuBar.qml"], - ..Default::default() - }) - .build(); + CxxQtBuilder::new() + // Link Qt's Network library + // - Qt Core is always linked + // - Qt Gui is linked by enabling the qt_gui Cargo feature of cxx-qt-lib. + // - Qt Qml is linked by enabling the qt_qml Cargo feature of cxx-qt-lib. + // - Qt Qml requires linking Qt Network on macOS + .qt_module("Network") + .qml_module(QmlModule { + uri: "clide.module", + rust_files: &["src/colors.rs", "src/filesystem.rs"], + qml_files: &[ + "qml/main.qml", + "qml/ClideAboutWindow.qml", + "qml/ClideProjectView.qml", + "qml/ClideEditor.qml", + "qml/ClideMenuBar.qml", + ], + ..Default::default() + }) + .build(); } diff --git a/src/main.rs b/src/main.rs index 6e5eac4..cad2fb6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,7 @@ use cxx_qt_lib::QString; pub mod colors; +pub mod filesystem; fn main() { use cxx_qt_lib::{QGuiApplication, QQmlApplicationEngine, QUrl}; -- 2.47.2 From 1546eb1028de0c9b167e93838aa3ea945bdb54bd Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Sun, 30 Mar 2025 13:50:23 -0400 Subject: [PATCH 14/73] Add FileSystem Rust module. --- qml/ClideEditor.qml | 7 ++++++ qml/ClideProjectView.qml | 2 ++ src/filesystem.rs | 53 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 62 insertions(+) create mode 100644 src/filesystem.rs diff --git a/qml/ClideEditor.qml b/qml/ClideEditor.qml index 26eea2e..bde3318 100644 --- a/qml/ClideEditor.qml +++ b/qml/ClideEditor.qml @@ -5,10 +5,16 @@ import QtQuick.Layouts import clide.module 1.0 SplitView { + id: root Layout.fillHeight: true Layout.fillWidth: true orientation: Qt.Vertical + // The path to the file to show in the text editor. + // This is updated by a signal caught within ClideProjectView. + // Initialized by the Default trait for the Rust QML singleton FileSystem. + required property string filePath; + // Customized handle to drag between the Editor and the Console. handle: Rectangle { border.color: SplitHandle.pressed ? RustColors.pressed : SplitHandle.hovered ? RustColors.hovered : RustColors.gutter @@ -102,6 +108,7 @@ SplitView { // selectionColor: RustColors.editor_highlight textFormat: Qt.AutoText wrapMode: TextArea.Wrap + text: FileSystem.readFile(root.filePath) background: Rectangle { color: RustColors.editor_background diff --git a/qml/ClideProjectView.qml b/qml/ClideProjectView.qml index 5ceab61..eead520 100644 --- a/qml/ClideProjectView.qml +++ b/qml/ClideProjectView.qml @@ -51,5 +51,7 @@ SplitView { } } ClideEditor { + // Initialize using the Default trait in Rust QML singleton FileSystem. + filePath: FileSystem.filePath } } diff --git a/src/filesystem.rs b/src/filesystem.rs new file mode 100644 index 0000000..ecc9fcd --- /dev/null +++ b/src/filesystem.rs @@ -0,0 +1,53 @@ +#[cxx_qt::bridge] +pub mod qobject { + unsafe extern "C++" { + // Import Qt Types from 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; + } + + unsafe extern "RustQt" { + // Export QML Types from Rust + #[qobject] + #[qml_element] + #[qml_singleton] + #[qproperty(QString, file_path, cxx_name = "filePath")] + type FileSystem = super::FileSystemImpl; + + #[qinvokable] + #[cxx_name = "readFile"] + fn read_file(self: &FileSystem, path: &QString) -> QString; + } +} + +use cxx_qt_lib::{QModelIndex, QString}; +use std::fs; +pub struct FileSystemImpl { + file_path: QString, + model_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!()), + model_index: Default::default(), + } + } +} + +impl qobject::FileSystem { + fn read_file(&self, path: &QString) -> QString { + if path.is_empty() { + return QString::default(); + } + // TODO: What if the file is binary or something horrible? + QString::from( + fs::read_to_string(path.to_string()) + .expect(format!("Failed to read file {}", path).as_str()), + ) + } +} -- 2.47.2 From b62dce631fe2ef84596bb3683dc765e53e2e42c5 Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Sun, 30 Mar 2025 16:14:58 -0400 Subject: [PATCH 15/73] Add ClideTreeView. --- Cargo.lock | 106 ++++++++++++++++++++++++++-- Cargo.toml | 1 + README.md | 19 +++++ build.rs | 1 + icons/folder_closed.svg | 38 ++++++++++ icons/folder_open.svg | 38 ++++++++++ icons/generic_file.svg | 38 ++++++++++ qml/ClideProjectView.qml | 15 ++-- qml/ClideTreeView.qml | 148 +++++++++++++++++++++++++++++++++++++++ src/colors.rs | 6 ++ src/filesystem.rs | 35 ++++++++- 11 files changed, 433 insertions(+), 12 deletions(-) create mode 100644 icons/folder_closed.svg create mode 100644 icons/folder_open.svg create mode 100644 icons/generic_file.svg create mode 100644 qml/ClideTreeView.qml diff --git a/Cargo.lock b/Cargo.lock index 3574567..55532cf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,6 +8,12 @@ version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" +[[package]] +name = "bitflags" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" + [[package]] name = "cc" version = "1.2.16" @@ -19,13 +25,19 @@ dependencies = [ "shlex", ] +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + [[package]] name = "clang-format" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "696283b40e1a39d208ee614b92e5f6521d16962edeb47c48372585ec92419943" dependencies = [ - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -62,6 +74,7 @@ dependencies = [ "cxx-qt", "cxx-qt-build", "cxx-qt-lib", + "dirs", "log", ] @@ -121,7 +134,7 @@ dependencies = [ "cxx-qt-macro", "qt-build-utils", "static_assertions", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -210,6 +223,27 @@ dependencies = [ "syn", ] +[[package]] +name = "dirs" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys", +] + [[package]] name = "either" version = "1.15.0" @@ -222,6 +256,17 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + [[package]] name = "indoc" version = "2.0.6" @@ -258,6 +303,16 @@ version = "0.2.171" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags", + "libc", +] + [[package]] name = "link-cplusplus" version = "1.0.10" @@ -295,6 +350,12 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + [[package]] name = "proc-macro2" version = "1.0.94" @@ -311,7 +372,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "efb239fdd8c036fabb95364320041ef68197cd4ab971bb3b4ca3ea0b7b93d12c" dependencies = [ "cc", - "thiserror", + "thiserror 1.0.69", "versions", ] @@ -324,6 +385,17 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "redox_users" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" +dependencies = [ + "getrandom", + "libredox", + "thiserror 2.0.12", +] + [[package]] name = "rustversion" version = "1.0.20" @@ -412,7 +484,16 @@ version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl 2.0.12", ] [[package]] @@ -426,6 +507,17 @@ dependencies = [ "syn", ] +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "unicode-ident" version = "1.0.18" @@ -460,6 +552,12 @@ dependencies = [ "nom", ] +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + [[package]] name = "winapi-util" version = "0.1.9" diff --git a/Cargo.toml b/Cargo.toml index 8b7c81c..c039058 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ cxx = "1.0.95" cxx-qt = "0.7" cxx-qt-lib = { version="0.7", features = ["qt_full"] } log = { version = "0.4.27", features = [] } +dirs = "6.0.0" [build-dependencies] # The link_qt_object_files feature is required for statically linking Qt 6. diff --git a/README.md b/README.md index 6172640..0caa5c2 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,25 @@ Using Qt Assistant is recommended. It comes with Qt6 when installed. Run the fol nohup $HOME/Qt/6.8.3/gcc_64/bin/assistant > /dev/null 2>&1 & ``` +If you are looking for an include path from Qt + +```bash +find /usr/include/x86_64-linux-gnu/qt6/ -name QFile* + +/usr/include/x86_64-linux-gnu/qt6/QtWidgets/QFileIconProvider +/usr/include/x86_64-linux-gnu/qt6/QtWidgets/QFileDialog +/usr/include/x86_64-linux-gnu/qt6/QtGui/QFileSystemModel +/usr/include/x86_64-linux-gnu/qt6/QtGui/QFileOpenEvent +/usr/include/x86_64-linux-gnu/qt6/QtCore/QFile +/usr/include/x86_64-linux-gnu/qt6/QtCore/QFileDevice +/usr/include/x86_64-linux-gnu/qt6/QtCore/QFileSystemWatcher +/usr/include/x86_64-linux-gnu/qt6/QtCore/QFileInfoList +/usr/include/x86_64-linux-gnu/qt6/QtCore/QFileInfo +/usr/include/x86_64-linux-gnu/qt6/QtCore/QFileSelector +``` + +This helped find that QFileSystemModel is in QtGui and not QtCore. + ### Resources Some helpful links for reading up on QML if you're just getting started. diff --git a/build.rs b/build.rs index 07367f3..69a103e 100644 --- a/build.rs +++ b/build.rs @@ -14,6 +14,7 @@ fn main() { qml_files: &[ "qml/main.qml", "qml/ClideAboutWindow.qml", + "qml/ClideTreeView.qml", "qml/ClideProjectView.qml", "qml/ClideEditor.qml", "qml/ClideMenuBar.qml", diff --git a/icons/folder_closed.svg b/icons/folder_closed.svg new file mode 100644 index 0000000..281be32 --- /dev/null +++ b/icons/folder_closed.svg @@ -0,0 +1,38 @@ + + + + + + diff --git a/icons/folder_open.svg b/icons/folder_open.svg new file mode 100644 index 0000000..09f7615 --- /dev/null +++ b/icons/folder_open.svg @@ -0,0 +1,38 @@ + + + + + + diff --git a/icons/generic_file.svg b/icons/generic_file.svg new file mode 100644 index 0000000..e0423f2 --- /dev/null +++ b/icons/generic_file.svg @@ -0,0 +1,38 @@ + + + + + + diff --git a/qml/ClideProjectView.qml b/qml/ClideProjectView.qml index eead520..569a038 100644 --- a/qml/ClideProjectView.qml +++ b/qml/ClideProjectView.qml @@ -5,6 +5,11 @@ import QtQuick.Layouts import clide.module 1.0 SplitView { + id: root + + // Path to the file selected in the tree view. + property string selectedFilePath; + Layout.fillHeight: true Layout.fillWidth: true anchors.fill: parent @@ -42,12 +47,10 @@ SplitView { wrapMode: TextArea.Wrap } - // TODO: Shows the files on the file system. - // ClideTreeView { - // id: fileSystemView - // color: Colors.surface1 - // onFileClicked: path => root.currentFilePath = path - // } + ClideTreeView { + id: clideTreeView + onFileClicked: path => root.currentFilePath = path + } } } ClideEditor { diff --git a/qml/ClideTreeView.qml b/qml/ClideTreeView.qml new file mode 100644 index 0000000..a2cf773 --- /dev/null +++ b/qml/ClideTreeView.qml @@ -0,0 +1,148 @@ +import QtQuick +import QtQuick.Controls + +import clide.module 1.0 + +Rectangle { + id: root + signal fileClicked(string filePath) + + TreeView { + id: fileSystemTreeView + + // rootIndex: FileSystem.rootIndex + property int lastIndex: -1 + + // model: FileSystem + anchors.fill: parent + boundsBehavior: Flickable.StopAtBounds + boundsMovement: Flickable.StopAtBounds + clip: true + + Component.onCompleted: fileSystemTreeView.toggleExpanded(0) + + // The delegate represents a single entry in the filesystem. + delegate: TreeViewDelegate { + id: treeDelegate + indentation: 8 + implicitWidth: fileSystemTreeView.width > 0 ? fileSystemTreeView.width : 250 + implicitHeight: 25 + + // Since we have the 'ComponentBehavior Bound' pragma, we need to + // require these properties from our model. This is a convenient way + // to bind the properties provided by the model's role names. + required property int index + required property url filePath + required property string fileName + + indicator: Image { + id: directoryIcon + + x: treeDelegate.leftMargin + (treeDelegate.depth * treeDelegate.indentation) + anchors.verticalCenter: parent.verticalCenter + source: treeDelegate.hasChildren ? (treeDelegate.expanded + ? "../icons/folder_open.svg" : "../icons/folder_closed.svg") + : "../icons/generic_file.svg" + sourceSize.width: 20 + sourceSize.height: 20 + fillMode: Image.PreserveAspectFit + + smooth: true + antialiasing: true + asynchronous: true + } + + contentItem: Text { + text: treeDelegate.fileName + color: RustColors.editor_text + } + + background: Rectangle { + color: (treeDelegate.index === fileSystemTreeView.lastIndex) + ? RustColors.editor_highlight + : (hoverHandler.hovered ? RustColors.active : "transparent") + } + + // We color the directory icons with this MultiEffect, where we overlay + // the colorization color ontop of the SVG icons. + // MultiEffect { + // id: iconOverlay + // + // anchors.fill: directoryIcon + // source: directoryIcon + // colorization: 1.0 + // brightness: 1.0 + // colorizationColor: { + // const isFile = treeDelegate.index === fileSystemTreeView.lastIndex + // && !treeDelegate.hasChildren; + // if (isFile) + // return Qt.lighter(RustColors.explorer_folder, 3) + // + // const isExpandedFolder = treeDelegate.expanded && treeDelegate.hasChildren; + // if (isExpandedFolder) + // return RustColors.explorer_forder_open + // else + // return RustColors.explorer_folder + // } + // } + + HoverHandler { + id: hoverHandler + } + + TapHandler { + acceptedButtons: Qt.LeftButton | Qt.RightButton + onSingleTapped: (eventPoint, button) => { + switch (button) { + case Qt.LeftButton: + fileSystemTreeView.toggleExpanded(treeDelegate.row) + fileSystemTreeView.lastIndex = treeDelegate.index + // If this model item doesn't have children, it means it's + // representing a file. + if (!treeDelegate.hasChildren) + root.fileClicked(treeDelegate.filePath) + break; + case Qt.RightButton: + if (treeDelegate.hasChildren) + contextMenu.popup(); + break; + } + } + } + + Menu { + id: contextMenu + Action { + text: qsTr("Set as root index") + onTriggered: { + // fileSystemTreeView.rootIndex = fileSystemTreeView.index(treeDelegate.row, 0) + } + } + Action { + text: qsTr("Reset root index") + // onTriggered: fileSystemTreeView.rootIndex = undefined + } + } + } + + // Provide our own custom ScrollIndicator for the TreeView. + ScrollIndicator.vertical: ScrollIndicator { + active: true + implicitWidth: 15 + + contentItem: Rectangle { + implicitWidth: 6 + implicitHeight: 6 + + color: RustColors.scrollbar + opacity: fileSystemTreeView.movingVertically ? 0.5 : 0.0 + + Behavior on opacity { + OpacityAnimator { + duration: 500 + } + } + } + } + } +} diff --git a/src/colors.rs b/src/colors.rs index 1b699b5..7d1f08f 100644 --- a/src/colors.rs +++ b/src/colors.rs @@ -26,6 +26,8 @@ pub mod qobject { #[qproperty(QColor, editor_highlight)] #[qproperty(QColor, gutter)] #[qproperty(QColor, explorer_background)] + #[qproperty(QColor, explorer_folder)] + #[qproperty(QColor, explorer_folder_open)] type RustColors = super::RustColorsImpl; } } @@ -50,6 +52,8 @@ pub struct RustColorsImpl { editor_highlight: QColor, gutter: QColor, explorer_background: QColor, + explorer_folder: QColor, + explorer_folder_open: QColor, } impl Default for RustColorsImpl { @@ -72,6 +76,8 @@ impl Default for RustColorsImpl { editor_highlight: QColor::try_from("#ccced3").unwrap(), gutter: QColor::try_from("#1e1f22").unwrap(), explorer_background: QColor::try_from("#3c3f41").unwrap(), + explorer_folder: QColor::try_from("#FFF").unwrap(), + explorer_folder_open: QColor::try_from("#FFF").unwrap(), } } } diff --git a/src/filesystem.rs b/src/filesystem.rs index ecc9fcd..0f97344 100644 --- a/src/filesystem.rs +++ b/src/filesystem.rs @@ -14,19 +14,31 @@ pub mod qobject { #[qml_element] #[qml_singleton] #[qproperty(QString, file_path, cxx_name = "filePath")] + #[qproperty(QModelIndex, root_index, cxx_name = "rootIndex")] type FileSystem = super::FileSystemImpl; + #[qinvokable] + #[cxx_name = "columnCount"] + pub fn column_count(self: &FileSystem, index: &QModelIndex) -> i32; + #[qinvokable] #[cxx_name = "readFile"] fn read_file(self: &FileSystem, path: &QString) -> QString; + + // TODO: Remove if unused in QML. + #[qinvokable] + #[cxx_name = "setInitialDirectory"] + fn set_initial_directory(self: &FileSystem, path: &QString); } } use cxx_qt_lib::{QModelIndex, QString}; +use dirs; use std::fs; + pub struct FileSystemImpl { file_path: QString, - model_index: QModelIndex, + root_index: QModelIndex, } // Default is explicit to make the editor open this source file initially. @@ -34,7 +46,7 @@ impl Default for FileSystemImpl { fn default() -> Self { Self { file_path: QString::from(file!()), - model_index: Default::default(), + root_index: Default::default(), } } } @@ -50,4 +62,23 @@ impl qobject::FileSystem { .expect(format!("Failed to read file {}", path).as_str()), ) } + + // There will never be more than one column. + pub fn column_count(&self, _index: &QModelIndex) -> i32 { + 1 + } + + fn set_initial_directory(&self, path: &QString) { + if !path.is_empty() + && fs::metadata(path.to_string()) + .expect(format!("Failed to get metadata for file {}", path).as_str()) + .is_file() + { + // Open the file + // setRootPa + } else { + // If the initial directory can't be opened, attempt to find the home directory. + // dirs::home_dir() + } + } } -- 2.47.2 From bdf942371c9b5e044975922baa1691226b7872ae Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Sun, 30 Mar 2025 21:38:57 -0400 Subject: [PATCH 16/73] Add basic FileSystem view. --- Cargo.toml | 2 +- README.md | 2 +- build.rs | 3 ++ qml/ClideMenuBar.qml | 1 + qml/ClideProjectView.qml | 15 ++------ qml/ClideTreeView.qml | 74 +++++++++++++++++++--------------------- src/colors.rs | 11 +++++- src/filesystem.rs | 36 +++++++++++++------ 8 files changed, 80 insertions(+), 64 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c039058..91334c3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ edition = "2024" [dependencies] cxx = "1.0.95" cxx-qt = "0.7" -cxx-qt-lib = { version="0.7", features = ["qt_full"] } +cxx-qt-lib = { version="0.7", features = ["qt_full", "qt_gui"] } log = { version = "0.4.27", features = [] } dirs = "6.0.0" diff --git a/README.md b/README.md index 0caa5c2..ad8d381 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ CLIDE is an IDE written in Rust that supports both full and headless Linux envir The following packages must be installed before the application will build. ```bash -sudo apt install qt6-base-dev qt6-declarative-dev qt6-tools-dev qml6-module-qtquick-controls qml6-module-qtquick-layouts qml6-module-qtquick-window qml6-module-qtqml-workerscript qml6-module-qtquick-templates qml6-module-qtquick qml6-module-qtquick-dialogs +sudo apt install qt6-base-dev qt6-declarative-dev qt6-tools-dev qml6-module-qtquick-controls qml6-module-qtquick-layouts qml6-module-qtquick-window qml6-module-qtqml-workerscript qml6-module-qtquick-templates qml6-module-qtquick qml6-module-qtquick-dialogs qt6-svg-dev ``` And of course, [Rust](https://www.rust-lang.org/tools/install). diff --git a/build.rs b/build.rs index 69a103e..1e369a2 100644 --- a/build.rs +++ b/build.rs @@ -8,6 +8,9 @@ fn main() { // - Qt Qml is linked by enabling the qt_qml Cargo feature of cxx-qt-lib. // - Qt Qml requires linking Qt Network on macOS .qt_module("Network") + .qt_module("Gui") + .qt_module("Svg") + .qt_module("Xml") .qml_module(QmlModule { uri: "clide.module", rust_files: &["src/colors.rs", "src/filesystem.rs"], diff --git a/qml/ClideMenuBar.qml b/qml/ClideMenuBar.qml index 9a9661c..73a6655 100644 --- a/qml/ClideMenuBar.qml +++ b/qml/ClideMenuBar.qml @@ -67,6 +67,7 @@ MenuBar { } ClideMenuItem { action: actionOpen + onTriggered: FileSystem.setDirectory(FileSystem.filePath) } ClideMenuItem { action: actionSave diff --git a/qml/ClideProjectView.qml b/qml/ClideProjectView.qml index 569a038..fa1cad5 100644 --- a/qml/ClideProjectView.qml +++ b/qml/ClideProjectView.qml @@ -8,7 +8,7 @@ SplitView { id: root // Path to the file selected in the tree view. - property string selectedFilePath; + default property string selectedFilePath: FileSystem.filePath; Layout.fillHeight: true Layout.fillWidth: true @@ -38,23 +38,14 @@ SplitView { StackLayout { anchors.fill: parent - - // Shows the help text. - TextArea { - placeholderText: qsTr("File system view placeholder") - placeholderTextColor: "white" - readOnly: true - wrapMode: TextArea.Wrap - } - ClideTreeView { id: clideTreeView - onFileClicked: path => root.currentFilePath = path + onFileClicked: path => root.selectedFilePath = path } } } ClideEditor { // Initialize using the Default trait in Rust QML singleton FileSystem. - filePath: FileSystem.filePath + filePath: root.selectedFilePath } } diff --git a/qml/ClideTreeView.qml b/qml/ClideTreeView.qml index a2cf773..eeb05aa 100644 --- a/qml/ClideTreeView.qml +++ b/qml/ClideTreeView.qml @@ -1,25 +1,32 @@ import QtQuick import QtQuick.Controls +import QtQuick.Layouts import clide.module 1.0 Rectangle { id: root + signal fileClicked(string filePath) + color: RustColors.explorer_background + TreeView { id: fileSystemTreeView // rootIndex: FileSystem.rootIndex property int lastIndex: -1 - // model: FileSystem + model: FileSystem anchors.fill: parent boundsBehavior: Flickable.StopAtBounds boundsMovement: Flickable.StopAtBounds clip: true - Component.onCompleted: fileSystemTreeView.toggleExpanded(0) + Component.onCompleted: { + FileSystem.setDirectory(FileSystem.filePath) + fileSystemTreeView.expandRecursively(0, 4) + } // The delegate represents a single entry in the filesystem. delegate: TreeViewDelegate { @@ -28,9 +35,6 @@ Rectangle { implicitWidth: fileSystemTreeView.width > 0 ? fileSystemTreeView.width : 250 implicitHeight: 25 - // Since we have the 'ComponentBehavior Bound' pragma, we need to - // require these properties from our model. This is a convenient way - // to bind the properties provided by the model's role names. required property int index required property url filePath required property string fileName @@ -40,11 +44,16 @@ Rectangle { x: treeDelegate.leftMargin + (treeDelegate.depth * treeDelegate.indentation) anchors.verticalCenter: parent.verticalCenter - source: treeDelegate.hasChildren ? (treeDelegate.expanded - ? "../icons/folder_open.svg" : "../icons/folder_closed.svg") - : "../icons/generic_file.svg" - sourceSize.width: 20 - sourceSize.height: 20 + source: { + // If the item has children, it's a directory. + if (treeDelegate.hasChildren) { + return treeDelegate.expanded ? + "../icons/folder-open-solid.svg" : "../icons/folder-solid.svg"; + } + return "../icons/file-solid.svg" + } + sourceSize.width: 15 + sourceSize.height: 15 fillMode: Image.PreserveAspectFit smooth: true @@ -54,37 +63,23 @@ Rectangle { contentItem: Text { text: treeDelegate.fileName - color: RustColors.editor_text + color: RustColors.explorer_text } background: Rectangle { + // TODO: Fix flickering from color transition on states here. color: (treeDelegate.index === fileSystemTreeView.lastIndex) - ? RustColors.editor_highlight - : (hoverHandler.hovered ? RustColors.active : "transparent") - } + ? RustColors.explorer_text_selected + : (hoverHandler.hovered ? RustColors.explorer_hovered : "transparent") + radius: 2.5 + opacity: hoverHandler.hovered ? 0.75 : 1.0 - // We color the directory icons with this MultiEffect, where we overlay - // the colorization color ontop of the SVG icons. - // MultiEffect { - // id: iconOverlay - // - // anchors.fill: directoryIcon - // source: directoryIcon - // colorization: 1.0 - // brightness: 1.0 - // colorizationColor: { - // const isFile = treeDelegate.index === fileSystemTreeView.lastIndex - // && !treeDelegate.hasChildren; - // if (isFile) - // return Qt.lighter(RustColors.explorer_folder, 3) - // - // const isExpandedFolder = treeDelegate.expanded && treeDelegate.hasChildren; - // if (isExpandedFolder) - // return RustColors.explorer_forder_open - // else - // return RustColors.explorer_folder - // } - // } + Behavior on color { + ColorAnimation { + duration: 300 + } + } + } HoverHandler { id: hoverHandler @@ -115,12 +110,15 @@ Rectangle { Action { text: qsTr("Set as root index") onTriggered: { - // fileSystemTreeView.rootIndex = fileSystemTreeView.index(treeDelegate.row, 0) + console.log("Setting directory: " + treeDelegate.filePath) + FileSystem.setDirectory(treeDelegate.filePath) } } Action { text: qsTr("Reset root index") - // onTriggered: fileSystemTreeView.rootIndex = undefined + onTriggered: { + FileSystem.setDirectory("") + } } } } diff --git a/src/colors.rs b/src/colors.rs index 7d1f08f..cc10722 100644 --- a/src/colors.rs +++ b/src/colors.rs @@ -25,6 +25,9 @@ pub mod qobject { #[qproperty(QColor, editor_highlighted_text)] #[qproperty(QColor, editor_highlight)] #[qproperty(QColor, gutter)] + #[qproperty(QColor, explorer_hovered)] + #[qproperty(QColor, explorer_text)] + #[qproperty(QColor, explorer_text_selected)] #[qproperty(QColor, explorer_background)] #[qproperty(QColor, explorer_folder)] #[qproperty(QColor, explorer_folder_open)] @@ -51,6 +54,9 @@ pub struct RustColorsImpl { editor_highlighted_text: QColor, editor_highlight: QColor, gutter: QColor, + explorer_hovered: QColor, + explorer_text: QColor, + explorer_text_selected: QColor, explorer_background: QColor, explorer_folder: QColor, explorer_folder_open: QColor, @@ -75,7 +81,10 @@ impl Default for RustColorsImpl { editor_highlighted_text: QColor::try_from("#ccced3").unwrap(), editor_highlight: QColor::try_from("#ccced3").unwrap(), gutter: QColor::try_from("#1e1f22").unwrap(), - explorer_background: QColor::try_from("#3c3f41").unwrap(), + explorer_hovered: QColor::try_from("#4c5053").unwrap(), + explorer_text: QColor::try_from("#3b3b3b").unwrap(), + explorer_text_selected: QColor::try_from("#8b8b8b").unwrap(), + explorer_background: QColor::try_from("#676c70").unwrap(), explorer_folder: QColor::try_from("#FFF").unwrap(), explorer_folder_open: QColor::try_from("#FFF").unwrap(), } diff --git a/src/filesystem.rs b/src/filesystem.rs index 0f97344..540bcbe 100644 --- a/src/filesystem.rs +++ b/src/filesystem.rs @@ -6,29 +6,36 @@ pub mod qobject { type QString = cxx_qt_lib::QString; include!("cxx-qt-lib/qmodelindex.h"); type QModelIndex = cxx_qt_lib::QModelIndex; + include!(); + type QFileSystemModel; } unsafe extern "RustQt" { // Export QML Types from Rust #[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; + #[inherit] + #[cxx_name = "setRootPath"] + fn set_root_path(self: Pin<&mut FileSystem>, path: &QString) -> QModelIndex; + #[qinvokable] + #[cxx_override] #[cxx_name = "columnCount"] - pub fn column_count(self: &FileSystem, index: &QModelIndex) -> i32; + fn column_count(self: &FileSystem, _index: &QModelIndex) -> i32; #[qinvokable] #[cxx_name = "readFile"] fn read_file(self: &FileSystem, path: &QString) -> QString; - // TODO: Remove if unused in QML. #[qinvokable] - #[cxx_name = "setInitialDirectory"] - fn set_initial_directory(self: &FileSystem, path: &QString); + #[cxx_name = "setDirectory"] + fn set_directory(self: Pin<&mut FileSystem>, path: &QString) -> QModelIndex; } } @@ -36,6 +43,7 @@ use cxx_qt_lib::{QModelIndex, QString}; use dirs; use std::fs; +// TODO: Impleent a provider for QFileSystemModel::setIconProvider for icons. pub struct FileSystemImpl { file_path: QString, root_index: QModelIndex, @@ -64,21 +72,27 @@ impl qobject::FileSystem { } // There will never be more than one column. - pub fn column_count(&self, _index: &QModelIndex) -> i32 { + fn column_count(&self, _index: &QModelIndex) -> i32 { 1 } - fn set_initial_directory(&self, path: &QString) { + 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 file {}", path).as_str()) - .is_file() + .expect(format!("Failed to get metadata for path {}", path).as_str()) + .is_dir() { - // Open the file - // setRootPa + self.set_root_path(path) } else { // If the initial directory can't be opened, attempt to find the home directory. - // dirs::home_dir() + self.set_root_path(&QString::from( + dirs::home_dir() + .expect("Failed to get home directory") + .as_path() + .to_str() + .unwrap() + .to_string(), + )) } } } -- 2.47.2 From b426b88b7965b7dc42e48cb7fff74c645f42a734 Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Sun, 30 Mar 2025 21:58:38 -0400 Subject: [PATCH 17/73] Fix colors. --- qml/ClideEditor.qml | 2 +- qml/ClideTreeView.qml | 4 ++-- src/colors.rs | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/qml/ClideEditor.qml b/qml/ClideEditor.qml index bde3318..fc89e6e 100644 --- a/qml/ClideEditor.qml +++ b/qml/ClideEditor.qml @@ -63,7 +63,7 @@ SplitView { Label { id: numbers - color: "white" + color: RustColors.linenumber font: textArea.font height: parent.height horizontalAlignment: Text.AlignLeft diff --git a/qml/ClideTreeView.qml b/qml/ClideTreeView.qml index eeb05aa..4f62089 100644 --- a/qml/ClideTreeView.qml +++ b/qml/ClideTreeView.qml @@ -6,13 +6,13 @@ import clide.module 1.0 Rectangle { id: root + color: RustColors.explorer_background signal fileClicked(string filePath) - color: RustColors.explorer_background - TreeView { id: fileSystemTreeView + anchors.margins: 15 // rootIndex: FileSystem.rootIndex property int lastIndex: -1 diff --git a/src/colors.rs b/src/colors.rs index cc10722..815630e 100644 --- a/src/colors.rs +++ b/src/colors.rs @@ -73,7 +73,7 @@ impl Default for RustColorsImpl { scrollbar: QColor::try_from("#4b4f51").unwrap(), scrollbar_active: QColor::try_from("#4b4f51").unwrap(), scrollbar_gutter: QColor::try_from("#3b3b3b").unwrap(), - linenumber: QColor::try_from("#FFF").unwrap(), + linenumber: QColor::try_from("#94989b").unwrap(), active: QColor::try_from("#a9acb0").unwrap(), inactive: QColor::try_from("#FFF").unwrap(), editor_background: QColor::try_from("#2b2b2b").unwrap(), @@ -85,7 +85,7 @@ impl Default for RustColorsImpl { explorer_text: QColor::try_from("#3b3b3b").unwrap(), explorer_text_selected: QColor::try_from("#8b8b8b").unwrap(), explorer_background: QColor::try_from("#676c70").unwrap(), - explorer_folder: QColor::try_from("#FFF").unwrap(), + explorer_folder: QColor::try_from("#54585b").unwrap(), explorer_folder_open: QColor::try_from("#FFF").unwrap(), } } -- 2.47.2 From 365940267f8100a62f58fb8de814c067455ad05a Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Mon, 31 Mar 2025 19:26:04 -0400 Subject: [PATCH 18/73] Add some checks before reading file. --- qml/ClideEditor.qml | 34 +++++++++++++++++++--------------- src/colors.rs | 2 +- src/filesystem.rs | 21 ++++++++++++++++----- 3 files changed, 36 insertions(+), 21 deletions(-) diff --git a/qml/ClideEditor.qml b/qml/ClideEditor.qml index fc89e6e..8a14eb3 100644 --- a/qml/ClideEditor.qml +++ b/qml/ClideEditor.qml @@ -35,31 +35,36 @@ SplitView { // extend the available height. Flickable { id: lineNumbers - Layout.fillHeight: true Layout.fillWidth: false - - // Calculate the width based on the logarithmic scale. + // Calculating the width correctly is important as the number grows. + // We need to ensure space required to show N line number digits. + // We use log10 to find how many digits are needed in a line number. + // log10(9) ~= .95; log10(10) = 1.0; log10(100) = 2.0 ...etc + // We +1 to ensure space for at least 1 digit, as floor(1.95) = 1. + // The +10 is additional spacing and can be adjusted. Layout.preferredWidth: fontMetrics.averageCharacterWidth * (Math.floor(Math.log10(textArea.lineCount)) + 1) + 10 contentY: editorFlickable.contentY interactive: false - visible: true Column { anchors.fill: parent - topPadding: 6 + topPadding: textArea.topPadding Repeater { id: repeatedLineNumbers + // TODO: Bug where text wrapping shows as new line number. + model: textArea.lineCount - // Each line number in the gutter. + // This Item is used for each line number in the gutter. delegate: Item { - required property int index - - // Calculate the height of each line in the text area. + // Calculates the height of each line in the text area. height: textArea.contentHeight / textArea.lineCount width: parent.width + required property int index + + // Show the line number. Label { id: numbers @@ -69,8 +74,9 @@ SplitView { horizontalAlignment: Text.AlignLeft text: parent.index + 1 verticalAlignment: Text.AlignVCenter - width: parent.width + width: parent.width - indicator.width } + // Draw edge along the right side of the line number. Rectangle { id: indicator @@ -80,14 +86,11 @@ SplitView { width: 1 } } - // TODO: Bug where text wrapping shows as new line number. - model: textArea.lineCount } } } Flickable { id: editorFlickable - Layout.fillHeight: true Layout.fillWidth: true boundsBehavior: Flickable.StopAtBounds @@ -97,6 +100,7 @@ SplitView { } ScrollBar.vertical: MyScrollBar { } + TextArea.flickable: TextArea { id: textArea @@ -104,8 +108,8 @@ SplitView { focus: true persistentSelection: true selectByMouse: true - // selectedTextColor: RustColors.editor_highlighted_text - // selectionColor: RustColors.editor_highlight + selectedTextColor: RustColors.editor_highlighted_text + selectionColor: RustColors.editor_highlight textFormat: Qt.AutoText wrapMode: TextArea.Wrap text: FileSystem.readFile(root.filePath) diff --git a/src/colors.rs b/src/colors.rs index 815630e..d113109 100644 --- a/src/colors.rs +++ b/src/colors.rs @@ -77,7 +77,7 @@ impl Default for RustColorsImpl { active: QColor::try_from("#a9acb0").unwrap(), inactive: QColor::try_from("#FFF").unwrap(), editor_background: QColor::try_from("#2b2b2b").unwrap(), - editor_text: QColor::try_from("#ccced3").unwrap(), + editor_text: QColor::try_from("#acaea3").unwrap(), editor_highlighted_text: QColor::try_from("#ccced3").unwrap(), editor_highlight: QColor::try_from("#ccced3").unwrap(), gutter: QColor::try_from("#1e1f22").unwrap(), diff --git a/src/filesystem.rs b/src/filesystem.rs index 540bcbe..fc99f67 100644 --- a/src/filesystem.rs +++ b/src/filesystem.rs @@ -42,6 +42,8 @@ pub mod qobject { use cxx_qt_lib::{QModelIndex, QString}; use dirs; use std::fs; +use std::fs::FileType; +use log::warn; // TODO: Impleent a provider for QFileSystemModel::setIconProvider for icons. pub struct FileSystemImpl { @@ -64,11 +66,20 @@ impl qobject::FileSystem { if path.is_empty() { return QString::default(); } - // TODO: What if the file is binary or something horrible? - QString::from( - fs::read_to_string(path.to_string()) - .expect(format!("Failed to read file {}", path).as_str()), - ) + // TODO: Use syntect for syntax highlighting? + // https://github.com/trishume/syntect/blob/master/examples/syncat.rs + if fs::metadata(path.to_string()) + .expect(format!("Failed to get file metadata {}", path).as_str()) + .is_file() + { + QString::from( + fs::read_to_string(path.to_string()) + .expect(format!("Failed to read file {}", path).as_str()), + ) + } else { + warn!("Attempted to open file {} that is not a valid file", path); + QString::default() + } } // There will never be more than one column. -- 2.47.2 From 9b86553513e1260c9e9683225e231ed88a3f58fb Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Mon, 31 Mar 2025 22:32:17 -0400 Subject: [PATCH 19/73] Add syntax highlighting with syntect. --- Cargo.lock | 256 +++++++++++++++++++++++++++++++++++++++++++- Cargo.toml | 1 + qml/ClideEditor.qml | 12 +-- src/filesystem.rs | 20 ++-- 4 files changed, 273 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 55532cf..5c181d1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,12 +2,39 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + [[package]] name = "anstyle" version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.9.0" @@ -76,6 +103,7 @@ dependencies = [ "cxx-qt-lib", "dirs", "log", + "syntect", ] [[package]] @@ -97,6 +125,15 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + [[package]] name = "cxx" version = "1.0.148" @@ -223,6 +260,15 @@ dependencies = [ "syn", ] +[[package]] +name = "deranged" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28cfac68e08048ae1883171632c2aef3ebc555621ae56fbccce1cbf22dd7f058" +dependencies = [ + "powerfmt", +] + [[package]] name = "dirs" version = "6.0.0" @@ -250,6 +296,28 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "flate2" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11faaf5a5236997af9848be0bef4db95824b1d534ebc64d0f0c6cf3e67bd38dc" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "foldhash" version = "0.1.5" @@ -267,6 +335,22 @@ dependencies = [ "wasi", ] +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" + +[[package]] +name = "indexmap" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3954d50fe15b02142bf25d3b8bdadb634ec3948f103d04ffe3031bc8fe9d7058" +dependencies = [ + "equivalent", + "hashbrown", +] + [[package]] name = "indoc" version = "2.0.6" @@ -309,7 +393,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags", + "bitflags 2.9.0", "libc", ] @@ -322,6 +406,12 @@ dependencies = [ "cc", ] +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + [[package]] name = "log" version = "0.4.27" @@ -340,6 +430,15 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" +[[package]] +name = "miniz_oxide" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5" +dependencies = [ + "adler2", +] + [[package]] name = "nom" version = "7.1.3" @@ -350,12 +449,71 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "onig" +version = "6.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c4b31c8722ad9171c6d77d3557db078cab2bd50afcc9d09c8b315c59df8ca4f" +dependencies = [ + "bitflags 1.3.2", + "libc", + "once_cell", + "onig_sys", +] + +[[package]] +name = "onig_sys" +version = "69.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b829e3d7e9cc74c7e315ee8edb185bf4190da5acde74afd7fc59c35b1f086e7" +dependencies = [ + "cc", + "pkg-config", +] + [[package]] name = "option-ext" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "plist" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac26e981c03a6e53e0aee43c113e3202f5581d5360dae7bd2c70e800dd0451d" +dependencies = [ + "base64", + "indexmap", + "quick-xml", + "serde", + "time", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "proc-macro2" version = "1.0.94" @@ -376,6 +534,15 @@ dependencies = [ "versions", ] +[[package]] +name = "quick-xml" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d3a6e5838b60e0e8fa7a43f22ade549a37d61f8bdbe636d0d7816191de969c2" +dependencies = [ + "memchr", +] + [[package]] name = "quote" version = "1.0.40" @@ -396,6 +563,12 @@ dependencies = [ "thiserror 2.0.12", ] +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + [[package]] name = "rustversion" version = "1.0.20" @@ -408,6 +581,15 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "serde" version = "1.0.219" @@ -469,6 +651,28 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syntect" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "874dcfa363995604333cf947ae9f751ca3af4522c60886774c4963943b4746b1" +dependencies = [ + "bincode", + "bitflags 1.3.2", + "flate2", + "fnv", + "once_cell", + "onig", + "plist", + "regex-syntax", + "serde", + "serde_derive", + "serde_json", + "thiserror 1.0.69", + "walkdir", + "yaml-rust", +] + [[package]] name = "termcolor" version = "1.4.1" @@ -518,6 +722,37 @@ dependencies = [ "syn", ] +[[package]] +name = "time" +version = "0.3.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" + +[[package]] +name = "time-macros" +version = "0.2.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" +dependencies = [ + "num-conv", + "time-core", +] + [[package]] name = "unicode-ident" version = "1.0.18" @@ -552,6 +787,16 @@ dependencies = [ "nom", ] +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -639,3 +884,12 @@ name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "yaml-rust" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +dependencies = [ + "linked-hash-map", +] diff --git a/Cargo.toml b/Cargo.toml index 91334c3..1eddc33 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ cxx-qt = "0.7" cxx-qt-lib = { version="0.7", features = ["qt_full", "qt_gui"] } log = { version = "0.4.27", features = [] } dirs = "6.0.0" +syntect = "5.2.0" [build-dependencies] # The link_qt_object_files feature is required for statically linking Qt 6. diff --git a/qml/ClideEditor.qml b/qml/ClideEditor.qml index 8a14eb3..c971e81 100644 --- a/qml/ClideEditor.qml +++ b/qml/ClideEditor.qml @@ -67,7 +67,6 @@ SplitView { // Show the line number. Label { id: numbers - color: RustColors.linenumber font: textArea.font height: parent.height @@ -79,7 +78,6 @@ SplitView { // Draw edge along the right side of the line number. Rectangle { id: indicator - anchors.left: numbers.right color: RustColors.linenumber height: parent.height @@ -103,19 +101,17 @@ SplitView { TextArea.flickable: TextArea { id: textArea - - color: RustColors.editor_text focus: true persistentSelection: true + antialiasing: true selectByMouse: true - selectedTextColor: RustColors.editor_highlighted_text selectionColor: RustColors.editor_highlight + selectedTextColor: RustColors.editor_highlighted_text textFormat: Qt.AutoText wrapMode: TextArea.Wrap - text: FileSystem.readFile(root.filePath) - background: Rectangle { - color: RustColors.editor_background + Component.onCompleted: { + textArea.text = FileSystem.readFile(FileSystem.filePath) } onLinkActivated: function (link) { diff --git a/src/filesystem.rs b/src/filesystem.rs index fc99f67..7306b2c 100644 --- a/src/filesystem.rs +++ b/src/filesystem.rs @@ -41,9 +41,12 @@ pub mod qobject { use cxx_qt_lib::{QModelIndex, QString}; use dirs; +use log::warn; use std::fs; use std::fs::FileType; -use log::warn; +use syntect::highlighting::{Style, ThemeSet}; +use syntect::parsing::SyntaxSet; +use syntect::html::highlighted_html_for_file; // TODO: Impleent a provider for QFileSystemModel::setIconProvider for icons. pub struct FileSystemImpl { @@ -66,16 +69,19 @@ impl qobject::FileSystem { if path.is_empty() { return QString::default(); } - // TODO: Use syntect for syntax highlighting? - // https://github.com/trishume/syntect/blob/master/examples/syncat.rs if fs::metadata(path.to_string()) .expect(format!("Failed to get file metadata {}", path).as_str()) .is_file() { - QString::from( - fs::read_to_string(path.to_string()) - .expect(format!("Failed to read file {}", path).as_str()), - ) + let ps = SyntaxSet::load_defaults_nonewlines(); + let ts = ThemeSet::load_defaults(); + + if let Ok(result) = highlighted_html_for_file(std::path::Path::new(&path.to_string()), &ps, &ts.themes["base16-ocean.dark"]) { + QString::from(result) + } else { + warn!("Failed to read file {}", path); + QString::default() + } } else { warn!("Attempted to open file {} that is not a valid file", path); QString::default() -- 2.47.2 From f740ff347b79ed37e41bcc69bfffe5a73d4eefb0 Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Mon, 31 Mar 2025 22:56:57 -0400 Subject: [PATCH 20/73] Fix file loading. --- qml/ClideEditor.qml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/qml/ClideEditor.qml b/qml/ClideEditor.qml index c971e81..b16d9b5 100644 --- a/qml/ClideEditor.qml +++ b/qml/ClideEditor.qml @@ -109,10 +109,7 @@ SplitView { selectedTextColor: RustColors.editor_highlighted_text textFormat: Qt.AutoText wrapMode: TextArea.Wrap - - Component.onCompleted: { - textArea.text = FileSystem.readFile(FileSystem.filePath) - } + text: FileSystem.readFile(root.filePath) onLinkActivated: function (link) { Qt.openUrlExternally(link); -- 2.47.2 From 2dcf0529d17ba7b611efdb0350fb4d9906a1f2bc Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Sat, 12 Apr 2025 13:33:39 -0400 Subject: [PATCH 21/73] Custom highlighting to fix UI bugs. + Selecting text caused blurry editor view. + Now prefers syntect theme background color over QML background color. --- src/filesystem.rs | 52 +++++++++++++++++++++++++++++++++-------------- 1 file changed, 37 insertions(+), 15 deletions(-) diff --git a/src/filesystem.rs b/src/filesystem.rs index 7306b2c..2c39d7f 100644 --- a/src/filesystem.rs +++ b/src/filesystem.rs @@ -43,10 +43,13 @@ use cxx_qt_lib::{QModelIndex, QString}; use dirs; use log::warn; use std::fs; -use std::fs::FileType; -use syntect::highlighting::{Style, ThemeSet}; +use std::io::BufRead; +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; -use syntect::html::highlighted_html_for_file; // TODO: Impleent a provider for QFileSystemModel::setIconProvider for icons. pub struct FileSystemImpl { @@ -69,23 +72,42 @@ impl qobject::FileSystem { if path.is_empty() { return QString::default(); } - if fs::metadata(path.to_string()) + if !fs::metadata(path.to_string()) .expect(format!("Failed to get file metadata {}", path).as_str()) .is_file() { - let ps = SyntaxSet::load_defaults_nonewlines(); - let ts = ThemeSet::load_defaults(); - - if let Ok(result) = highlighted_html_for_file(std::path::Path::new(&path.to_string()), &ps, &ts.themes["base16-ocean.dark"]) { - QString::from(result) - } else { - warn!("Failed to read file {}", path); - QString::default() - } - } else { warn!("Attempted to open file {} that is not a valid file", path); - QString::default() + 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( + ®ions[..], + IncludeBackground::Yes, + &mut output, + ) + .expect("Failed to insert highlighted html"); + line.clear(); + } + output.push_str("\n"); + QString::from(output) } // There will never be more than one column. -- 2.47.2 From 90c10d2a16c1a996f5e976201a827e66d38b3e90 Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Sun, 13 Apr 2025 08:31:41 -0400 Subject: [PATCH 22/73] Debug missing console placeholder text. It appears on resizing horizontally --- qml/ClideProjectView.qml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/qml/ClideProjectView.qml b/qml/ClideProjectView.qml index fa1cad5..1f97333 100644 --- a/qml/ClideProjectView.qml +++ b/qml/ClideProjectView.qml @@ -16,6 +16,7 @@ SplitView { // Customized handle to drag between the Navigation and the Editor. handle: Rectangle { + id: verticalSplitHandle border.color: SplitHandle.pressed ? RustColors.pressed : SplitHandle.hovered ? RustColors.hovered : RustColors.gutter color: SplitHandle.pressed ? RustColors.pressed : SplitHandle.hovered ? RustColors.hovered : RustColors.gutter implicitWidth: 8 @@ -31,10 +32,12 @@ SplitView { Rectangle { id: navigationView + color: RustColors.explorer_background SplitView.fillHeight: true + SplitView.minimumWidth: 0 SplitView.preferredWidth: 200 - color: RustColors.explorer_background + SplitView.maximumWidth: 250 StackLayout { anchors.fill: parent @@ -45,6 +48,7 @@ SplitView { } } ClideEditor { + SplitView.fillWidth: true // Initialize using the Default trait in Rust QML singleton FileSystem. filePath: root.selectedFilePath } -- 2.47.2 From 7bf6c3299cbcce99c1fb0c496a2dc73227691363 Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Sun, 13 Apr 2025 09:35:55 -0400 Subject: [PATCH 23/73] Add basic CLI for launching head(less) mode. --- src/main.rs | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 57 insertions(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index cad2fb6..5f8a245 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,11 +1,14 @@ // TODO: Header use cxx_qt_lib::QString; +use std::env; +use std::error::Error; +use std::process::exit; pub mod colors; pub mod filesystem; -fn main() { +fn run_gui() -> Result<(), Box> { use cxx_qt_lib::{QGuiApplication, QQmlApplicationEngine, QUrl}; let mut app = QGuiApplication::new(); @@ -21,4 +24,57 @@ fn main() { if let Some(app) = app.as_mut() { app.exec(); } + + Ok(()) +} + +fn run_tui() -> Result<(), Box> { + println!("Starting TUI editor..."); + // Your cursive logic here, like: + // let mut siv = cursive::default(); + // siv.add_layer(...); + // siv.run(); + Ok(()) +} + +fn print_help() { + println!("Usage: app_launcher [OPTION]"); + println!("Options:"); + println!(" gui Launch the QML GUI application"); + println!(" tui Launch the TUI text editor (cursive-based)"); + println!(" help Show this help message"); +} + +fn main() { + let args: Vec = env::args().collect(); + + if args.len() < 2 { + if let Err(e) = run_gui() { + eprintln!("Error launching GUI: {}", e); + exit(1); + } + } + + match args.get(1).map(|s| s.as_str()) { + Some("gui") => { + if let Err(e) = run_gui() { + eprintln!("Error launching GUI: {}", e); + exit(1); + } + } + Some("tui") => { + if let Err(e) = run_tui() { + eprintln!("Error launching TUI: {}", e); + exit(1); + } + } + Some("help") | None => { + print_help(); + } + Some(arg) => { + eprintln!("Unknown argument: {}", arg); + print_help(); + exit(1); + } + } } -- 2.47.2 From 41a9a2a3bf22f018c962ecc50d7e1959a3db6ba5 Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Sun, 13 Apr 2025 10:20:43 -0400 Subject: [PATCH 24/73] Use structopt. --- Cargo.lock | 182 +++++++++++++++++++++++++++++++++++++++++++++++++--- Cargo.toml | 1 + src/main.rs | 72 ++++++++------------- 3 files changed, 201 insertions(+), 54 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5c181d1..7783bc7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,12 +8,32 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + [[package]] name = "anstyle" version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + [[package]] name = "base64" version = "0.22.1" @@ -67,6 +87,21 @@ dependencies = [ "thiserror 1.0.69", ] +[[package]] +name = "clap" +version = "2.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" +dependencies = [ + "ansi_term", + "atty", + "bitflags 1.3.2", + "strsim 0.8.0", + "textwrap", + "unicode-width", + "vec_map", +] + [[package]] name = "clap" version = "4.5.32" @@ -84,7 +119,7 @@ checksum = "22a7ef7f676155edfb82daa97f99441f3ebf4a58d5e32f295a56259f1b6facc8" dependencies = [ "anstyle", "clap_lex", - "strsim", + "strsim 0.11.1", ] [[package]] @@ -103,6 +138,7 @@ dependencies = [ "cxx-qt-lib", "dirs", "log", + "structopt", "syntect", ] @@ -157,7 +193,7 @@ dependencies = [ "codespan-reporting", "proc-macro2", "quote", - "syn", + "syn 2.0.100", ] [[package]] @@ -203,7 +239,7 @@ dependencies = [ "indoc", "proc-macro2", "quote", - "syn", + "syn 2.0.100", ] [[package]] @@ -226,7 +262,7 @@ checksum = "58a4fe02c0604eda28c605792f5ba0d0251b4947f8f0fc43e55b61c06b2b8ec6" dependencies = [ "cxx-qt-gen", "proc-macro2", - "syn", + "syn 2.0.100", ] [[package]] @@ -235,11 +271,11 @@ version = "1.0.148" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40399fddbf3977647bfff7453dacffc6b5701b19a282a283369a870115d0a049" dependencies = [ - "clap", + "clap 4.5.32", "codespan-reporting", "proc-macro2", "quote", - "syn", + "syn 2.0.100", ] [[package]] @@ -257,7 +293,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn", + "syn 2.0.100", ] [[package]] @@ -341,6 +377,24 @@ version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +[[package]] +name = "heck" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + [[package]] name = "indexmap" version = "2.8.0" @@ -381,6 +435,12 @@ dependencies = [ "libc", ] +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + [[package]] name = "libc" version = "0.2.171" @@ -514,6 +574,30 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + [[package]] name = "proc-macro2" version = "1.0.94" @@ -607,7 +691,7 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.100", ] [[package]] @@ -634,12 +718,53 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" + [[package]] name = "strsim" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "structopt" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c6b5c64445ba8094a6ab0c3cd2ad323e07171012d9c98b0b15651daf1787a10" +dependencies = [ + "clap 2.34.0", + "lazy_static", + "structopt-derive", +] + +[[package]] +name = "structopt-derive" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.100" @@ -682,6 +807,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + [[package]] name = "thiserror" version = "1.0.69" @@ -708,7 +842,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.100", ] [[package]] @@ -719,7 +853,7 @@ checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.100", ] [[package]] @@ -771,6 +905,12 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + [[package]] name = "version_check" version = "0.9.5" @@ -803,6 +943,22 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + [[package]] name = "winapi-util" version = "0.1.9" @@ -812,6 +968,12 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "windows-sys" version = "0.59.0" diff --git a/Cargo.toml b/Cargo.toml index 1eddc33..ab6ff76 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ cxx-qt-lib = { version="0.7", features = ["qt_full", "qt_gui"] } log = { version = "0.4.27", features = [] } dirs = "6.0.0" syntect = "5.2.0" +structopt = "0.3.26" [build-dependencies] # The link_qt_object_files feature is required for statically linking Qt 6. diff --git a/src/main.rs b/src/main.rs index 5f8a245..03f1108 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,14 +1,15 @@ // TODO: Header use cxx_qt_lib::QString; -use std::env; use std::error::Error; -use std::process::exit; +use structopt::StructOpt; pub mod colors; pub mod filesystem; -fn run_gui() -> Result<(), Box> { +fn run_gui(root_path: std::path::PathBuf) -> Result<(), Box> { + println!("Starting the GUI editor at {:?}", root_path); + use cxx_qt_lib::{QGuiApplication, QQmlApplicationEngine, QUrl}; let mut app = QGuiApplication::new(); @@ -28,53 +29,36 @@ fn run_gui() -> Result<(), Box> { Ok(()) } -fn run_tui() -> Result<(), Box> { - println!("Starting TUI editor..."); - // Your cursive logic here, like: - // let mut siv = cursive::default(); - // siv.add_layer(...); - // siv.run(); +fn run_tui(root_path: std::path::PathBuf) -> Result<(), Box> { + println!("Starting the TUI editor at {:?}", root_path); Ok(()) } -fn print_help() { - println!("Usage: app_launcher [OPTION]"); - println!("Options:"); - println!(" gui Launch the QML GUI application"); - println!(" tui Launch the TUI text editor (cursive-based)"); - println!(" help Show this help message"); +#[derive(StructOpt, Debug)] +#[structopt(name = "clide")] +struct Cli { + /// The root path to open with the clide editor. + #[structopt(parse(from_os_str))] + pub path: Option, + + /// Run in headless mode if this flag is present. + #[structopt(name = "tui", short, long)] + pub tui: bool, } -fn main() { - let args: Vec = env::args().collect(); +fn main() -> Result<(), Box> { + let args = Cli::from_args(); - if args.len() < 2 { - if let Err(e) = run_gui() { - eprintln!("Error launching GUI: {}", e); - exit(1); - } - } + // If the CLI was provided a directory to open use it. + // Otherwise, attempt to find the home directory. If that fails use CWD. + let root_path = args + .path + .or_else(dirs::home_dir) + .unwrap_or_else(|| std::env::current_dir().expect("Failed to access filesystem.")); - match args.get(1).map(|s| s.as_str()) { - Some("gui") => { - if let Err(e) = run_gui() { - eprintln!("Error launching GUI: {}", e); - exit(1); - } - } - Some("tui") => { - if let Err(e) = run_tui() { - eprintln!("Error launching TUI: {}", e); - exit(1); - } - } - Some("help") | None => { - print_help(); - } - Some(arg) => { - eprintln!("Unknown argument: {}", arg); - print_help(); - exit(1); - } + // Open the TUI editor if requested, otherwise use the QML GUI by default. + match args.tui { + true => run_tui(root_path), + false => run_gui(root_path), } } -- 2.47.2 From a29ae43e84bff8038ed38521ea569bf6fb783a08 Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Sun, 13 Apr 2025 11:01:37 -0400 Subject: [PATCH 25/73] Use CWD if no directory is provided. --- src/main.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/main.rs b/src/main.rs index 03f1108..1b35fab 100644 --- a/src/main.rs +++ b/src/main.rs @@ -49,12 +49,14 @@ struct Cli { fn main() -> Result<(), Box> { let args = Cli::from_args(); - // If the CLI was provided a directory to open use it. - // Otherwise, attempt to find the home directory. If that fails use CWD. - let root_path = args - .path - .or_else(dirs::home_dir) - .unwrap_or_else(|| std::env::current_dir().expect("Failed to access filesystem.")); + let root_path = match args.path { + // If the CLI was provided a directory convert it to absolute. + Some(path) => std::path::absolute(path)?, + // If no path was provided, use current directory. + None => std::env::current_dir().unwrap_or_else(|_| + // If we can't find the CWD attempt to open the home directory. + dirs::home_dir().expect("Failed to access filesystem.")), + }; // Open the TUI editor if requested, otherwise use the QML GUI by default. match args.tui { -- 2.47.2 From d53ef9aa1bdbbb537fb005b53116c0dbd8e6a3e2 Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Sun, 13 Apr 2025 11:58:28 -0400 Subject: [PATCH 26/73] Launch clide in separate process by default. Improve CLI to support tui and gui modes. Also supports attaching the GUI to the current terminal via -g --- src/main.rs | 36 +++++++++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/src/main.rs b/src/main.rs index 1b35fab..ab1c84b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,6 +2,7 @@ use cxx_qt_lib::QString; use std::error::Error; +use std::process::{Command, Stdio}; use structopt::StructOpt; pub mod colors; @@ -34,16 +35,23 @@ fn run_tui(root_path: std::path::PathBuf) -> Result<(), Box> { Ok(()) } +/// Command line interface IDE with full GUI and headless modes. +/// If no flags are provided the GUI editor is launched in a separate process. +/// If no path is provided the current directory is used. #[derive(StructOpt, Debug)] -#[structopt(name = "clide")] +#[structopt(name = "clide", verbatim_doc_comment)] struct Cli { - /// The root path to open with the clide editor. + /// The root directory for the project to open with the clide editor. #[structopt(parse(from_os_str))] pub path: Option, - /// Run in headless mode if this flag is present. + /// Run clide in headless mode. #[structopt(name = "tui", short, long)] pub tui: bool, + + /// Run the clide GUI in the current process, blocking the terminal and showing all output streams. + #[structopt(name = "gui", short, long)] + pub gui: bool, } fn main() -> Result<(), Box> { @@ -58,9 +66,23 @@ fn main() -> Result<(), Box> { dirs::home_dir().expect("Failed to access filesystem.")), }; - // Open the TUI editor if requested, otherwise use the QML GUI by default. - match args.tui { - true => run_tui(root_path), - false => run_gui(root_path), + match args.gui { + true => run_gui(root_path), + false => { + // Open the TUI editor if requested, otherwise use the QML GUI by default. + match args.tui { + true => run_tui(root_path), + false => { + // Relaunch the CLIDE GUI in a separate process. + Command::new(std::env::current_exe()?) + .args(&["--gui"]) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .stdin(Stdio::null()) + .spawn()?; + Ok(()) + } + } + } } } -- 2.47.2 From fd9d47f0c01ff853d83413b76bd1630ec3f7a330 Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Sun, 13 Apr 2025 12:09:18 -0400 Subject: [PATCH 27/73] Clean up. --- src/main.rs | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/src/main.rs b/src/main.rs index ab1c84b..d9c8474 100644 --- a/src/main.rs +++ b/src/main.rs @@ -68,21 +68,19 @@ fn main() -> Result<(), Box> { match args.gui { true => run_gui(root_path), - false => { + false => match args.tui { // Open the TUI editor if requested, otherwise use the QML GUI by default. - match args.tui { - true => run_tui(root_path), - false => { - // Relaunch the CLIDE GUI in a separate process. - Command::new(std::env::current_exe()?) - .args(&["--gui"]) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .stdin(Stdio::null()) - .spawn()?; - Ok(()) - } + true => run_tui(root_path), + false => { + // Relaunch the CLIDE GUI in a separate process. + Command::new(std::env::current_exe()?) + .args(&["--gui"]) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .stdin(Stdio::null()) + .spawn()?; + Ok(()) } - } + }, } } -- 2.47.2 From fd3c8fb204b5c772389a7ad905d5562e43b31473 Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Sun, 13 Apr 2025 12:15:31 -0400 Subject: [PATCH 28/73] Factor out GUI code. --- build.rs | 2 +- src/gui.rs | 27 +++++++++++++++++++++++++++ src/{ => gui}/colors.rs | 0 src/{ => gui}/filesystem.rs | 0 src/main.rs | 28 ++-------------------------- 5 files changed, 30 insertions(+), 27 deletions(-) create mode 100644 src/gui.rs rename src/{ => gui}/colors.rs (100%) rename src/{ => gui}/filesystem.rs (100%) diff --git a/build.rs b/build.rs index 1e369a2..87a98ba 100644 --- a/build.rs +++ b/build.rs @@ -13,7 +13,7 @@ fn main() { .qt_module("Xml") .qml_module(QmlModule { uri: "clide.module", - rust_files: &["src/colors.rs", "src/filesystem.rs"], + rust_files: &["src/gui/colors.rs", "src/gui/filesystem.rs"], qml_files: &[ "qml/main.qml", "qml/ClideAboutWindow.qml", diff --git a/src/gui.rs b/src/gui.rs new file mode 100644 index 0000000..62c6172 --- /dev/null +++ b/src/gui.rs @@ -0,0 +1,27 @@ +use cxx_qt_lib::QString; +use std::error::Error; + +pub mod colors; +pub mod filesystem; + +pub fn run(root_path: std::path::PathBuf) -> Result<(), Box> { + println!("Starting the GUI editor at {:?}", root_path); + + use cxx_qt_lib::{QGuiApplication, QQmlApplicationEngine, QUrl}; + + let mut app = QGuiApplication::new(); + let mut engine = QQmlApplicationEngine::new(); + + if let Some(engine) = engine.as_mut() { + engine.add_import_path(&QString::from("qml/")); + } + if let Some(engine) = engine.as_mut() { + engine.load(&QUrl::from("qml/main.qml")); + } + + if let Some(app) = app.as_mut() { + app.exec(); + } + + Ok(()) +} diff --git a/src/colors.rs b/src/gui/colors.rs similarity index 100% rename from src/colors.rs rename to src/gui/colors.rs diff --git a/src/filesystem.rs b/src/gui/filesystem.rs similarity index 100% rename from src/filesystem.rs rename to src/gui/filesystem.rs diff --git a/src/main.rs b/src/main.rs index d9c8474..c62f3b2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,34 +1,10 @@ // TODO: Header -use cxx_qt_lib::QString; use std::error::Error; use std::process::{Command, Stdio}; use structopt::StructOpt; -pub mod colors; -pub mod filesystem; - -fn run_gui(root_path: std::path::PathBuf) -> Result<(), Box> { - println!("Starting the GUI editor at {:?}", root_path); - - use cxx_qt_lib::{QGuiApplication, QQmlApplicationEngine, QUrl}; - - let mut app = QGuiApplication::new(); - let mut engine = QQmlApplicationEngine::new(); - - if let Some(engine) = engine.as_mut() { - engine.add_import_path(&QString::from("qml/")); - } - if let Some(engine) = engine.as_mut() { - engine.load(&QUrl::from("qml/main.qml")); - } - - if let Some(app) = app.as_mut() { - app.exec(); - } - - Ok(()) -} +pub mod gui; fn run_tui(root_path: std::path::PathBuf) -> Result<(), Box> { println!("Starting the TUI editor at {:?}", root_path); @@ -67,7 +43,7 @@ fn main() -> Result<(), Box> { }; match args.gui { - true => run_gui(root_path), + true => gui::run(root_path), false => match args.tui { // Open the TUI editor if requested, otherwise use the QML GUI by default. true => run_tui(root_path), -- 2.47.2 From f4242f7749c535e42d3d1471beb8df8b21901052 Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Sun, 13 Apr 2025 12:17:11 -0400 Subject: [PATCH 29/73] Factor out TUI code. --- src/main.rs | 8 ++------ src/tui.rs | 6 ++++++ 2 files changed, 8 insertions(+), 6 deletions(-) create mode 100644 src/tui.rs diff --git a/src/main.rs b/src/main.rs index c62f3b2..e62a7e1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,11 +5,7 @@ use std::process::{Command, Stdio}; use structopt::StructOpt; pub mod gui; - -fn run_tui(root_path: std::path::PathBuf) -> Result<(), Box> { - println!("Starting the TUI editor at {:?}", root_path); - Ok(()) -} +pub mod tui; /// Command line interface IDE with full GUI and headless modes. /// If no flags are provided the GUI editor is launched in a separate process. @@ -46,7 +42,7 @@ fn main() -> Result<(), Box> { true => gui::run(root_path), false => match args.tui { // Open the TUI editor if requested, otherwise use the QML GUI by default. - true => run_tui(root_path), + true => tui::run(root_path), false => { // Relaunch the CLIDE GUI in a separate process. Command::new(std::env::current_exe()?) diff --git a/src/tui.rs b/src/tui.rs new file mode 100644 index 0000000..2f3e4cd --- /dev/null +++ b/src/tui.rs @@ -0,0 +1,6 @@ +use std::error::Error; + +pub fn run(root_path: std::path::PathBuf) -> Result<(), Box> { + println!("Starting the TUI editor at {:?}", root_path); + Ok(()) +} -- 2.47.2 From 6a4957588d8fe31ed8e82777b9a8a8feef69c78e Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Sun, 13 Apr 2025 13:47:12 -0400 Subject: [PATCH 30/73] Pass root path to GUI process. --- src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index e62a7e1..11efd35 100644 --- a/src/main.rs +++ b/src/main.rs @@ -46,7 +46,7 @@ fn main() -> Result<(), Box> { false => { // Relaunch the CLIDE GUI in a separate process. Command::new(std::env::current_exe()?) - .args(&["--gui"]) + .args(&["--gui", root_path.to_str().unwrap()]) .stdout(Stdio::null()) .stderr(Stdio::null()) .stdin(Stdio::null()) -- 2.47.2 From cf59fdfccad63e2222c1942b0c11921e70dde68e Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Sat, 19 Apr 2025 13:49:29 -0400 Subject: [PATCH 31/73] Embed SVG icons. --- icons/folder_closed.svg | 38 -------------------------------------- icons/folder_open.svg | 38 -------------------------------------- icons/generic_file.svg | 38 -------------------------------------- qml/ClideTreeView.qml | 8 ++++++-- 4 files changed, 6 insertions(+), 116 deletions(-) delete mode 100644 icons/folder_closed.svg delete mode 100644 icons/folder_open.svg delete mode 100644 icons/generic_file.svg diff --git a/icons/folder_closed.svg b/icons/folder_closed.svg deleted file mode 100644 index 281be32..0000000 --- a/icons/folder_closed.svg +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - diff --git a/icons/folder_open.svg b/icons/folder_open.svg deleted file mode 100644 index 09f7615..0000000 --- a/icons/folder_open.svg +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - diff --git a/icons/generic_file.svg b/icons/generic_file.svg deleted file mode 100644 index e0423f2..0000000 --- a/icons/generic_file.svg +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - diff --git a/qml/ClideTreeView.qml b/qml/ClideTreeView.qml index 4f62089..fa4b483 100644 --- a/qml/ClideTreeView.qml +++ b/qml/ClideTreeView.qml @@ -45,12 +45,16 @@ Rectangle { x: treeDelegate.leftMargin + (treeDelegate.depth * treeDelegate.indentation) anchors.verticalCenter: parent.verticalCenter source: { + let folderOpen = "data:image/svg+xml;utf8,"; + let folderClosed = "data:image/svg+xml;utf8,"; + let file = "data:image/svg+xml;utf8,"; // If the item has children, it's a directory. if (treeDelegate.hasChildren) { return treeDelegate.expanded ? - "../icons/folder-open-solid.svg" : "../icons/folder-solid.svg"; + folderOpen : folderClosed; + } else { + return file } - return "../icons/file-solid.svg" } sourceSize.width: 15 sourceSize.height: 15 -- 2.47.2 From 7fe3e3e14d34ebb2a25752f8f4dcaacc9b2ba88d Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Sat, 17 Jan 2026 11:40:40 -0500 Subject: [PATCH 32/73] WIP ratatui. --- Cargo.lock | 1599 ++++++++++++++++++++++++++++++++++++++++++++------- Cargo.toml | 2 + src/main.rs | 2 +- src/tui.rs | 38 +- 4 files changed, 1418 insertions(+), 223 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7783bc7..1d9d0f3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,24 @@ version = 4 [[package]] name = "adler2" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "ansi_term" @@ -19,9 +34,24 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.10" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" + +[[package]] +name = "anyhow" +version = "1.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" + +[[package]] +name = "atomic" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89cbf775b137e9b968e67227ef7f775587cde3fd31b0d8599dbd0f598a48340" +dependencies = [ + "bytemuck", +] [[package]] name = "atty" @@ -34,6 +64,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + [[package]] name = "base64" version = "0.22.1" @@ -49,6 +85,21 @@ dependencies = [ "serde", ] +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + [[package]] name = "bitflags" version = "1.3.2" @@ -57,16 +108,47 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.9.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" + +[[package]] +name = "bytemuck" +version = "1.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" + +[[package]] +name = "castaway" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a" +dependencies = [ + "rustversion", +] [[package]] name = "cc" -version = "1.2.16" +version = "1.2.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c" +checksum = "755d2fce177175ffca841e9a06afdb2c4ab0f593d53b4dee48147dfaade85932" dependencies = [ + "find-msvc-tools", "jobserver", "libc", "shlex", @@ -74,9 +156,15 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "clang-format" @@ -98,24 +186,24 @@ dependencies = [ "bitflags 1.3.2", "strsim 0.8.0", "textwrap", - "unicode-width", + "unicode-width 0.1.14", "vec_map", ] [[package]] name = "clap" -version = "4.5.32" +version = "4.5.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6088f3ae8c3608d19260cd7445411865a485688711b78b5be70d78cd96136f83" +checksum = "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.32" +version = "4.5.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22a7ef7f676155edfb82daa97f99441f3ebf4a58d5e32f295a56259f1b6facc8" +checksum = "fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00" dependencies = [ "anstyle", "clap_lex", @@ -124,20 +212,22 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.4" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" +checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" [[package]] name = "clide" version = "0.1.0" dependencies = [ + "anyhow", "cxx", "cxx-qt", "cxx-qt-build", "cxx-qt-lib", "dirs", "log", + "ratatui", "structopt", "syntect", ] @@ -149,7 +239,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" dependencies = [ "termcolor", - "unicode-width", + "unicode-width 0.1.14", +] + +[[package]] +name = "codespan-reporting" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af491d569909a7e4dee0ad7db7f5341fef5c614d5b8ec8cf765732aba3cff681" +dependencies = [ + "serde", + "termcolor", + "unicode-width 0.2.2", +] + +[[package]] +name = "compact_str" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb1325a1cece981e8a296ab8f0f9b63ae357bd0784a9faaf548cc7b480707a" +dependencies = [ + "castaway", + "cfg-if", + "itoa", + "rustversion", + "ryu", + "static_assertions", ] [[package]] @@ -162,21 +277,87 @@ dependencies = [ ] [[package]] -name = "crc32fast" -version = "1.4.2" +name = "convert_case" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" dependencies = [ "cfg-if", ] [[package]] -name = "cxx" -version = "1.0.148" +name = "crossterm" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "342b09ea23e087717542308a865984555782302855f29427540bbe02d5e8a28a" +checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" +dependencies = [ + "bitflags 2.10.0", + "crossterm_winapi", + "derive_more", + "document-features", + "mio", + "parking_lot", + "rustix", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "csscolorparser" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb2a7d3066da2de787b7f032c736763eb7ae5d355f81a68bab2675a96008b0bf" +dependencies = [ + "lab", + "phf", +] + +[[package]] +name = "cxx" +version = "1.0.192" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbda285ba6e5866529faf76352bdf73801d9b44a6308d7cd58ca2379f378e994" dependencies = [ "cc", + "cxx-build", "cxxbridge-cmd", "cxxbridge-flags", "cxxbridge-macro", @@ -185,22 +366,38 @@ dependencies = [ ] [[package]] -name = "cxx-gen" -version = "0.7.148" +name = "cxx-build" +version = "1.0.192" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f3438c8fed495501035e5db627f6d7defe4635f3e29824dafce97018dd71a1d" +checksum = "af9efde466c5d532d57efd92f861da3bdb7f61e369128ce8b4c3fe0c9de4fa4d" dependencies = [ - "codespan-reporting", + "cc", + "codespan-reporting 0.13.1", + "indexmap", "proc-macro2", "quote", - "syn 2.0.100", + "scratch", + "syn 2.0.114", +] + +[[package]] +name = "cxx-gen" +version = "0.7.192" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee08d1131e8f050a1d1acbb7c699e5c8d29c325dffc382331c280d99f98c2618" +dependencies = [ + "codespan-reporting 0.13.1", + "indexmap", + "proc-macro2", + "quote", + "syn 2.0.114", ] [[package]] name = "cxx-qt" -version = "0.7.1" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "208ad6c4feac92f221fde00796f317b049ba1892b97be0d60ca177d0d3469fc5" +checksum = "fb4ce9106b3ee7ef85f77d5f69ec30ec3037ea1edacd3e29a61b26ff47ecc637" dependencies = [ "cxx", "cxx-qt-build", @@ -212,12 +409,12 @@ dependencies = [ [[package]] name = "cxx-qt-build" -version = "0.7.1" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15f80e109aa68795486c70c302f6c2d921f00028b3b62038a4601efb5c585c1c" +checksum = "23a7884708e645dc34a2c475bd8c505a2ffeb7c9cb0843f5c580c002c939ea30" dependencies = [ "cc", - "codespan-reporting", + "codespan-reporting 0.11.1", "cxx-gen", "cxx-qt-gen", "proc-macro2", @@ -230,23 +427,23 @@ dependencies = [ [[package]] name = "cxx-qt-gen" -version = "0.7.1" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc17d95ca9cc60c2f91f804a4e0ba6a3e1b8ed338c207a1bd8d176133e2fd05d" +checksum = "ea6b431dc58e4a9b7a55b675be0941331cc2cab0a494e4742ebf7bb393c3fc39" dependencies = [ "clang-format", - "convert_case", + "convert_case 0.6.0", "indoc", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.114", ] [[package]] name = "cxx-qt-lib" -version = "0.7.1" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f116c5d982bbf3be707acf97f566802c30454d52ca319c745ed39a04834e8bc6" +checksum = "527b46c28be6bf3dd02f1567706641fe247a8798defab8268c84602955741217" dependencies = [ "cxx", "cxx-qt", @@ -256,55 +453,128 @@ dependencies = [ [[package]] name = "cxx-qt-macro" -version = "0.7.1" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58a4fe02c0604eda28c605792f5ba0d0251b4947f8f0fc43e55b61c06b2b8ec6" +checksum = "971d8811fd1c8dd06c17284edcfc8e9663dac30d7e3d52159405f836a81923f1" dependencies = [ "cxx-qt-gen", "proc-macro2", - "syn 2.0.100", + "syn 2.0.114", ] [[package]] name = "cxxbridge-cmd" -version = "1.0.148" +version = "1.0.192" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40399fddbf3977647bfff7453dacffc6b5701b19a282a283369a870115d0a049" +checksum = "3efb93799095bccd4f763ca07997dc39a69e5e61ab52d2c407d4988d21ce144d" dependencies = [ - "clap 4.5.32", - "codespan-reporting", + "clap 4.5.54", + "codespan-reporting 0.13.1", + "indexmap", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.114", ] [[package]] name = "cxxbridge-flags" -version = "1.0.148" +version = "1.0.192" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9161673896b799047e79a245927e7921787ad016eed6770227f3f23de2746c7" +checksum = "3092010228026e143b32a4463ed9fa8f86dca266af4bf5f3b2a26e113dbe4e45" [[package]] name = "cxxbridge-macro" -version = "1.0.148" +version = "1.0.192" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dff513230582d396298cc00e8fb3d9a752822f85137c323fac4227ac5be6c268" +checksum = "31d72ebfcd351ae404fb00ff378dfc9571827a00722c9e735c9181aec320ba0a" dependencies = [ + "indexmap", "proc-macro2", "quote", - "rustversion", - "syn 2.0.100", + "syn 2.0.114", ] [[package]] -name = "deranged" -version = "0.4.1" +name = "darling" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28cfac68e08048ae1883171632c2aef3ebc555621ae56fbccce1cbf22dd7f058" +checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" +dependencies = [ + "ident_case", + "proc-macro2", + "quote", + "strsim 0.11.1", + "syn 2.0.114", +] + +[[package]] +name = "darling_macro" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "deltae" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5729f5117e208430e437df2f4843f5e5952997175992d1414f94c57d61e270b4" + +[[package]] +name = "deranged" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" dependencies = [ "powerfmt", ] +[[package]] +name = "derive_more" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "convert_case 0.10.0", + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.114", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + [[package]] name = "dirs" version = "6.0.0" @@ -326,6 +596,15 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "document-features" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" +dependencies = [ + "litrs", +] + [[package]] name = "either" version = "1.15.0" @@ -339,10 +618,68 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] -name = "flate2" -version = "1.1.0" +name = "errno" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11faaf5a5236997af9848be0bef4db95824b1d534ebc64d0f0c6cf3e67bd38dc" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "euclid" +version = "0.22.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad9cdb4b747e485a12abb0e6566612956c7a1bafa3bdb8d682c5b6d403589e48" +dependencies = [ + "num-traits", +] + +[[package]] +name = "fancy-regex" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b95f7c0680e4142284cf8b22c14a476e87d61b004a3a0861872b32ef7ead40a2" +dependencies = [ + "bit-set", + "regex", +] + +[[package]] +name = "filedescriptor" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e40758ed24c9b2eeb76c35fb0aebc66c626084edd827e07e1552279814c6682d" +dependencies = [ + "libc", + "thiserror 1.0.69", + "winapi", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db" + +[[package]] +name = "finl_unicode" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9844ddc3a6e533d62bba727eb6c28b5d360921d5175e9ff0f1e621a5c590a4d5" + +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + +[[package]] +name = "flate2" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b375d6465b98090a5f25b1c7703f3859783755aa9a80433b36e0379a3ec2f369" dependencies = [ "crc32fast", "miniz_oxide", @@ -356,15 +693,25 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "foldhash" -version = "0.1.5" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] [[package]] name = "getrandom" -version = "0.2.15" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", "libc", @@ -372,10 +719,27 @@ dependencies = [ ] [[package]] -name = "hashbrown" -version = "0.15.2" +name = "getrandom" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] [[package]] name = "heck" @@ -386,6 +750,12 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "hermit-abi" version = "0.1.19" @@ -396,10 +766,22 @@ dependencies = [ ] [[package]] -name = "indexmap" -version = "2.8.0" +name = "hex" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3954d50fe15b02142bf25d3b8bdadb634ec3948f103d04ffe3031bc8fe9d7058" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "indexmap" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" dependencies = [ "equivalent", "hashbrown", @@ -407,9 +789,25 @@ dependencies = [ [[package]] name = "indoc" -version = "2.0.6" +version = "2.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd" +checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706" +dependencies = [ + "rustversion", +] + +[[package]] +name = "instability" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357b7205c6cd18dd2c86ed312d1e70add149aea98e7ef72b9fdf0270e555c11d" +dependencies = [ + "darling", + "indoc", + "proc-macro2", + "quote", + "syn 2.0.114", +] [[package]] name = "itertools" @@ -421,20 +819,57 @@ dependencies = [ ] [[package]] -name = "itoa" -version = "1.0.15" +name = "itertools" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "jobserver" -version = "0.1.32" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" dependencies = [ + "getrandom 0.3.4", "libc", ] +[[package]] +name = "js-sys" +version = "0.3.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "kasuari" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fe90c1150662e858c7d5f945089b7517b0a80d8bf7ba4b1b5ffc984e7230a5b" +dependencies = [ + "hashbrown", + "portable-atomic", + "thiserror 2.0.17", +] + +[[package]] +name = "lab" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf36173d4167ed999940f804952e6b08197cae5ad5d572eb4db150ce8ad5d58f" + [[package]] name = "lazy_static" version = "1.5.0" @@ -443,25 +878,34 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.171" +version = "0.2.180" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" +checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" [[package]] name = "libredox" -version = "0.1.3" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.10.0", "libc", ] [[package]] -name = "link-cplusplus" -version = "1.0.10" +name = "line-clipping" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a6f6da007f968f9def0d65a05b187e2960183de70c160204ecfccf0ee330212" +checksum = "5f4de44e98ddbf09375cbf4d17714d18f39195f4f4894e8524501726fd9a8a4a" +dependencies = [ + "bitflags 2.10.0", +] + +[[package]] +name = "link-cplusplus" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f78c730aaa7d0b9336a299029ea49f9ee53b0ed06e9202e8cb7db9bae7b8c82" dependencies = [ "cc", ] @@ -473,16 +917,71 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" [[package]] -name = "log" -version = "0.4.27" +name = "linux-raw-sys" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + +[[package]] +name = "litrs" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "lru" +version = "0.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1dc47f592c06f33f8e3aea9591776ec7c9f9e4124778ff8a3c3b87159f7e593" +dependencies = [ + "hashbrown", +] + +[[package]] +name = "mac_address" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0aeb26bf5e836cc1c341c8106051b573f1766dfa05aa87f0b98be5e51b02303" +dependencies = [ + "nix", + "winapi", +] [[package]] name = "memchr" -version = "2.7.4" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "memmem" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a64a92489e2744ce060c349162be1c5f33c6969234104dbd99ddb5feb08b8c15" + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] [[package]] name = "minimal-lexical" @@ -492,11 +991,37 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.8.5" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys", +] + +[[package]] +name = "nix" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +dependencies = [ + "bitflags 2.10.0", + "cfg-if", + "cfg_aliases", + "libc", + "memoffset", ] [[package]] @@ -515,6 +1040,35 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_threads" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" +dependencies = [ + "libc", +] + [[package]] name = "once_cell" version = "1.21.3" @@ -523,11 +1077,11 @@ checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "onig" -version = "6.4.0" +version = "6.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c4b31c8722ad9171c6d77d3557db078cab2bd50afcc9d09c8b315c59df8ca4f" +checksum = "336b9c63443aceef14bea841b899035ae3abe89b7c486aaf4c5bd8aafedac3f0" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.10.0", "libc", "once_cell", "onig_sys", @@ -535,9 +1089,9 @@ dependencies = [ [[package]] name = "onig_sys" -version = "69.8.1" +version = "69.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b829e3d7e9cc74c7e315ee8edb185bf4190da5acde74afd7fc59c35b1f086e7" +checksum = "c7f86c6eef3d6df15f23bcfb6af487cbd2fed4e5581d58d5bf1f5f8b7f6727dc" dependencies = [ "cc", "pkg-config", @@ -549,6 +1103,133 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" +[[package]] +name = "ordered-float" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bb71e1b3fa6ca1c61f383464aaf2bb0e2f8e772a1f01d486832464de363b951" +dependencies = [ + "num-traits", +] + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "pest" +version = "2.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9eb05c21a464ea704b53158d358a31e6425db2f63a1a7312268b05fe2b75f7" +dependencies = [ + "memchr", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f9dbced329c441fa79d80472764b1a2c7e57123553b8519b36663a2fb234ed" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bb96d5051a78f44f43c8f712d8e810adb0ebf923fc9ed2655a7f66f63ba8ee5" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "pest_meta" +version = "2.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "602113b5b5e8621770cfd490cfd90b9f84ab29bd2b0e49ad83eb6d186cef2365" +dependencies = [ + "pest", + "sha2", +] + +[[package]] +name = "phf" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +dependencies = [ + "phf_macros", + "phf_shared", +] + +[[package]] +name = "phf_codegen" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" +dependencies = [ + "phf_generator", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" +dependencies = [ + "phf_shared", + "rand", +] + +[[package]] +name = "phf_macros" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher", +] + [[package]] name = "pkg-config" version = "0.3.32" @@ -557,9 +1238,9 @@ checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "plist" -version = "1.7.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac26e981c03a6e53e0aee43c113e3202f5581d5360dae7bd2c70e800dd0451d" +checksum = "740ebea15c5d1428f910cd1a5f52cebf8d25006245ed8ade92702f4943d91e07" dependencies = [ "base64", "indexmap", @@ -568,6 +1249,12 @@ dependencies = [ "time", ] +[[package]] +name = "portable-atomic" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f89776e4d69bb58bc6993e99ffa1d11f228b839984854c7daeb5d37f87cbe950" + [[package]] name = "powerfmt" version = "0.2.0" @@ -600,18 +1287,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.94" +version = "1.0.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" +checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7" dependencies = [ "unicode-ident", ] [[package]] name = "qt-build-utils" -version = "0.7.1" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efb239fdd8c036fabb95364320041ef68197cd4ab971bb3b4ca3ea0b7b93d12c" +checksum = "63c7e24653d0b3084180066306b26e532b152470b13a050c2d2960284d4b9f53" dependencies = [ "cc", "thiserror 1.0.69", @@ -620,50 +1307,210 @@ dependencies = [ [[package]] name = "quick-xml" -version = "0.32.0" +version = "0.38.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d3a6e5838b60e0e8fa7a43f22ade549a37d61f8bdbe636d0d7816191de969c2" +checksum = "b66c2058c55a409d601666cffe35f04333cf1013010882cec174a7467cd4e21c" dependencies = [ "memchr", ] [[package]] name = "quote" -version = "1.0.40" +version = "1.0.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" dependencies = [ "proc-macro2", ] [[package]] -name = "redox_users" -version = "0.5.0" +name = "r-efi" +version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ - "getrandom", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + +[[package]] +name = "ratatui" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1ce67fb8ba4446454d1c8dbaeda0557ff5e94d39d5e5ed7f10a65eb4c8266bc" +dependencies = [ + "instability", + "ratatui-core", + "ratatui-crossterm", + "ratatui-macros", + "ratatui-termwiz", + "ratatui-widgets", +] + +[[package]] +name = "ratatui-core" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ef8dea09a92caaf73bff7adb70b76162e5937524058a7e5bff37869cbbec293" +dependencies = [ + "bitflags 2.10.0", + "compact_str", + "hashbrown", + "indoc", + "itertools 0.14.0", + "kasuari", + "lru", + "strum", + "thiserror 2.0.17", + "unicode-segmentation", + "unicode-truncate", + "unicode-width 0.2.2", +] + +[[package]] +name = "ratatui-crossterm" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "577c9b9f652b4c121fb25c6a391dd06406d3b092ba68827e6d2f09550edc54b3" +dependencies = [ + "cfg-if", + "crossterm", + "instability", + "ratatui-core", +] + +[[package]] +name = "ratatui-macros" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7f1342a13e83e4bb9d0b793d0ea762be633f9582048c892ae9041ef39c936f4" +dependencies = [ + "ratatui-core", + "ratatui-widgets", +] + +[[package]] +name = "ratatui-termwiz" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f76fe0bd0ed4295f0321b1676732e2454024c15a35d01904ddb315afd3d545c" +dependencies = [ + "ratatui-core", + "termwiz", +] + +[[package]] +name = "ratatui-widgets" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7dbfa023cd4e604c2553483820c5fe8aa9d71a42eea5aa77c6e7f35756612db" +dependencies = [ + "bitflags 2.10.0", + "hashbrown", + "indoc", + "instability", + "itertools 0.14.0", + "line-clipping", + "ratatui-core", + "strum", + "time", + "unicode-segmentation", + "unicode-width 0.2.2", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.10.0", +] + +[[package]] +name = "redox_users" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" +dependencies = [ + "getrandom 0.2.17", "libredox", - "thiserror 2.0.12", + "thiserror 2.0.17", +] + +[[package]] +name = "regex" +version = "1.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", ] [[package]] name = "regex-syntax" -version = "0.8.5" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" +dependencies = [ + "bitflags 2.10.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] [[package]] name = "rustversion" -version = "1.0.20" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" -version = "1.0.20" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" [[package]] name = "same-file" @@ -675,35 +1522,75 @@ dependencies = [ ] [[package]] -name = "serde" -version = "1.0.219" +name = "scopeguard" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "scratch" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d68f2ec51b097e4c1a75b681a8bec621909b5e91f15bb7b840c4f2f7b01148b2" + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.114", ] [[package]] name = "serde_json" -version = "1.0.140" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ "itoa", "memchr", - "ryu", "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", ] [[package]] @@ -712,6 +1599,55 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "signal-hook" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b75a19a7a740b25bc7944bdee6172368f988763b744e3d4dfe753f6b4ece40cc" +dependencies = [ + "libc", + "mio", + "signal-hook", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "simd-adler32" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" + +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + [[package]] name = "static_assertions" version = "1.1.0" @@ -747,13 +1683,34 @@ version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0" dependencies = [ - "heck", + "heck 0.3.3", "proc-macro-error", "proc-macro2", "quote", "syn 1.0.109", ] +[[package]] +name = "strum" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.114", +] + [[package]] name = "syn" version = "1.0.109" @@ -767,9 +1724,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.100" +version = "2.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" dependencies = [ "proc-macro2", "quote", @@ -778,12 +1735,11 @@ dependencies = [ [[package]] name = "syntect" -version = "5.2.0" +version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "874dcfa363995604333cf947ae9f751ca3af4522c60886774c4963943b4746b1" +checksum = "656b45c05d95a5704399aeef6bd0ddec7b2b3531b7c9e900abbf7c4d2190c925" dependencies = [ "bincode", - "bitflags 1.3.2", "flate2", "fnv", "once_cell", @@ -793,7 +1749,7 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "thiserror 1.0.69", + "thiserror 2.0.17", "walkdir", "yaml-rust", ] @@ -807,13 +1763,76 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "terminfo" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4ea810f0692f9f51b382fff5893887bb4580f5fa246fde546e0b13e7fcee662" +dependencies = [ + "fnv", + "nom", + "phf", + "phf_codegen", +] + +[[package]] +name = "termios" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "411c5bf740737c7918b8b1fe232dca4dc9f8e754b8ad5e20966814001ed0ac6b" +dependencies = [ + "libc", +] + +[[package]] +name = "termwiz" +version = "0.23.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4676b37242ccbd1aabf56edb093a4827dc49086c0ffd764a5705899e0f35f8f7" +dependencies = [ + "anyhow", + "base64", + "bitflags 2.10.0", + "fancy-regex", + "filedescriptor", + "finl_unicode", + "fixedbitset", + "hex", + "lazy_static", + "libc", + "log", + "memmem", + "nix", + "num-derive", + "num-traits", + "ordered-float", + "pest", + "pest_derive", + "phf", + "sha2", + "signal-hook", + "siphasher", + "terminfo", + "termios", + "thiserror 1.0.69", + "ucd-trie", + "unicode-segmentation", + "vtparse", + "wezterm-bidi", + "wezterm-blob-leases", + "wezterm-color-types", + "wezterm-dynamic", + "wezterm-input-types", + "winapi", +] + [[package]] name = "textwrap" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" dependencies = [ - "unicode-width", + "unicode-width 0.1.14", ] [[package]] @@ -827,11 +1846,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.12" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" dependencies = [ - "thiserror-impl 2.0.12", + "thiserror-impl 2.0.17", ] [[package]] @@ -842,56 +1861,70 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.114", ] [[package]] name = "thiserror-impl" -version = "2.0.12" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.114", ] [[package]] name = "time" -version = "0.3.41" +version = "0.3.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" +checksum = "f9e442fc33d7fdb45aa9bfeb312c095964abdf596f7567261062b2a7107aaabd" dependencies = [ "deranged", "itoa", + "libc", "num-conv", + "num_threads", "powerfmt", - "serde", + "serde_core", "time-core", "time-macros", ] [[package]] name = "time-core" -version = "0.1.4" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" +checksum = "8b36ee98fd31ec7426d599183e8fe26932a8dc1fb76ddb6214d05493377d34ca" [[package]] name = "time-macros" -version = "0.2.22" +version = "0.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" +checksum = "71e552d1249bf61ac2a52db88179fd0673def1e1ad8243a00d9ec9ed71fee3dd" dependencies = [ "num-conv", "time-core", ] [[package]] -name = "unicode-ident" -version = "1.0.18" +name = "typenum" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "ucd-trie" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] name = "unicode-segmentation" @@ -899,12 +1932,47 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" +[[package]] +name = "unicode-truncate" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b380a1238663e5f8a691f9039c73e1cdae598a30e9855f541d29b08b53e9a5" +dependencies = [ + "itertools 0.14.0", + "unicode-segmentation", + "unicode-width 0.2.2", +] + [[package]] name = "unicode-width" version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "uuid" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a" +dependencies = [ + "atomic", + "getrandom 0.3.4", + "js-sys", + "wasm-bindgen", +] + [[package]] name = "vec_map" version = "0.8.2" @@ -923,10 +1991,19 @@ version = "6.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f25d498b63d1fdb376b4250f39ab3a5ee8d103957346abacd911e2d8b612c139" dependencies = [ - "itertools", + "itertools 0.13.0", "nom", ] +[[package]] +name = "vtparse" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d9b2acfb050df409c972a37d3b8e08cdea3bddb0c09db9d53137e504cfabed0" +dependencies = [ + "utf8parse", +] + [[package]] name = "walkdir" version = "2.5.0" @@ -939,9 +2016,135 @@ dependencies = [ [[package]] name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.2+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.114", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wezterm-bidi" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0a6e355560527dd2d1cf7890652f4f09bb3433b6aadade4c9b5ed76de5f3ec" +dependencies = [ + "log", + "wezterm-dynamic", +] + +[[package]] +name = "wezterm-blob-leases" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "692daff6d93d94e29e4114544ef6d5c942a7ed998b37abdc19b17136ea428eb7" +dependencies = [ + "getrandom 0.3.4", + "mac_address", + "sha2", + "thiserror 1.0.69", + "uuid", +] + +[[package]] +name = "wezterm-color-types" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7de81ef35c9010270d63772bebef2f2d6d1f2d20a983d27505ac850b8c4b4296" +dependencies = [ + "csscolorparser", + "deltae", + "lazy_static", + "wezterm-dynamic", +] + +[[package]] +name = "wezterm-dynamic" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f2ab60e120fd6eaa68d9567f3226e876684639d22a4219b313ff69ec0ccd5ac" +dependencies = [ + "log", + "ordered-float", + "strsim 0.11.1", + "thiserror 1.0.69", + "wezterm-dynamic-derive", +] + +[[package]] +name = "wezterm-dynamic-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c0cf2d539c645b448eaffec9ec494b8b19bd5077d9e58cb1ae7efece8d575b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "wezterm-input-types" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7012add459f951456ec9d6c7e6fc340b1ce15d6fc9629f8c42853412c029e57e" +dependencies = [ + "bitflags 1.3.2", + "euclid", + "lazy_static", + "serde", + "wezterm-dynamic", +] [[package]] name = "winapi" @@ -961,9 +2164,9 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.9" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ "windows-sys", ] @@ -974,78 +2177,26 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + [[package]] name = "windows-sys" -version = "0.59.0" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows-targets", + "windows-link", ] [[package]] -name = "windows-targets" -version = "0.52.6" +name = "wit-bindgen" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" [[package]] name = "yaml-rust" @@ -1055,3 +2206,9 @@ checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" dependencies = [ "linked-hash-map", ] + +[[package]] +name = "zmij" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd8f3f50b848df28f887acb68e41201b5aea6bc8a8dacc00fb40635ff9a72fea" diff --git a/Cargo.toml b/Cargo.toml index ab6ff76..4db63bb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,8 @@ log = { version = "0.4.27", features = [] } dirs = "6.0.0" syntect = "5.2.0" structopt = "0.3.26" +ratatui = "0.30.0" +anyhow = "1.0.100" [build-dependencies] # The link_qt_object_files feature is required for statically linking Qt 6. diff --git a/src/main.rs b/src/main.rs index 11efd35..bcef94b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -42,7 +42,7 @@ fn main() -> Result<(), Box> { true => gui::run(root_path), false => match args.tui { // Open the TUI editor if requested, otherwise use the QML GUI by default. - true => tui::run(root_path), + true => Ok(tui::start(root_path)?), false => { // Relaunch the CLIDE GUI in a separate process. Command::new(std::env::current_exe()?) diff --git a/src/tui.rs b/src/tui.rs index 2f3e4cd..0710a42 100644 --- a/src/tui.rs +++ b/src/tui.rs @@ -1,6 +1,42 @@ +use anyhow::{Context, Result}; +use ratatui::{DefaultTerminal, Frame}; use std::error::Error; +use std::time::Duration; +use ratatui::crossterm::event; +use ratatui::crossterm::event::{Event, KeyCode}; +use ratatui::widgets::Paragraph; -pub fn run(root_path: std::path::PathBuf) -> Result<(), Box> { +pub fn start(root_path: std::path::PathBuf) -> Result<()> { println!("Starting the TUI editor at {:?}", root_path); + let terminal = ratatui::init(); + let app_result = run(terminal, root_path).context("Failed to start the TUI editor."); + ratatui::restore(); + app_result +} + +pub fn run( + mut terminal: DefaultTerminal, + root_path: std::path::PathBuf, +) -> Result<()> { + loop { + terminal.draw(draw)?; + if should_quit()? { + break; + } + } Ok(()) } +fn should_quit() -> Result { + if event::poll(Duration::from_millis(250)).context("event poll failed")? { + if let Event::Key(key) = event::read().context("event read failed")? { + return Ok(KeyCode::Char('q') == key.code); + } + } + Ok(false) +} + +fn draw(frame: &mut Frame) { + let greeting = Paragraph::new("Hello World! (press 'q' to quit)"); + frame.render_widget(greeting, frame.area()); +} + -- 2.47.2 From fac6ea6bcdcaf1d30c27020738d30ab4aa54c87f Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Sat, 17 Jan 2026 14:04:02 -0500 Subject: [PATCH 33/73] Create App struct for TUI. --- README.md | 21 ++++++- src/main.rs | 10 ++-- src/tui.rs | 39 ++----------- src/tui/app.rs | 146 +++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 174 insertions(+), 42 deletions(-) create mode 100644 src/tui/app.rs diff --git a/README.md b/README.md index ad8d381..43c1f54 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,18 @@ # CLIDE -CLIDE is an IDE written in Rust that supports both full and headless Linux environments. +CLIDE is a barebones but extendable IDE written in Rust using the Qt UI framework that supports both full and headless Linux environments. +The core application will provide you with a text editor that can be extended with plugins written in Rust. + +The UI is written in QML and compiled to C++ using `cxx`, which is then linked into the Rust application. + +It's up to you to build your own development environment for your tools. +This project is intended to be a light-weight core application with no language-specific tools or features. +To add tools for your purposes, create a plugin that implements the `ClidePlugin` trait. (This is currently under development and not yet available.) +Once you've created your plugin, you can submit a pull request to add your plugin to the final section in this README if you'd like to contribute. +If this section becomes too large, we may explore other options to distribute plugins. The following packages must be installed before the application will build. +In the future, we may provide a minimal installation option that only includes dependencies for the headless TUI. ```bash sudo apt install qt6-base-dev qt6-declarative-dev qt6-tools-dev qml6-module-qtquick-controls qml6-module-qtquick-layouts qml6-module-qtquick-window qml6-module-qtqml-workerscript qml6-module-qtquick-templates qml6-module-qtquick qml6-module-qtquick-dialogs qt6-svg-dev @@ -22,8 +32,8 @@ The [Qt Installer](https://www.qt.io/download-qt-installer) will provide the lat If using RustRover be sure to set your QML binaries path in the settings menu. If Qt was installed to its default directory this will be `$HOME/Qt/6.8.3/gcc_64/bin/`. -Viewing documentation in the web browser is possible, but you will end up in a mess of tabs. -Using Qt Assistant is recommended. It comes with Qt6 when installed. Run the following command to start it. +Viewing documentation in the web browser is possible, but using Qt Assistant is recommended. +It comes with Qt6 when installed. Run the following command to start it. ```bash nohup $HOME/Qt/6.8.3/gcc_64/bin/assistant > /dev/null 2>&1 & @@ -58,3 +68,8 @@ Some helpful links for reading up on QML if you're just getting started. * [All QML Controls Types](https://doc.qt.io/qt-6/qtquick-controls-qmlmodule.html) * [KDAB CXX-Qt Book](https://kdab.github.io/cxx-qt/book/) * [github.com/KDAB/cxx-qt](https://github.com/KDAB/cxx-qt) + + +### Plugins + +TODO: Add a list of plugins here. The first example will be C++ with CMake functionality. diff --git a/src/main.rs b/src/main.rs index bcef94b..2d53646 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,8 +8,8 @@ pub mod gui; pub mod tui; /// Command line interface IDE with full GUI and headless modes. -/// If no flags are provided the GUI editor is launched in a separate process. -/// If no path is provided the current directory is used. +/// If no flags are provided, the GUI editor is launched in a separate process. +/// If no path is provided, the current directory is used. #[derive(StructOpt, Debug)] #[structopt(name = "clide", verbatim_doc_comment)] struct Cli { @@ -30,11 +30,11 @@ fn main() -> Result<(), Box> { let args = Cli::from_args(); let root_path = match args.path { - // If the CLI was provided a directory convert it to absolute. + // If the CLI was provided a directory, convert it to absolute. Some(path) => std::path::absolute(path)?, - // If no path was provided, use current directory. + // If no path was provided, use the current directory. None => std::env::current_dir().unwrap_or_else(|_| - // If we can't find the CWD attempt to open the home directory. + // If we can't find the CWD, attempt to open the home directory. dirs::home_dir().expect("Failed to access filesystem.")), }; diff --git a/src/tui.rs b/src/tui.rs index 0710a42..3ba419d 100644 --- a/src/tui.rs +++ b/src/tui.rs @@ -1,42 +1,13 @@ +pub mod app; + use anyhow::{Context, Result}; -use ratatui::{DefaultTerminal, Frame}; -use std::error::Error; -use std::time::Duration; -use ratatui::crossterm::event; -use ratatui::crossterm::event::{Event, KeyCode}; -use ratatui::widgets::Paragraph; pub fn start(root_path: std::path::PathBuf) -> Result<()> { println!("Starting the TUI editor at {:?}", root_path); let terminal = ratatui::init(); - let app_result = run(terminal, root_path).context("Failed to start the TUI editor."); + let app_result = app::App::new(&root_path) + .run(terminal) + .context("Failed to start the TUI editor."); ratatui::restore(); app_result } - -pub fn run( - mut terminal: DefaultTerminal, - root_path: std::path::PathBuf, -) -> Result<()> { - loop { - terminal.draw(draw)?; - if should_quit()? { - break; - } - } - Ok(()) -} -fn should_quit() -> Result { - if event::poll(Duration::from_millis(250)).context("event poll failed")? { - if let Event::Key(key) = event::read().context("event read failed")? { - return Ok(KeyCode::Char('q') == key.code); - } - } - Ok(false) -} - -fn draw(frame: &mut Frame) { - let greeting = Paragraph::new("Hello World! (press 'q' to quit)"); - frame.render_widget(greeting, frame.area()); -} - diff --git a/src/tui/app.rs b/src/tui/app.rs new file mode 100644 index 0000000..1d24d6a --- /dev/null +++ b/src/tui/app.rs @@ -0,0 +1,146 @@ +use anyhow::Context; +use ratatui::buffer::Buffer; +use ratatui::crossterm::event; +use ratatui::crossterm::event::{Event, KeyCode}; +use ratatui::layout::{Constraint, Direction, Layout, Rect}; +use ratatui::prelude::{Color, Style, Widget}; +use ratatui::widgets::{Block, Borders, Padding, Paragraph, Tabs, Wrap}; +use ratatui::{DefaultTerminal, symbols}; +use std::time::Duration; + +#[derive(Debug, Clone, Copy)] +pub struct App<'a> { + root_path: &'a std::path::Path, +} + +impl<'a> App<'a> { + pub(crate) fn new(root_path: &'a std::path::Path) -> Self { + Self { root_path } + } + + pub fn run(mut self, mut terminal: DefaultTerminal) -> anyhow::Result<()> { + loop { + terminal.draw(|f| f.render_widget(self, f.area()))?; + if self.should_quit()? { + break; + } + self.handle_events()?; + } + Ok(()) + } + + fn handle_events(&mut self) -> anyhow::Result<()> { + // Handle other keyboard events here, aside from quitting. + Ok(()) + } + + fn should_quit(self) -> anyhow::Result { + if event::poll(Duration::from_millis(250)).context("event poll failed")? { + if let Event::Key(key) = event::read().context("event read failed")? { + return Ok(KeyCode::Char('q') == key.code); + } + } + Ok(false) + } + + fn draw_status(self, area: Rect, buf: &mut Buffer) { + Tabs::new(["File", "Edit", "View", "Help"]) + .style(Style::default()) + .block(Block::default().borders(Borders::ALL)) + .render(area, buf); + } + + fn draw_tabs(self, area: Rect, buf: &mut Buffer) { + // TODO: Tabs should be opened from file explorer + Tabs::new(["file.md", "file.cpp"]) + .divider(symbols::DOT) + .block( + Block::default() + .borders(Borders::NONE) + .padding(Padding::new(0, 0, 0, 0)), + ) + .highlight_style(Style::default().fg(Color::LightRed)) + .render(area, buf); + } + + fn draw_editor(self, area: Rect, buf: &mut Buffer) { + // TODO: Title should be detected programming language name + // TODO: Content should be file contents + // TODO: Contents should use vim in rendered TTY + // TODO: Vimrc should be used + Paragraph::new("This is an example of the TUI interface (press 'q' to quit)") + .style(Style::default()) + .block( + Block::default() + .title("Rust") + .title_style(Style::default().fg(Color::Yellow)) + .borders(Borders::ALL) + .padding(Padding::new(0, 0, 0, 1)), + ) + .wrap(Wrap { trim: false }) + .render(area, buf); + } + + fn draw_terminal(self, area: Rect, buf: &mut Buffer) { + // TODO: Title should be detected shell name + // TODO: Contents should be shell output + Paragraph::new("Terminal placeholder") + .style(Style::default()) + .block( + Block::default() + .title("Bash") + .title_style(Style::default().fg(Color::DarkGray)) + .borders(Borders::ALL), + ) + .wrap(Wrap { trim: false }) + .render(area, buf); + } + + fn draw_file_explorer(self, area: Rect, buf: &mut Buffer) { + Paragraph::new("File explorer placeholder") + .style(Style::default()) + .block(Block::default().borders(Borders::ALL)) + .render(area, buf); + } +} + +// TODO: Separate complex components into their own widgets. +impl<'a> Widget for App<'a> { + fn render(self, area: Rect, buf: &mut Buffer) + where + Self: Sized, + { + let vertical = Layout::default() + .direction(Direction::Vertical) + .constraints([ + Constraint::Length(3), // status bar + Constraint::Percentage(70), // horizontal layout + Constraint::Percentage(30), // terminal + ]) + .split(area); + + let horizontal = Layout::default() + .direction(Direction::Horizontal) + .constraints([ + Constraint::Max(30), // File explorer with a max width of 30 characters. + Constraint::Fill(1), // Editor fills the remaining space. + ]) + .split(vertical[1]); + + let editor_layout = Layout::default() + .direction(Direction::Vertical) + .constraints([ + Constraint::Length(1), // Editor tabs. + Constraint::Fill(1), + ]) + .split(horizontal[1]); + + self.draw_status(vertical[0], buf); + self.draw_terminal(vertical[2], buf); + + self.draw_file_explorer(horizontal[0], buf); + + self.draw_tabs(editor_layout[0], buf); + self.draw_editor(editor_layout[1], buf); + } +} -- 2.47.2 From b65565adfa0ecb4454b17d6346f4b73233e58877 Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Sat, 17 Jan 2026 15:07:05 -0500 Subject: [PATCH 34/73] [tui] Add Explorer widget for left panel. --- Cargo.lock | 13 ++++++++++ Cargo.toml | 2 ++ src/tui.rs | 1 + src/tui/app.rs | 19 +++++++-------- src/tui/explorer.rs | 58 +++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 82 insertions(+), 11 deletions(-) create mode 100644 src/tui/explorer.rs diff --git a/Cargo.lock b/Cargo.lock index 1d9d0f3..86a6948 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -230,6 +230,8 @@ dependencies = [ "ratatui", "structopt", "syntect", + "tui-tree-widget", + "uuid", ] [[package]] @@ -1908,6 +1910,17 @@ dependencies = [ "time-core", ] +[[package]] +name = "tui-tree-widget" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "deca119555009eee2e0cfb9c020f39f632444dc4579918d5fc009d51d75dff92" +dependencies = [ + "ratatui-core", + "ratatui-widgets", + "unicode-width 0.2.2", +] + [[package]] name = "typenum" version = "1.19.0" diff --git a/Cargo.toml b/Cargo.toml index 4db63bb..0f88e17 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,8 @@ syntect = "5.2.0" structopt = "0.3.26" ratatui = "0.30.0" anyhow = "1.0.100" +tui-tree-widget = "0.24.0" +uuid = { version = "1.19.0", features = ["v4"] } [build-dependencies] # The link_qt_object_files feature is required for statically linking Qt 6. diff --git a/src/tui.rs b/src/tui.rs index 3ba419d..98229c0 100644 --- a/src/tui.rs +++ b/src/tui.rs @@ -1,4 +1,5 @@ pub mod app; +mod explorer; use anyhow::{Context, Result}; diff --git a/src/tui/app.rs b/src/tui/app.rs index 1d24d6a..7ee8b14 100644 --- a/src/tui/app.rs +++ b/src/tui/app.rs @@ -8,14 +8,18 @@ use ratatui::widgets::{Block, Borders, Padding, Paragraph, Tabs, Wrap}; use ratatui::{DefaultTerminal, symbols}; use std::time::Duration; +use crate::tui::explorer::Explorer; + #[derive(Debug, Clone, Copy)] pub struct App<'a> { - root_path: &'a std::path::Path, + explorer: Explorer<'a>, } impl<'a> App<'a> { pub(crate) fn new(root_path: &'a std::path::Path) -> Self { - Self { root_path } + Self { + explorer: Explorer::new(root_path), + } } pub fn run(mut self, mut terminal: DefaultTerminal) -> anyhow::Result<()> { @@ -84,7 +88,7 @@ impl<'a> App<'a> { fn draw_terminal(self, area: Rect, buf: &mut Buffer) { // TODO: Title should be detected shell name // TODO: Contents should be shell output - Paragraph::new("Terminal placeholder") + Paragraph::new("shaun@pc:~/Code/clide$ ") .style(Style::default()) .block( Block::default() @@ -95,13 +99,6 @@ impl<'a> App<'a> { .wrap(Wrap { trim: false }) .render(area, buf); } - - fn draw_file_explorer(self, area: Rect, buf: &mut Buffer) { - Paragraph::new("File explorer placeholder") - .style(Style::default()) - .block(Block::default().borders(Borders::ALL)) - .render(area, buf); - } } // TODO: Separate complex components into their own widgets. @@ -138,7 +135,7 @@ impl<'a> Widget for App<'a> { self.draw_status(vertical[0], buf); self.draw_terminal(vertical[2], buf); - self.draw_file_explorer(horizontal[0], buf); + self.explorer.draw(horizontal[0], buf); self.draw_tabs(editor_layout[0], buf); self.draw_editor(editor_layout[1], buf); diff --git a/src/tui/explorer.rs b/src/tui/explorer.rs new file mode 100644 index 0000000..5be7736 --- /dev/null +++ b/src/tui/explorer.rs @@ -0,0 +1,58 @@ +use std::fs; +use ratatui::buffer::Buffer; +use ratatui::layout::Rect; +use ratatui::prelude::Style; +use ratatui::widgets::{Block, Borders, Widget}; +use tui_tree_widget::{Tree, TreeItem}; +use uuid::Uuid; + +#[derive(Clone, Copy, Debug)] +pub struct Explorer<'a> { + root_path: &'a std::path::Path, +} + +impl<'a> Explorer<'a> { + pub fn new(path: &'a std::path::Path) -> Self { + Explorer { root_path: path } + } + + pub fn draw(self, area: Rect, buf: &mut Buffer) { + let tree_item = Self::build_tree_from_path(self.root_path.to_path_buf()); + Tree::new(&tree_item.children()) + .expect("Failed to build tree.") + .style(Style::default()) + .block(Block::default().borders(Borders::ALL)) + .render(area, buf); + } + + fn build_tree_from_path(path: std::path::PathBuf) -> TreeItem<'static, String> { + let mut children = vec![]; + if let Ok(entries) = fs::read_dir(&path) { + let mut paths = entries + .map(|res| res.map(|e| e.path())) + .collect::, std::io::Error>>() + .expect(""); + paths.sort(); + for path in paths { + if path.is_dir() { + children.push(Self::build_tree_from_path(path)); + } else { + children.push(TreeItem::new_leaf( + Uuid::new_v4().to_string(), + path.file_name().unwrap().to_string_lossy().to_string(), + )); + } + } + } + + TreeItem::new( + Uuid::new_v4().to_string(), + path.file_name() + .unwrap_or_default() + .to_string_lossy() + .to_string(), + children, + ) + .expect("Failed to build tree from path.") + } +} -- 2.47.2 From 733a43ccde9e6852245fcaefb456db938bd5eaf9 Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Sat, 17 Jan 2026 17:09:42 -0500 Subject: [PATCH 35/73] [tui] Add basic component trait. --- src/main.rs | 1 - src/tui.rs | 1 + src/tui/app.rs | 54 ++++++++++++++++++-------------------------- src/tui/component.rs | 40 ++++++++++++++++++++++++++++++++ src/tui/explorer.rs | 36 +++++++++++++++++++---------- 5 files changed, 87 insertions(+), 45 deletions(-) create mode 100644 src/tui/component.rs diff --git a/src/main.rs b/src/main.rs index 2d53646..b3be5c8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,7 +6,6 @@ use structopt::StructOpt; pub mod gui; pub mod tui; - /// Command line interface IDE with full GUI and headless modes. /// If no flags are provided, the GUI editor is launched in a separate process. /// If no path is provided, the current directory is used. diff --git a/src/tui.rs b/src/tui.rs index 98229c0..32a106e 100644 --- a/src/tui.rs +++ b/src/tui.rs @@ -1,4 +1,5 @@ pub mod app; +mod component; mod explorer; use anyhow::{Context, Result}; diff --git a/src/tui/app.rs b/src/tui/app.rs index 7ee8b14..33a4443 100644 --- a/src/tui/app.rs +++ b/src/tui/app.rs @@ -1,16 +1,12 @@ -use anyhow::Context; +use crate::tui::component::{Action, ClideComponent}; +use crate::tui::explorer::Explorer; use ratatui::buffer::Buffer; -use ratatui::crossterm::event; -use ratatui::crossterm::event::{Event, KeyCode}; use ratatui::layout::{Constraint, Direction, Layout, Rect}; use ratatui::prelude::{Color, Style, Widget}; use ratatui::widgets::{Block, Borders, Padding, Paragraph, Tabs, Wrap}; use ratatui::{DefaultTerminal, symbols}; -use std::time::Duration; -use crate::tui::explorer::Explorer; - -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone)] pub struct App<'a> { explorer: Explorer<'a>, } @@ -24,37 +20,29 @@ impl<'a> App<'a> { pub fn run(mut self, mut terminal: DefaultTerminal) -> anyhow::Result<()> { loop { - terminal.draw(|f| f.render_widget(self, f.area()))?; - if self.should_quit()? { - break; + terminal.draw(|f| { + f.render_widget(&self, f.area()); + })?; + + // TODO: Handle events based on which component is active. + // match self.explorer.handle_events() { ... } + match self.handle_events() { + Action::Quit => break, + _ => {} } - self.handle_events()?; } Ok(()) } - fn handle_events(&mut self) -> anyhow::Result<()> { - // Handle other keyboard events here, aside from quitting. - Ok(()) - } - - fn should_quit(self) -> anyhow::Result { - if event::poll(Duration::from_millis(250)).context("event poll failed")? { - if let Event::Key(key) = event::read().context("event read failed")? { - return Ok(KeyCode::Char('q') == key.code); - } - } - Ok(false) - } - - fn draw_status(self, area: Rect, buf: &mut Buffer) { + fn draw_status(&self, area: Rect, buf: &mut Buffer) { + // TODO: Status bar should have drop down menus Tabs::new(["File", "Edit", "View", "Help"]) .style(Style::default()) .block(Block::default().borders(Borders::ALL)) .render(area, buf); } - fn draw_tabs(self, area: Rect, buf: &mut Buffer) { + fn draw_tabs(&self, area: Rect, buf: &mut Buffer) { // TODO: Tabs should be opened from file explorer Tabs::new(["file.md", "file.cpp"]) .divider(symbols::DOT) @@ -67,7 +55,7 @@ impl<'a> App<'a> { .render(area, buf); } - fn draw_editor(self, area: Rect, buf: &mut Buffer) { + fn draw_editor(&self, area: Rect, buf: &mut Buffer) { // TODO: Title should be detected programming language name // TODO: Content should be file contents // TODO: Contents should use vim in rendered TTY @@ -85,7 +73,7 @@ impl<'a> App<'a> { .render(area, buf); } - fn draw_terminal(self, area: Rect, buf: &mut Buffer) { + fn draw_terminal(&self, area: Rect, buf: &mut Buffer) { // TODO: Title should be detected shell name // TODO: Contents should be shell output Paragraph::new("shaun@pc:~/Code/clide$ ") @@ -102,7 +90,7 @@ impl<'a> App<'a> { } // TODO: Separate complex components into their own widgets. -impl<'a> Widget for App<'a> { +impl<'a> Widget for &App<'a> { fn render(self, area: Rect, buf: &mut Buffer) where Self: Sized, @@ -128,16 +116,18 @@ impl<'a> Widget for App<'a> { .direction(Direction::Vertical) .constraints([ Constraint::Length(1), // Editor tabs. - Constraint::Fill(1), + Constraint::Fill(1), // Editor contents. ]) .split(horizontal[1]); self.draw_status(vertical[0], buf); self.draw_terminal(vertical[2], buf); - self.explorer.draw(horizontal[0], buf); + self.explorer.render(horizontal[0], buf); self.draw_tabs(editor_layout[0], buf); self.draw_editor(editor_layout[1], buf); } } + +impl<'a> ClideComponent for App<'a> {} diff --git a/src/tui/component.rs b/src/tui/component.rs new file mode 100644 index 0000000..7f13d86 --- /dev/null +++ b/src/tui/component.rs @@ -0,0 +1,40 @@ +use ratatui::crossterm::event; +use ratatui::crossterm::event::{Event, KeyCode, KeyEvent, MouseEvent}; +use std::time::Duration; + +pub enum Action { + Noop, + Quit, +} + +pub trait ClideComponent { + fn handle_events(&mut self) -> Action { + if !event::poll(Duration::from_millis(250)).expect("event poll failed") { + return Action::Noop; + } + + let key_event = event::read().expect("event read failed"); + match key_event { + Event::Key(key_event) => self.handle_key_events(key_event), + Event::Mouse(mouse_event) => self.handle_mouse_events(mouse_event), + _ => Action::Noop, + } + } + + fn handle_key_events(&mut self, key: KeyEvent) -> Action { + match key.code { + KeyCode::Char('q') => Action::Quit, + _ => Action::Noop, + } + } + + fn handle_mouse_events(&mut self, mouse: MouseEvent) -> Action { + Action::Noop + } + + fn update(&mut self, action: Action) -> Action { + Action::Noop + } + + // fn render(&mut self, area: Rect, buf: &mut Buffer); +} diff --git a/src/tui/explorer.rs b/src/tui/explorer.rs index 5be7736..76a37e5 100644 --- a/src/tui/explorer.rs +++ b/src/tui/explorer.rs @@ -1,29 +1,29 @@ -use std::fs; +use crate::tui::component::ClideComponent; +use anyhow::Result; use ratatui::buffer::Buffer; use ratatui::layout::Rect; use ratatui::prelude::Style; use ratatui::widgets::{Block, Borders, Widget}; +use std::fs; use tui_tree_widget::{Tree, TreeItem}; use uuid::Uuid; -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Debug)] pub struct Explorer<'a> { root_path: &'a std::path::Path, + tree_items: TreeItem<'a, String>, } impl<'a> Explorer<'a> { pub fn new(path: &'a std::path::Path) -> Self { - Explorer { root_path: path } + let mut explorer = Explorer { + root_path: path, + tree_items: Self::build_tree_from_path(path.into()), + }; + explorer } - pub fn draw(self, area: Rect, buf: &mut Buffer) { - let tree_item = Self::build_tree_from_path(self.root_path.to_path_buf()); - Tree::new(&tree_item.children()) - .expect("Failed to build tree.") - .style(Style::default()) - .block(Block::default().borders(Borders::ALL)) - .render(area, buf); - } + pub fn draw(&self, area: Rect, buf: &mut Buffer) {} fn build_tree_from_path(path: std::path::PathBuf) -> TreeItem<'static, String> { let mut children = vec![]; @@ -53,6 +53,18 @@ impl<'a> Explorer<'a> { .to_string(), children, ) - .expect("Failed to build tree from path.") + .expect("Failed to build tree from path.") } } + +impl<'a> Widget for &Explorer<'a> { + fn render(self, area: Rect, buf: &mut Buffer) { + Tree::new(&self.tree_items.children()) + .expect("Failed to build tree.") + .style(Style::default()) + .block(Block::default().borders(Borders::ALL)) + .render(area, buf); + } +} + +impl<'a> ClideComponent for Explorer<'a> {} -- 2.47.2 From b35b98743b8697f0b3c83bda7f6a71842e1dbfbc Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Sat, 17 Jan 2026 17:39:13 -0500 Subject: [PATCH 36/73] [tui] Clean up Border titles. --- src/tui/app.rs | 3 ++- src/tui/component.rs | 2 -- src/tui/explorer.rs | 16 ++++++++++++++-- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/tui/app.rs b/src/tui/app.rs index 33a4443..fa761b1 100644 --- a/src/tui/app.rs +++ b/src/tui/app.rs @@ -1,7 +1,7 @@ use crate::tui::component::{Action, ClideComponent}; use crate::tui::explorer::Explorer; use ratatui::buffer::Buffer; -use ratatui::layout::{Constraint, Direction, Layout, Rect}; +use ratatui::layout::{Alignment, Constraint, Direction, Layout, Rect}; use ratatui::prelude::{Color, Style, Widget}; use ratatui::widgets::{Block, Borders, Padding, Paragraph, Tabs, Wrap}; use ratatui::{DefaultTerminal, symbols}; @@ -66,6 +66,7 @@ impl<'a> App<'a> { Block::default() .title("Rust") .title_style(Style::default().fg(Color::Yellow)) + .title_alignment(Alignment::Right) .borders(Borders::ALL) .padding(Padding::new(0, 0, 0, 1)), ) diff --git a/src/tui/component.rs b/src/tui/component.rs index 7f13d86..c2bc1b7 100644 --- a/src/tui/component.rs +++ b/src/tui/component.rs @@ -35,6 +35,4 @@ pub trait ClideComponent { fn update(&mut self, action: Action) -> Action { Action::Noop } - - // fn render(&mut self, area: Rect, buf: &mut Buffer); } diff --git a/src/tui/explorer.rs b/src/tui/explorer.rs index 76a37e5..63f7be0 100644 --- a/src/tui/explorer.rs +++ b/src/tui/explorer.rs @@ -1,8 +1,9 @@ use crate::tui::component::ClideComponent; use anyhow::Result; use ratatui::buffer::Buffer; -use ratatui::layout::Rect; +use ratatui::layout::{Alignment, Rect}; use ratatui::prelude::Style; +use ratatui::style::Color; use ratatui::widgets::{Block, Borders, Widget}; use std::fs; use tui_tree_widget::{Tree, TreeItem}; @@ -62,7 +63,18 @@ impl<'a> Widget for &Explorer<'a> { Tree::new(&self.tree_items.children()) .expect("Failed to build tree.") .style(Style::default()) - .block(Block::default().borders(Borders::ALL)) + .block( + Block::default() + .borders(Borders::ALL) + .title( + self.root_path + .file_name() + .expect("Failed to get file name from path.") + .to_string_lossy(), + ) + .title_style(Style::default().fg(Color::Green)) + .title_alignment(Alignment::Center), + ) .render(area, buf); } } -- 2.47.2 From a8de77f3705da597cc3f1c03aa594d676fdffc5b Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Sat, 17 Jan 2026 19:21:14 -0500 Subject: [PATCH 37/73] [tui] WIP neovim editor. --- Cargo.lock | 339 +++++++++++++++++++++++++++++++++++++++++++++- Cargo.toml | 2 + src/tui.rs | 1 + src/tui/editor.rs | 156 +++++++++++++++++++++ 4 files changed, 493 insertions(+), 5 deletions(-) create mode 100644 src/tui/editor.rs diff --git a/Cargo.lock b/Cargo.lock index 86a6948..3ac7f69 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -44,6 +44,17 @@ version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + [[package]] name = "atomic" version = "0.6.1" @@ -133,6 +144,28 @@ version = "1.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "206fdffcfa2df7cbe15601ef46c813fce0965eb3286db6b56c583b814b51c81c" +dependencies = [ + "byteorder", + "iovec", +] + +[[package]] +name = "bytes" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" + [[package]] name = "castaway" version = "0.2.4" @@ -227,9 +260,11 @@ dependencies = [ "cxx-qt-lib", "dirs", "log", + "nvim-rs", "ratatui", "structopt", "syntect", + "tokio", "tui-tree-widget", "uuid", ] @@ -595,7 +630,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -626,7 +661,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -699,6 +734,103 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" +[[package]] +name = "futures" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a471a38ef8ed83cd6e40aa59c1ffe17db6855c18e3604d9c4ed8c08ebc28678" + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures 0.1.31", + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", + "tokio-io", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -811,6 +943,15 @@ dependencies = [ "syn 2.0.114", ] +[[package]] +name = "iovec" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" +dependencies = [ + "libc", +] + [[package]] name = "itertools" version = "0.13.0" @@ -1010,7 +1151,7 @@ dependencies = [ "libc", "log", "wasi", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -1071,6 +1212,21 @@ dependencies = [ "libc", ] +[[package]] +name = "nvim-rs" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "010294fd782a554d4b9b17608305f7498809e857a6ed7a3e858588ad8e5691f5" +dependencies = [ + "async-trait", + "futures 0.3.31", + "log", + "rmp", + "rmpv", + "tokio", + "tokio-util", +] + [[package]] name = "once_cell" version = "1.21.3" @@ -1232,6 +1388,18 @@ dependencies = [ "siphasher", ] +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + [[package]] name = "pkg-config" version = "0.3.32" @@ -1480,6 +1648,24 @@ version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" +[[package]] +name = "rmp" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ba8be72d372b2c9b35542551678538b562e7cf86c3315773cae48dfbfe7790c" +dependencies = [ + "num-traits", +] + +[[package]] +name = "rmpv" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a4e1d4b9b938a26d2996af33229f0ca0956c652c1375067f0b45291c1df8417" +dependencies = [ + "rmp", +] + [[package]] name = "rustc_version" version = "0.4.1" @@ -1499,7 +1685,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -1644,12 +1830,28 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" +[[package]] +name = "slab" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" + [[package]] name = "smallvec" version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +[[package]] +name = "socket2" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" +dependencies = [ + "libc", + "windows-sys 0.60.2", +] + [[package]] name = "static_assertions" version = "1.1.0" @@ -1910,6 +2112,59 @@ dependencies = [ "time-core", ] +[[package]] +name = "tokio" +version = "1.49.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" +dependencies = [ + "bytes 1.11.0", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-io" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57fc868aae093479e3131e3d165c93b1c7474109d13c90ec0dda2a1bbfff0674" +dependencies = [ + "bytes 0.4.12", + "futures 0.1.31", + "log", +] + +[[package]] +name = "tokio-macros" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes 1.11.0", + "futures-core", + "futures-io", + "futures-sink", + "pin-project-lite", + "tokio", +] + [[package]] name = "tui-tree-widget" version = "0.24.0" @@ -2181,7 +2436,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -2196,6 +2451,15 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-sys" version = "0.61.2" @@ -2205,6 +2469,71 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + [[package]] name = "wit-bindgen" version = "0.51.0" diff --git a/Cargo.toml b/Cargo.toml index 0f88e17..f50d4c7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,8 @@ ratatui = "0.30.0" anyhow = "1.0.100" tui-tree-widget = "0.24.0" uuid = { version = "1.19.0", features = ["v4"] } +nvim-rs = { version = "0.9.2", features = ["use_tokio"] } +tokio = { version = "1.49.0", features = ["rt-multi-thread", "macros", "process"] } [build-dependencies] # The link_qt_object_files feature is required for statically linking Qt 6. diff --git a/src/tui.rs b/src/tui.rs index 32a106e..cfafaf9 100644 --- a/src/tui.rs +++ b/src/tui.rs @@ -1,6 +1,7 @@ pub mod app; mod component; mod explorer; +mod editor; use anyhow::{Context, Result}; diff --git a/src/tui/editor.rs b/src/tui/editor.rs new file mode 100644 index 0000000..66939d3 --- /dev/null +++ b/src/tui/editor.rs @@ -0,0 +1,156 @@ +use crate::tui::component::ClideComponent; +use anyhow::Result; +use nvim_rs::compat::tokio::Compat; +use nvim_rs::{Handler, Neovim, UiAttachOptions, Value}; +use ratatui::buffer::Buffer; +use ratatui::layout::Rect; +use ratatui::text::Line; +use ratatui::widgets::{Paragraph, Widget}; +use std::process::Stdio; +use std::sync::{Arc, Mutex}; +use tokio::process::Command; + +struct Editor { + ui: Arc, + height: usize, + width: usize, +} + +impl Editor { + fn new(height: usize, width: usize) -> Self { + let editor = Editor { + ui: Arc::new(NvimUI::default()), + height, + width, + }; + editor + .ui + .grid + .lock() + .unwrap() + .resize(height, vec![' '; width]); + editor + } +} + +impl<'a> Widget for &Editor { + fn render(self, area: Rect, buf: &mut Buffer) + where + Self: Sized, + { + let grid = self.ui.grid.lock().unwrap(); + + let lines: Vec = grid + .iter() + .map(|row| Line::from(row.iter().collect::())) + .collect(); + + Paragraph::new(lines).render(area, buf); + } +} + +impl ClideComponent for Editor {} + +#[derive(Default, Clone)] +pub struct NvimUI { + pub grid: Arc>>>, + pub cursor: Arc>, +} + +impl Handler for NvimUI { + type Writer = Compat; + + async fn handle_notify( + &self, + _name: String, + _args: Vec, + _neovim: Neovim>, + ) -> Result<()> { + if _name != "redraw" { + return Ok(()); + } + + for event in _args { + if let Value::Array(items) = event { + if items.is_empty() { + continue; + } + + let event_name = items[0].as_str().unwrap_or(""); + + match event_name { + "grid_line" => self.handle_grid_line(&items), + "cursor_goto" => self.handle_cursor(&items), + _ => {} + } + } + } + Ok(()) + } +} + +impl NvimUI { + fn handle_grid_line(&self, items: &[Value]) { + // ["grid_line", grid, row, col, cells] + let row = items[2].as_u64().unwrap() as usize; + let col = items[3].as_u64().unwrap() as usize; + + let cells = items[4].as_array().unwrap(); + + let mut grid = self.grid.lock().unwrap(); + let mut c = col; + + for cell in cells { + let cell_arr = cell.as_array().unwrap(); + let text = cell_arr[0].as_str().unwrap(); + let repeat = cell_arr.get(2).and_then(|v| v.as_u64()).unwrap_or(1); + + for _ in 0..repeat { + if let Some(ch) = text.chars().next() { + grid[row][c] = ch; + } + c += 1; + } + } + } + + fn handle_cursor(&self, items: &[Value]) { + let row = items[2].as_u64().unwrap() as usize; + let col = items[3].as_u64().unwrap() as usize; + *self.cursor.lock().unwrap() = (row, col); + } + + pub async fn spawn_nvim( + handler: H, + ) -> Result>> { + let mut child = Command::new("nvim") + .arg("--embed") + .arg("--headless") + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .spawn()?; + + let stdin = child.stdin.take().unwrap(); + let mut stdout = child.stdout.take().unwrap(); + + let (nvim, io_handler) = Neovim::new(stdout, stdin, handler); + + tokio::spawn(async move { + nvim_rs::compat::tokio::spawn(stdout.compat(), io_handler).await; + }); + + Ok(nvim) + } + + pub async fn init_ui( + nvim: &Neovim>, + w: i64, + h: i64, + ) -> anyhow::Result<()> { + let mut opts = UiAttachOptions::default(); + opts.set_rgb(true).set_linegrid_external(true); + + nvim.ui_attach(w, h, &opts).await?; + Ok(()) + } +} -- 2.47.2 From fe6390c1cd72a262f5d3a87178bc0302d84cf0a0 Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Sun, 18 Jan 2026 10:09:28 -0500 Subject: [PATCH 38/73] [tui] Add edtui editor for basic vim emulation. --- Cargo.lock | 538 +++++++++++++++++++++++++++---------------- Cargo.toml | 2 +- src/main.rs | 2 +- src/tui.rs | 60 ++++- src/tui/app.rs | 83 ++++--- src/tui/component.rs | 35 +-- src/tui/editor.rs | 181 ++++----------- src/tui/explorer.rs | 4 +- 8 files changed, 494 insertions(+), 411 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3ac7f69..e88454e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -45,14 +45,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" [[package]] -name = "async-trait" -version = "0.1.89" +name = "arboard" +version = "3.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +checksum = "0348a1c054491f4bfe6ab86a7b6ab1e44e45d899005de92f58b3df180b36ddaf" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.114", + "clipboard-win", + "image", + "log", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation", + "parking_lot", + "percent-encoding", + "windows-sys 0.60.2", + "x11rb", ] [[package]] @@ -145,20 +154,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" [[package]] -name = "byteorder" -version = "1.5.0" +name = "byteorder-lite" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - -[[package]] -name = "bytes" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "206fdffcfa2df7cbe15601ef46c813fce0965eb3286db6b56c583b814b51c81c" -dependencies = [ - "byteorder", - "iovec", -] +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" [[package]] name = "bytes" @@ -259,8 +258,8 @@ dependencies = [ "cxx-qt-build", "cxx-qt-lib", "dirs", + "edtui", "log", - "nvim-rs", "ratatui", "structopt", "syntect", @@ -269,6 +268,15 @@ dependencies = [ "uuid", ] +[[package]] +name = "clipboard-win" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bde03770d3df201d4fb868f2c9c59e66a3e4e2bd06692a0fe701e7103c7e84d4" +dependencies = [ + "error-code", +] + [[package]] name = "codespan-reporting" version = "0.11.1" @@ -367,6 +375,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + [[package]] name = "crypto-common" version = "0.1.7" @@ -633,6 +647,16 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "dispatch2" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" +dependencies = [ + "bitflags 2.10.0", + "objc2", +] + [[package]] name = "document-features" version = "0.2.12" @@ -642,12 +666,47 @@ dependencies = [ "litrs", ] +[[package]] +name = "edtui" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "417b85aa75bedb1da51eeed2d7a9241a061ddc6a0212e80057968ba34256fad8" +dependencies = [ + "arboard", + "crossterm", + "edtui-jagged", + "enum_dispatch", + "once_cell", + "ratatui-core", + "ratatui-widgets", + "syntect", + "unicode-width 0.2.2", +] + +[[package]] +name = "edtui-jagged" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6818b2d6b8b3da52f7491b6331e27d45ae34e5baaffeb1edfde43911fe63dd6" + [[package]] name = "either" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +[[package]] +name = "enum_dispatch" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa18ce2bc66555b3218614519ac839ddb759a7d6720732f979ef8d13be147ecd" +dependencies = [ + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.114", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -664,6 +723,12 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "error-code" +version = "3.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59" + [[package]] name = "euclid" version = "0.22.11" @@ -683,6 +748,35 @@ dependencies = [ "regex", ] +[[package]] +name = "fax" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05de7d48f37cd6730705cbca900770cab77a89f413d23e100ad7fad7795a0ab" +dependencies = [ + "fax_derive", +] + +[[package]] +name = "fax_derive" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0aca10fb742cb43f9e7bb8467c91aa9bcb8e3ffbc6a6f7389bb93ffc920577d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + [[package]] name = "filedescriptor" version = "0.8.3" @@ -734,103 +828,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" -[[package]] -name = "futures" -version = "0.1.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a471a38ef8ed83cd6e40aa59c1ffe17db6855c18e3604d9c4ed8c08ebc28678" - -[[package]] -name = "futures" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-channel" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" -dependencies = [ - "futures-core", - "futures-sink", -] - -[[package]] -name = "futures-core" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" - -[[package]] -name = "futures-executor" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-io" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" - -[[package]] -name = "futures-macro" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.114", -] - -[[package]] -name = "futures-sink" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" - -[[package]] -name = "futures-task" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" - -[[package]] -name = "futures-util" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" -dependencies = [ - "futures 0.1.31", - "futures-channel", - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "pin-utils", - "slab", - "tokio-io", -] - [[package]] name = "generic-array" version = "0.14.7" @@ -841,6 +838,16 @@ dependencies = [ "version_check", ] +[[package]] +name = "gethostname" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8" +dependencies = [ + "rustix", + "windows-link", +] + [[package]] name = "getrandom" version = "0.2.17" @@ -864,6 +871,17 @@ dependencies = [ "wasip2", ] +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "zerocopy", +] + [[package]] name = "hashbrown" version = "0.16.1" @@ -911,6 +929,20 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" +[[package]] +name = "image" +version = "0.25.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6506c6c10786659413faa717ceebcb8f70731c0a60cbae39795fdf114519c1a" +dependencies = [ + "bytemuck", + "byteorder-lite", + "moxcms", + "num-traits", + "png", + "tiff", +] + [[package]] name = "indexmap" version = "2.13.0" @@ -943,15 +975,6 @@ dependencies = [ "syn 2.0.114", ] -[[package]] -name = "iovec" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" -dependencies = [ - "libc", -] - [[package]] name = "itertools" version = "0.13.0" @@ -1154,6 +1177,16 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "moxcms" +version = "0.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac9557c559cd6fc9867e122e20d2cbefc9ca29d80d027a8e39310920ed2f0a97" +dependencies = [ + "num-traits", + "pxfm", +] + [[package]] name = "nix" version = "0.29.0" @@ -1213,18 +1246,76 @@ dependencies = [ ] [[package]] -name = "nvim-rs" -version = "0.9.2" +name = "objc2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "010294fd782a554d4b9b17608305f7498809e857a6ed7a3e858588ad8e5691f5" +checksum = "b7c2599ce0ec54857b29ce62166b0ed9b4f6f1a70ccc9a71165b6154caca8c05" dependencies = [ - "async-trait", - "futures 0.3.31", - "log", - "rmp", - "rmpv", - "tokio", - "tokio-util", + "objc2-encode", +] + +[[package]] +name = "objc2-app-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" +dependencies = [ + "bitflags 2.10.0", + "objc2", + "objc2-core-graphics", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" +dependencies = [ + "bitflags 2.10.0", + "dispatch2", + "objc2", +] + +[[package]] +name = "objc2-core-graphics" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" +dependencies = [ + "bitflags 2.10.0", + "dispatch2", + "objc2", + "objc2-core-foundation", + "objc2-io-surface", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" +dependencies = [ + "bitflags 2.10.0", + "objc2", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-io-surface" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" +dependencies = [ + "bitflags 2.10.0", + "objc2", + "objc2-core-foundation", ] [[package]] @@ -1293,6 +1384,12 @@ dependencies = [ "windows-link", ] +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + [[package]] name = "pest" version = "2.8.5" @@ -1394,12 +1491,6 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - [[package]] name = "pkg-config" version = "0.3.32" @@ -1419,6 +1510,19 @@ dependencies = [ "time", ] +[[package]] +name = "png" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97baced388464909d42d89643fe4361939af9b7ce7a31ee32a168f832a70f2a0" +dependencies = [ + "bitflags 2.10.0", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + [[package]] name = "portable-atomic" version = "1.13.0" @@ -1464,6 +1568,15 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "pxfm" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7186d3822593aa4393561d186d1393b3923e9d6163d3fbfd6e825e3e6cf3e6a8" +dependencies = [ + "num-traits", +] + [[package]] name = "qt-build-utils" version = "0.7.3" @@ -1475,6 +1588,12 @@ dependencies = [ "versions", ] +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + [[package]] name = "quick-xml" version = "0.38.4" @@ -1648,24 +1767,6 @@ version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" -[[package]] -name = "rmp" -version = "0.8.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ba8be72d372b2c9b35542551678538b562e7cf86c3315773cae48dfbfe7790c" -dependencies = [ - "num-traits", -] - -[[package]] -name = "rmpv" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a4e1d4b9b938a26d2996af33229f0ca0956c652c1375067f0b45291c1df8417" -dependencies = [ - "rmp", -] - [[package]] name = "rustc_version" version = "0.4.1" @@ -1830,28 +1931,12 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" -[[package]] -name = "slab" -version = "0.4.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" - [[package]] name = "smallvec" version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" -[[package]] -name = "socket2" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" -dependencies = [ - "libc", - "windows-sys 0.60.2", -] - [[package]] name = "static_assertions" version = "1.1.0" @@ -2079,6 +2164,20 @@ dependencies = [ "syn 2.0.114", ] +[[package]] +name = "tiff" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af9605de7fee8d9551863fd692cce7637f548dbd9db9180fcc07ccc6d26c336f" +dependencies = [ + "fax", + "flate2", + "half", + "quick-error", + "weezl", + "zune-jpeg", +] + [[package]] name = "time" version = "0.3.45" @@ -2118,28 +2217,15 @@ version = "1.49.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" dependencies = [ - "bytes 1.11.0", + "bytes", "libc", "mio", - "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2", "tokio-macros", "windows-sys 0.61.2", ] -[[package]] -name = "tokio-io" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57fc868aae093479e3131e3d165c93b1c7474109d13c90ec0dda2a1bbfff0674" -dependencies = [ - "bytes 0.4.12", - "futures 0.1.31", - "log", -] - [[package]] name = "tokio-macros" version = "2.6.0" @@ -2151,20 +2237,6 @@ dependencies = [ "syn 2.0.114", ] -[[package]] -name = "tokio-util" -version = "0.7.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" -dependencies = [ - "bytes 1.11.0", - "futures-core", - "futures-io", - "futures-sink", - "pin-project-lite", - "tokio", -] - [[package]] name = "tui-tree-widget" version = "0.24.0" @@ -2342,6 +2414,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "weezl" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" + [[package]] name = "wezterm-bidi" version = "0.2.3" @@ -2540,6 +2618,23 @@ version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +[[package]] +name = "x11rb" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9993aa5be5a26815fe2c3eacfc1fde061fc1a1f094bf1ad2a18bf9c495dd7414" +dependencies = [ + "gethostname", + "rustix", + "x11rb-protocol", +] + +[[package]] +name = "x11rb-protocol" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd" + [[package]] name = "yaml-rust" version = "0.4.5" @@ -2549,8 +2644,43 @@ dependencies = [ "linked-hash-map", ] +[[package]] +name = "zerocopy" +version = "0.8.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "668f5168d10b9ee831de31933dc111a459c97ec93225beb307aed970d1372dfd" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c7962b26b0a8685668b671ee4b54d007a67d4eaf05fda79ac0ecf41e32270f1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + [[package]] name = "zmij" version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd8f3f50b848df28f887acb68e41201b5aea6bc8a8dacc00fb40635ff9a72fea" + +[[package]] +name = "zune-core" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" + +[[package]] +name = "zune-jpeg" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29ce2c8a9384ad323cf564b67da86e21d3cfdff87908bc1223ed5c99bc792713" +dependencies = [ + "zune-core", +] diff --git a/Cargo.toml b/Cargo.toml index f50d4c7..e4801f3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,8 +15,8 @@ ratatui = "0.30.0" anyhow = "1.0.100" tui-tree-widget = "0.24.0" uuid = { version = "1.19.0", features = ["v4"] } -nvim-rs = { version = "0.9.2", features = ["use_tokio"] } tokio = { version = "1.49.0", features = ["rt-multi-thread", "macros", "process"] } +edtui = "0.11.0" [build-dependencies] # The link_qt_object_files feature is required for statically linking Qt 6. diff --git a/src/main.rs b/src/main.rs index b3be5c8..cc84b45 100644 --- a/src/main.rs +++ b/src/main.rs @@ -41,7 +41,7 @@ fn main() -> Result<(), Box> { true => gui::run(root_path), false => match args.tui { // Open the TUI editor if requested, otherwise use the QML GUI by default. - true => Ok(tui::start(root_path)?), + true => Ok(tui::Tui::new(root_path).start()?), false => { // Relaunch the CLIDE GUI in a separate process. Command::new(std::env::current_exe()?) diff --git a/src/tui.rs b/src/tui.rs index cfafaf9..ecb2ce7 100644 --- a/src/tui.rs +++ b/src/tui.rs @@ -1,16 +1,58 @@ pub mod app; mod component; -mod explorer; mod editor; +mod explorer; use anyhow::{Context, Result}; +use ratatui::Terminal; +use ratatui::backend::CrosstermBackend; +use ratatui::crossterm::event::{ + DisableBracketedPaste, DisableMouseCapture, EnableBracketedPaste, EnableMouseCapture, +}; +use ratatui::crossterm::terminal::{ + EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode, +}; +use std::io::{Stdout, stdout}; -pub fn start(root_path: std::path::PathBuf) -> Result<()> { - println!("Starting the TUI editor at {:?}", root_path); - let terminal = ratatui::init(); - let app_result = app::App::new(&root_path) - .run(terminal) - .context("Failed to start the TUI editor."); - ratatui::restore(); - app_result +pub struct Tui { + terminal: Terminal>, + root_path: std::path::PathBuf, +} + +impl Tui { + pub fn new(root_path: std::path::PathBuf) -> Self { + Self { + terminal: Terminal::new(CrosstermBackend::new(stdout())) + .expect("Failed to initialize terminal"), + root_path, + } + } + + pub fn start(self) -> Result<()> { + println!("Starting the TUI editor at {:?}", self.root_path); + ratatui::crossterm::execute!( + stdout(), + EnterAlternateScreen, + EnableMouseCapture, + EnableBracketedPaste + )?; + enable_raw_mode()?; + + let app_result = app::App::new(&self.root_path) + .run(self.terminal) + .context("Failed to start the TUI editor."); + Self::stop()?; + app_result + } + + fn stop() -> Result<()> { + disable_raw_mode()?; + ratatui::crossterm::execute!( + stdout(), + LeaveAlternateScreen, + DisableMouseCapture, + DisableBracketedPaste + )?; + Ok(()) + } } diff --git a/src/tui/app.rs b/src/tui/app.rs index fa761b1..08d74c7 100644 --- a/src/tui/app.rs +++ b/src/tui/app.rs @@ -1,34 +1,63 @@ use crate::tui::component::{Action, ClideComponent}; +use crate::tui::editor::Editor; use crate::tui::explorer::Explorer; use ratatui::buffer::Buffer; -use ratatui::layout::{Alignment, Constraint, Direction, Layout, Rect}; +use ratatui::crossterm::event; +use ratatui::crossterm::event::{Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers}; +use ratatui::layout::{Constraint, Direction, Layout, Rect}; use ratatui::prelude::{Color, Style, Widget}; use ratatui::widgets::{Block, Borders, Padding, Paragraph, Tabs, Wrap}; use ratatui::{DefaultTerminal, symbols}; +use std::time::Duration; -#[derive(Debug, Clone)] pub struct App<'a> { explorer: Explorer<'a>, + editor: Editor, } impl<'a> App<'a> { pub(crate) fn new(root_path: &'a std::path::Path) -> Self { Self { explorer: Explorer::new(root_path), + editor: Editor::new(), } } + fn get_event(&mut self) -> Option { + if !event::poll(Duration::from_millis(250)).expect("event poll failed") { + return None; + } + + event::read().ok() + } + pub fn run(mut self, mut terminal: DefaultTerminal) -> anyhow::Result<()> { loop { terminal.draw(|f| { - f.render_widget(&self, f.area()); + f.render_widget(&mut self, f.area()); })?; // TODO: Handle events based on which component is active. - // match self.explorer.handle_events() { ... } - match self.handle_events() { - Action::Quit => break, - _ => {} + if let Some(event) = self.get_event() { + self.editor + .event_handler + .on_event(event.clone(), &mut self.editor.state); + + match event { + Event::FocusGained => {} + Event::FocusLost => {} + Event::Key(key_event) => { + // Handle main application key events. + match self.handle_key_events(key_event) { + Action::Noop => {} + Action::Quit => break, + Action::Pass => {} + } + } + Event::Mouse(_) => {} + Event::Paste(_) => {} + Event::Resize(_, _) => {} + } } } Ok(()) @@ -55,25 +84,6 @@ impl<'a> App<'a> { .render(area, buf); } - fn draw_editor(&self, area: Rect, buf: &mut Buffer) { - // TODO: Title should be detected programming language name - // TODO: Content should be file contents - // TODO: Contents should use vim in rendered TTY - // TODO: Vimrc should be used - Paragraph::new("This is an example of the TUI interface (press 'q' to quit)") - .style(Style::default()) - .block( - Block::default() - .title("Rust") - .title_style(Style::default().fg(Color::Yellow)) - .title_alignment(Alignment::Right) - .borders(Borders::ALL) - .padding(Padding::new(0, 0, 0, 1)), - ) - .wrap(Wrap { trim: false }) - .render(area, buf); - } - fn draw_terminal(&self, area: Rect, buf: &mut Buffer) { // TODO: Title should be detected shell name // TODO: Contents should be shell output @@ -91,7 +101,7 @@ impl<'a> App<'a> { } // TODO: Separate complex components into their own widgets. -impl<'a> Widget for &App<'a> { +impl<'a> Widget for &mut App<'a> { fn render(self, area: Rect, buf: &mut Buffer) where Self: Sized, @@ -127,8 +137,23 @@ impl<'a> Widget for &App<'a> { self.explorer.render(horizontal[0], buf); self.draw_tabs(editor_layout[0], buf); - self.draw_editor(editor_layout[1], buf); + self.editor.render(editor_layout[1], buf); } } -impl<'a> ClideComponent for App<'a> {} +impl<'a> ClideComponent for App<'a> { + fn handle_key_events(&mut self, key: KeyEvent) -> Action { + match key { + KeyEvent { + code: KeyCode::Char('c'), + modifiers: KeyModifiers::CONTROL, + kind: KeyEventKind::Press, + state: _state, + } => Action::Quit, + key_event => { + // Pass the key event to each component that can handle it. + self.explorer.handle_key_events(key_event) + } + } + } +} diff --git a/src/tui/component.rs b/src/tui/component.rs index c2bc1b7..0906154 100644 --- a/src/tui/component.rs +++ b/src/tui/component.rs @@ -1,38 +1,23 @@ -use ratatui::crossterm::event; -use ratatui::crossterm::event::{Event, KeyCode, KeyEvent, MouseEvent}; -use std::time::Duration; +use ratatui::crossterm::event::{KeyEvent, MouseEvent}; pub enum Action { Noop, Quit, + Pass, // Pass input to another component. } pub trait ClideComponent { - fn handle_events(&mut self) -> Action { - if !event::poll(Duration::from_millis(250)).expect("event poll failed") { - return Action::Noop; - } - - let key_event = event::read().expect("event read failed"); - match key_event { - Event::Key(key_event) => self.handle_key_events(key_event), - Event::Mouse(mouse_event) => self.handle_mouse_events(mouse_event), - _ => Action::Noop, - } - } - - fn handle_key_events(&mut self, key: KeyEvent) -> Action { - match key.code { - KeyCode::Char('q') => Action::Quit, - _ => Action::Noop, - } - } - - fn handle_mouse_events(&mut self, mouse: MouseEvent) -> Action { + fn handle_key_events(&mut self, _key: KeyEvent) -> Action { Action::Noop } - fn update(&mut self, action: Action) -> Action { + #[allow(dead_code)] + fn handle_mouse_events(&mut self, _mouse: MouseEvent) -> Action { + Action::Noop + } + + #[allow(dead_code)] + fn update(&mut self, _action: Action) -> Action { Action::Noop } } diff --git a/src/tui/editor.rs b/src/tui/editor.rs index 66939d3..9657184 100644 --- a/src/tui/editor.rs +++ b/src/tui/editor.rs @@ -1,156 +1,59 @@ -use crate::tui::component::ClideComponent; -use anyhow::Result; -use nvim_rs::compat::tokio::Compat; -use nvim_rs::{Handler, Neovim, UiAttachOptions, Value}; +use crate::tui::component::{Action, ClideComponent}; +use edtui::{ + EditorEventHandler, EditorState, EditorTheme, EditorView, LineNumbers, SyntaxHighlighter, +}; use ratatui::buffer::Buffer; -use ratatui::layout::Rect; -use ratatui::text::Line; -use ratatui::widgets::{Paragraph, Widget}; -use std::process::Stdio; -use std::sync::{Arc, Mutex}; -use tokio::process::Command; +use ratatui::crossterm::event::KeyEvent; +use ratatui::layout::{Alignment, Rect}; +use ratatui::prelude::{Color, Style}; +use ratatui::widgets::{Block, Borders, Padding, Widget}; -struct Editor { - ui: Arc, - height: usize, - width: usize, +// TODO: Consider using editor-command https://docs.rs/editor-command/latest/editor_command/ +// TODO: Title should be detected programming language name +// TODO: Content should be file contents +// TODO: Vimrc should be used +pub struct Editor { + pub state: EditorState, + pub event_handler: EditorEventHandler, } impl Editor { - fn new(height: usize, width: usize) -> Self { - let editor = Editor { - ui: Arc::new(NvimUI::default()), - height, - width, - }; - editor - .ui - .grid - .lock() - .unwrap() - .resize(height, vec![' '; width]); - editor + pub fn new() -> Self { + Editor { + state: EditorState::default(), + event_handler: EditorEventHandler::default(), + } } } -impl<'a> Widget for &Editor { +impl Widget for &mut Editor { fn render(self, area: Rect, buf: &mut Buffer) where Self: Sized, { - let grid = self.ui.grid.lock().unwrap(); - - let lines: Vec = grid - .iter() - .map(|row| Line::from(row.iter().collect::())) - .collect(); - - Paragraph::new(lines).render(area, buf); + // TODO: Use current file extension for syntax highlighting here. + EditorView::new(&mut self.state) + .wrap(true) + .theme( + EditorTheme::default().block( + Block::default() + .title("Rust") + .title_style(Style::default().fg(Color::Yellow)) + .title_alignment(Alignment::Right) + .borders(Borders::ALL) + .padding(Padding::new(0, 0, 0, 1)), + ), + ) + .syntax_highlighter(SyntaxHighlighter::new("dracula", "rs").ok()) + .tab_width(2) + .line_numbers(LineNumbers::Absolute) + .render(area, buf); } } -impl ClideComponent for Editor {} - -#[derive(Default, Clone)] -pub struct NvimUI { - pub grid: Arc>>>, - pub cursor: Arc>, -} - -impl Handler for NvimUI { - type Writer = Compat; - - async fn handle_notify( - &self, - _name: String, - _args: Vec, - _neovim: Neovim>, - ) -> Result<()> { - if _name != "redraw" { - return Ok(()); - } - - for event in _args { - if let Value::Array(items) = event { - if items.is_empty() { - continue; - } - - let event_name = items[0].as_str().unwrap_or(""); - - match event_name { - "grid_line" => self.handle_grid_line(&items), - "cursor_goto" => self.handle_cursor(&items), - _ => {} - } - } - } - Ok(()) - } -} - -impl NvimUI { - fn handle_grid_line(&self, items: &[Value]) { - // ["grid_line", grid, row, col, cells] - let row = items[2].as_u64().unwrap() as usize; - let col = items[3].as_u64().unwrap() as usize; - - let cells = items[4].as_array().unwrap(); - - let mut grid = self.grid.lock().unwrap(); - let mut c = col; - - for cell in cells { - let cell_arr = cell.as_array().unwrap(); - let text = cell_arr[0].as_str().unwrap(); - let repeat = cell_arr.get(2).and_then(|v| v.as_u64()).unwrap_or(1); - - for _ in 0..repeat { - if let Some(ch) = text.chars().next() { - grid[row][c] = ch; - } - c += 1; - } - } - } - - fn handle_cursor(&self, items: &[Value]) { - let row = items[2].as_u64().unwrap() as usize; - let col = items[3].as_u64().unwrap() as usize; - *self.cursor.lock().unwrap() = (row, col); - } - - pub async fn spawn_nvim( - handler: H, - ) -> Result>> { - let mut child = Command::new("nvim") - .arg("--embed") - .arg("--headless") - .stdin(Stdio::piped()) - .stdout(Stdio::piped()) - .spawn()?; - - let stdin = child.stdin.take().unwrap(); - let mut stdout = child.stdout.take().unwrap(); - - let (nvim, io_handler) = Neovim::new(stdout, stdin, handler); - - tokio::spawn(async move { - nvim_rs::compat::tokio::spawn(stdout.compat(), io_handler).await; - }); - - Ok(nvim) - } - - pub async fn init_ui( - nvim: &Neovim>, - w: i64, - h: i64, - ) -> anyhow::Result<()> { - let mut opts = UiAttachOptions::default(); - opts.set_rgb(true).set_linegrid_external(true); - - nvim.ui_attach(w, h, &opts).await?; - Ok(()) +impl ClideComponent for Editor { + fn handle_key_events(&mut self, key: KeyEvent) -> Action { + self.event_handler.on_key_event(key, &mut self.state); + Action::Pass } } diff --git a/src/tui/explorer.rs b/src/tui/explorer.rs index 63f7be0..37f2bee 100644 --- a/src/tui/explorer.rs +++ b/src/tui/explorer.rs @@ -17,15 +17,13 @@ pub struct Explorer<'a> { impl<'a> Explorer<'a> { pub fn new(path: &'a std::path::Path) -> Self { - let mut explorer = Explorer { + let explorer = Explorer { root_path: path, tree_items: Self::build_tree_from_path(path.into()), }; explorer } - pub fn draw(&self, area: Rect, buf: &mut Buffer) {} - fn build_tree_from_path(path: std::path::PathBuf) -> TreeItem<'static, String> { let mut children = vec![]; if let Ok(entries) = fs::read_dir(&path) { -- 2.47.2 From ce6c12f068503075e5573171cb40f37bd1251faa Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Sun, 18 Jan 2026 11:02:41 -0500 Subject: [PATCH 39/73] [tui] Move default input logic into ClideComponent. --- Cargo.lock | 47 ++++---------------------------------------- Cargo.toml | 1 - src/tui/app.rs | 19 ++++-------------- src/tui/component.rs | 9 ++++++++- 4 files changed, 16 insertions(+), 60 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e88454e..238dbd9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -159,12 +159,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" -[[package]] -name = "bytes" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" - [[package]] name = "castaway" version = "0.2.4" @@ -263,7 +257,6 @@ dependencies = [ "ratatui", "structopt", "syntect", - "tokio", "tui-tree-widget", "uuid", ] @@ -668,9 +661,9 @@ dependencies = [ [[package]] name = "edtui" -version = "0.11.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "417b85aa75bedb1da51eeed2d7a9241a061ddc6a0212e80057968ba34256fad8" +checksum = "e49905ece098e793ca21a019598e9efc9a66459ad1d76bd7619e771a42dae2fc" dependencies = [ "arboard", "crossterm", @@ -1485,12 +1478,6 @@ dependencies = [ "siphasher", ] -[[package]] -name = "pin-project-lite" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" - [[package]] name = "pkg-config" version = "0.3.32" @@ -2211,32 +2198,6 @@ dependencies = [ "time-core", ] -[[package]] -name = "tokio" -version = "1.49.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" -dependencies = [ - "bytes", - "libc", - "mio", - "pin-project-lite", - "signal-hook-registry", - "tokio-macros", - "windows-sys 0.61.2", -] - -[[package]] -name = "tokio-macros" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.114", -] - [[package]] name = "tui-tree-widget" version = "0.24.0" @@ -2666,9 +2627,9 @@ dependencies = [ [[package]] name = "zmij" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd8f3f50b848df28f887acb68e41201b5aea6bc8a8dacc00fb40635ff9a72fea" +checksum = "94f63c051f4fe3c1509da62131a678643c5b6fbdc9273b2b79d4378ebda003d2" [[package]] name = "zune-core" diff --git a/Cargo.toml b/Cargo.toml index e4801f3..6c6ad8a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,6 @@ ratatui = "0.30.0" anyhow = "1.0.100" tui-tree-widget = "0.24.0" uuid = { version = "1.19.0", features = ["v4"] } -tokio = { version = "1.49.0", features = ["rt-multi-thread", "macros", "process"] } edtui = "0.11.0" [build-dependencies] diff --git a/src/tui/app.rs b/src/tui/app.rs index 08d74c7..2d7af4e 100644 --- a/src/tui/app.rs +++ b/src/tui/app.rs @@ -42,21 +42,10 @@ impl<'a> App<'a> { self.editor .event_handler .on_event(event.clone(), &mut self.editor.state); - - match event { - Event::FocusGained => {} - Event::FocusLost => {} - Event::Key(key_event) => { - // Handle main application key events. - match self.handle_key_events(key_event) { - Action::Noop => {} - Action::Quit => break, - Action::Pass => {} - } - } - Event::Mouse(_) => {} - Event::Paste(_) => {} - Event::Resize(_, _) => {} + match self.handle_event(event) { + Action::Noop => {} + Action::Quit => break, + Action::Pass => {} } } } diff --git a/src/tui/component.rs b/src/tui/component.rs index 0906154..6ec0289 100644 --- a/src/tui/component.rs +++ b/src/tui/component.rs @@ -1,4 +1,4 @@ -use ratatui::crossterm::event::{KeyEvent, MouseEvent}; +use ratatui::crossterm::event::{Event, KeyEvent, MouseEvent}; pub enum Action { Noop, @@ -7,6 +7,13 @@ pub enum Action { } pub trait ClideComponent { + fn handle_event(&mut self, event: Event) -> Action { + match event { + Event::Key(key_event) => self.handle_key_events(key_event), + _ => Action::Noop, + } + } + fn handle_key_events(&mut self, _key: KeyEvent) -> Action { Action::Noop } -- 2.47.2 From 507a4d8651f3c451dcf958fe40ca382b2e3ffd28 Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Mon, 19 Jan 2026 09:23:12 -0500 Subject: [PATCH 40/73] [tui] Cleanup and renames. --- Cargo.lock | 1 + Cargo.toml | 5 +++-- src/tui.rs | 4 ++-- src/tui/app.rs | 6 +++--- src/tui/component.rs | 12 ++++++------ src/tui/editor.rs | 4 ++-- src/tui/explorer.rs | 19 +++++++++++-------- 7 files changed, 28 insertions(+), 23 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 238dbd9..b73c783 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -256,6 +256,7 @@ dependencies = [ "log", "ratatui", "structopt", + "strum", "syntect", "tui-tree-widget", "uuid", diff --git a/Cargo.toml b/Cargo.toml index 6c6ad8a..8fc100c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,8 +15,9 @@ ratatui = "0.30.0" anyhow = "1.0.100" tui-tree-widget = "0.24.0" uuid = { version = "1.19.0", features = ["v4"] } -edtui = "0.11.0" +edtui = "0.11.1" +strum = "0.27.2" [build-dependencies] # The link_qt_object_files feature is required for statically linking Qt 6. -cxx-qt-build = { version = "0.7", features = [ "link_qt_object_files" ] } \ No newline at end of file +cxx-qt-build = { version = "0.7", features = [ "link_qt_object_files" ] } diff --git a/src/tui.rs b/src/tui.rs index ecb2ce7..d10ff05 100644 --- a/src/tui.rs +++ b/src/tui.rs @@ -1,4 +1,4 @@ -pub mod app; +mod app; mod component; mod editor; mod explorer; @@ -38,7 +38,7 @@ impl Tui { )?; enable_raw_mode()?; - let app_result = app::App::new(&self.root_path) + let app_result = app::App::new(self.root_path) .run(self.terminal) .context("Failed to start the TUI editor."); Self::stop()?; diff --git a/src/tui/app.rs b/src/tui/app.rs index 2d7af4e..47bd6b5 100644 --- a/src/tui/app.rs +++ b/src/tui/app.rs @@ -1,4 +1,4 @@ -use crate::tui::component::{Action, ClideComponent}; +use crate::tui::component::{Action, Component}; use crate::tui::editor::Editor; use crate::tui::explorer::Explorer; use ratatui::buffer::Buffer; @@ -16,7 +16,7 @@ pub struct App<'a> { } impl<'a> App<'a> { - pub(crate) fn new(root_path: &'a std::path::Path) -> Self { + pub(crate) fn new(root_path: std::path::PathBuf) -> Self { Self { explorer: Explorer::new(root_path), editor: Editor::new(), @@ -130,7 +130,7 @@ impl<'a> Widget for &mut App<'a> { } } -impl<'a> ClideComponent for App<'a> { +impl<'a> Component for App<'a> { fn handle_key_events(&mut self, key: KeyEvent) -> Action { match key { KeyEvent { diff --git a/src/tui/component.rs b/src/tui/component.rs index 6ec0289..1ba4d60 100644 --- a/src/tui/component.rs +++ b/src/tui/component.rs @@ -1,3 +1,5 @@ +#![allow(dead_code, unused_variables)] + use ratatui::crossterm::event::{Event, KeyEvent, MouseEvent}; pub enum Action { @@ -6,7 +8,7 @@ pub enum Action { Pass, // Pass input to another component. } -pub trait ClideComponent { +pub trait Component { fn handle_event(&mut self, event: Event) -> Action { match event { Event::Key(key_event) => self.handle_key_events(key_event), @@ -14,17 +16,15 @@ pub trait ClideComponent { } } - fn handle_key_events(&mut self, _key: KeyEvent) -> Action { + fn handle_key_events(&mut self, key: KeyEvent) -> Action { Action::Noop } - #[allow(dead_code)] - fn handle_mouse_events(&mut self, _mouse: MouseEvent) -> Action { + fn handle_mouse_events(&mut self, mouse: MouseEvent) -> Action { Action::Noop } - #[allow(dead_code)] - fn update(&mut self, _action: Action) -> Action { + fn update(&mut self, action: Action) -> Action { Action::Noop } } diff --git a/src/tui/editor.rs b/src/tui/editor.rs index 9657184..dfa88f2 100644 --- a/src/tui/editor.rs +++ b/src/tui/editor.rs @@ -1,4 +1,4 @@ -use crate::tui::component::{Action, ClideComponent}; +use crate::tui::component::{Action, Component}; use edtui::{ EditorEventHandler, EditorState, EditorTheme, EditorView, LineNumbers, SyntaxHighlighter, }; @@ -51,7 +51,7 @@ impl Widget for &mut Editor { } } -impl ClideComponent for Editor { +impl Component for Editor { fn handle_key_events(&mut self, key: KeyEvent) -> Action { self.event_handler.on_key_event(key, &mut self.state); Action::Pass diff --git a/src/tui/explorer.rs b/src/tui/explorer.rs index 37f2bee..e934cee 100644 --- a/src/tui/explorer.rs +++ b/src/tui/explorer.rs @@ -1,4 +1,4 @@ -use crate::tui::component::ClideComponent; +use crate::tui::component::Component; use anyhow::Result; use ratatui::buffer::Buffer; use ratatui::layout::{Alignment, Rect}; @@ -11,15 +11,15 @@ use uuid::Uuid; #[derive(Clone, Debug)] pub struct Explorer<'a> { - root_path: &'a std::path::Path, + root_path: std::path::PathBuf, tree_items: TreeItem<'a, String>, } impl<'a> Explorer<'a> { - pub fn new(path: &'a std::path::Path) -> Self { + pub fn new(path: std::path::PathBuf) -> Self { let explorer = Explorer { - root_path: path, - tree_items: Self::build_tree_from_path(path.into()), + root_path: path.to_owned(), + tree_items: Self::build_tree_from_path(path), }; explorer } @@ -38,7 +38,10 @@ impl<'a> Explorer<'a> { } else { children.push(TreeItem::new_leaf( Uuid::new_v4().to_string(), - path.file_name().unwrap().to_string_lossy().to_string(), + path.file_name() + .expect("Failed to get file name from path.") + .to_string_lossy() + .to_string(), )); } } @@ -47,7 +50,7 @@ impl<'a> Explorer<'a> { TreeItem::new( Uuid::new_v4().to_string(), path.file_name() - .unwrap_or_default() + .expect("Failed to get file name from path.") .to_string_lossy() .to_string(), children, @@ -77,4 +80,4 @@ impl<'a> Widget for &Explorer<'a> { } } -impl<'a> ClideComponent for Explorer<'a> {} +impl<'a> Component for Explorer<'a> {} -- 2.47.2 From f10d4cd41de915b4bf762df7ca728bf98f19fff3 Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Mon, 19 Jan 2026 15:03:50 -0500 Subject: [PATCH 41/73] [tui] Allow saving file with CTRL+S. + Improved event handling in general. --- src/tui/app.rs | 58 ++++++++++++++++++++++++++++++++------- src/tui/component.rs | 20 ++++++++++++-- src/tui/editor.rs | 64 ++++++++++++++++++++++++++++++++++++++------ src/tui/explorer.rs | 10 ++++--- 4 files changed, 130 insertions(+), 22 deletions(-) diff --git a/src/tui/app.rs b/src/tui/app.rs index 47bd6b5..ec70957 100644 --- a/src/tui/app.rs +++ b/src/tui/app.rs @@ -17,10 +17,14 @@ pub struct App<'a> { impl<'a> App<'a> { pub(crate) fn new(root_path: std::path::PathBuf) -> Self { - Self { - explorer: Explorer::new(root_path), + let mut app = Self { + explorer: Explorer::new(&root_path), editor: Editor::new(), - } + }; + app.editor + .set_contents(&root_path.join("src/tui/app.rs")) + .expect("Failed to set editor contents."); + app } fn get_event(&mut self) -> Option { @@ -33,19 +37,18 @@ impl<'a> App<'a> { pub fn run(mut self, mut terminal: DefaultTerminal) -> anyhow::Result<()> { loop { + // TODO: Handle events based on which component is active. terminal.draw(|f| { f.render_widget(&mut self, f.area()); })?; - // TODO: Handle events based on which component is active. if let Some(event) = self.get_event() { - self.editor - .event_handler - .on_event(event.clone(), &mut self.editor.state); match self.handle_event(event) { - Action::Noop => {} Action::Quit => break, - Action::Pass => {} + Action::Handled => {} + _ => { + // panic!("Unhandled event: {:?}", event); + } } } } @@ -131,6 +134,43 @@ impl<'a> Widget for &mut App<'a> { } impl<'a> Component for App<'a> { + fn id(&self) -> &str { + "app" + } + + /// TODO: Get active widget with some Component trait function helper? + /// trait Component { fn get_state() -> ComponentState; } + /// if component.get_state() = ComponentState::Active { component.handle_event(); } + /// + /// App could then provide helpers for altering Component state based on TUI grouping.. + /// (such as editor tabs, file explorer, status bars, etc..) + fn handle_event(&mut self, event: Event) -> Action { + // Handle events in the primary application. + if let Some(key_event) = event.as_key_event() { + match self.handle_key_events(key_event) { + Action::Quit => return Action::Quit, + Action::Handled => { + dbg!(format!("Handled event: {:?}", self.id())); + return Action::Handled; + } + _ => {} + } + } + self.editor.handle_event(event); + + // Handle events for all components. + // for component in &mut self.components { + // dbg!(format!("Handling event: {:?}", component.id())); + // // Actions returned here abort the input handling iteration. + // match component.handle_event(event.clone()) { + // Action::Quit => return Action::Quit, + // Action::Handled => return Action::Handled, + // _ => continue, + // } + // } + Action::Noop + } + fn handle_key_events(&mut self, key: KeyEvent) -> Action { match key { KeyEvent { diff --git a/src/tui/component.rs b/src/tui/component.rs index 1ba4d60..4af5b70 100644 --- a/src/tui/component.rs +++ b/src/tui/component.rs @@ -3,12 +3,28 @@ use ratatui::crossterm::event::{Event, KeyEvent, MouseEvent}; pub enum Action { - Noop, + /// Exit the application. Quit, - Pass, // Pass input to another component. + + /// The input was checked by the Component and had no effect. + Noop, + + /// Pass input to another component or external handler. + /// Similar to Noop with the added context that externally handled input may have had an impact. + Pass, + + /// Save the current file. + Save, + + /// The input was handled by a Component and should not be passed to the next component. + Handled, } pub trait Component { + /// Returns a unique identifier for the component. + /// This is used for lookup in a container of Components. + fn id(&self) -> &str; + fn handle_event(&mut self, event: Event) -> Action { match event { Event::Key(key_event) => self.handle_key_events(key_event), diff --git a/src/tui/editor.rs b/src/tui/editor.rs index dfa88f2..580043a 100644 --- a/src/tui/editor.rs +++ b/src/tui/editor.rs @@ -1,9 +1,11 @@ use crate::tui::component::{Action, Component}; + +use anyhow::Result; use edtui::{ - EditorEventHandler, EditorState, EditorTheme, EditorView, LineNumbers, SyntaxHighlighter, + EditorEventHandler, EditorState, EditorTheme, EditorView, LineNumbers, Lines, SyntaxHighlighter, }; use ratatui::buffer::Buffer; -use ratatui::crossterm::event::KeyEvent; +use ratatui::crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers}; use ratatui::layout::{Alignment, Rect}; use ratatui::prelude::{Color, Style}; use ratatui::widgets::{Block, Borders, Padding, Widget}; @@ -15,6 +17,7 @@ use ratatui::widgets::{Block, Borders, Padding, Widget}; pub struct Editor { pub state: EditorState, pub event_handler: EditorEventHandler, + file_path: Option, } impl Editor { @@ -22,15 +25,32 @@ impl Editor { Editor { state: EditorState::default(), event_handler: EditorEventHandler::default(), + file_path: None, } } + + pub fn set_contents(&mut self, path: &std::path::PathBuf) -> Result<()> { + let contents = std::fs::read_to_string(path) + .expect(&format!("Failed to read file contents: {}", path.display())); + let lines: Vec<_> = contents + .lines() + .map(|line| line.chars().collect::>()) + .collect(); + self.file_path = Some(path.clone()); + self.state.lines = Lines::new(lines); + Ok(()) + } + + pub fn save(&self) -> Result<()> { + if let Some(path) = &self.file_path { + return std::fs::write(path, self.state.lines.to_string()).map_err(|e| e.into()); + }; + Err(anyhow::anyhow!("File not saved. No file path set.")) + } } impl Widget for &mut Editor { - fn render(self, area: Rect, buf: &mut Buffer) - where - Self: Sized, - { + fn render(self, area: Rect, buf: &mut Buffer) { // TODO: Use current file extension for syntax highlighting here. EditorView::new(&mut self.state) .wrap(true) @@ -52,8 +72,36 @@ impl Widget for &mut Editor { } impl Component for Editor { - fn handle_key_events(&mut self, key: KeyEvent) -> Action { - self.event_handler.on_key_event(key, &mut self.state); + fn id(&self) -> &str { + "editor" + } + + fn handle_event(&mut self, event: Event) -> Action { + if let Some(key_event) = event.as_key_event() { + // Handle events here that should not be passed on to the vim emulation handler. + match self.handle_key_events(key_event) { + Action::Handled => return Action::Handled, + _ => {} + } + } + self.event_handler.on_event(event, &mut self.state); Action::Pass } + + /// The events for the vim emulation should be handled by EditorEventHandler::on_event. + /// These events are custom to the clide application. + fn handle_key_events(&mut self, key: KeyEvent) -> Action { + match key { + KeyEvent { + code: KeyCode::Char('s'), + modifiers: KeyModifiers::CONTROL, + .. + } => { + self.save().expect("Failed to save file."); + Action::Handled + } + // For other events not handled here, pass to the vim emulation handler. + _ => Action::Noop, + } + } } diff --git a/src/tui/explorer.rs b/src/tui/explorer.rs index e934cee..a8aec73 100644 --- a/src/tui/explorer.rs +++ b/src/tui/explorer.rs @@ -16,10 +16,10 @@ pub struct Explorer<'a> { } impl<'a> Explorer<'a> { - pub fn new(path: std::path::PathBuf) -> Self { + pub fn new(path: &std::path::PathBuf) -> Self { let explorer = Explorer { root_path: path.to_owned(), - tree_items: Self::build_tree_from_path(path), + tree_items: Self::build_tree_from_path(path.to_owned()), }; explorer } @@ -80,4 +80,8 @@ impl<'a> Widget for &Explorer<'a> { } } -impl<'a> Component for Explorer<'a> {} +impl<'a> Component for Explorer<'a> { + fn id(&self) -> &str { + "explorer" + } +} -- 2.47.2 From e65eb20048d23226a0ce97a27c144b3a28a0daac Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Mon, 19 Jan 2026 17:41:46 -0500 Subject: [PATCH 42/73] [tui] File explorer controls editor contents. --- src/tui/app.rs | 15 ++++- src/tui/editor.rs | 21 ++++--- src/tui/explorer.rs | 145 +++++++++++++++++++++++++++++++++----------- 3 files changed, 135 insertions(+), 46 deletions(-) diff --git a/src/tui/app.rs b/src/tui/app.rs index ec70957..90e37c3 100644 --- a/src/tui/app.rs +++ b/src/tui/app.rs @@ -129,6 +129,16 @@ impl<'a> Widget for &mut App<'a> { self.explorer.render(horizontal[0], buf); self.draw_tabs(editor_layout[0], buf); + + if let Some(editor) = self.editor.file_path.clone() { + let editor_abs = std::path::absolute(editor).unwrap(); + if let Some(selected) = self.explorer.selected() { + let selected_abs = std::path::absolute(selected).unwrap(); + if selected_abs.is_file() && selected_abs != editor_abs { + self.editor.set_contents(&selected_abs).ok(); + } + } + } self.editor.render(editor_layout[1], buf); } } @@ -150,13 +160,14 @@ impl<'a> Component for App<'a> { match self.handle_key_events(key_event) { Action::Quit => return Action::Quit, Action::Handled => { - dbg!(format!("Handled event: {:?}", self.id())); + // dbg!(format!("Handled event: {:?}", self.id())); return Action::Handled; } _ => {} } } - self.editor.handle_event(event); + self.explorer.handle_event(event.clone()); + self.editor.handle_event(event.clone()); // Handle events for all components. // for component in &mut self.components { diff --git a/src/tui/editor.rs b/src/tui/editor.rs index 580043a..2ff4f8c 100644 --- a/src/tui/editor.rs +++ b/src/tui/editor.rs @@ -17,7 +17,7 @@ use ratatui::widgets::{Block, Borders, Padding, Widget}; pub struct Editor { pub state: EditorState, pub event_handler: EditorEventHandler, - file_path: Option, + pub file_path: Option, } impl Editor { @@ -30,15 +30,16 @@ impl Editor { } pub fn set_contents(&mut self, path: &std::path::PathBuf) -> Result<()> { - let contents = std::fs::read_to_string(path) - .expect(&format!("Failed to read file contents: {}", path.display())); - let lines: Vec<_> = contents - .lines() - .map(|line| line.chars().collect::>()) - .collect(); - self.file_path = Some(path.clone()); - self.state.lines = Lines::new(lines); - Ok(()) + if let Ok(contents) = std::fs::read_to_string(path) { + let lines: Vec<_> = contents + .lines() + .map(|line| line.chars().collect::>()) + .collect(); + self.file_path = Some(path.clone()); + self.state.lines = Lines::new(lines); + return Ok(()); + } + Err(anyhow::Error::msg("Failed to set editor file contents")) } pub fn save(&self) -> Result<()> { diff --git a/src/tui/explorer.rs b/src/tui/explorer.rs index a8aec73..0c843b1 100644 --- a/src/tui/explorer.rs +++ b/src/tui/explorer.rs @@ -1,18 +1,19 @@ -use crate::tui::component::Component; +use crate::tui::component::{Action, Component}; use anyhow::Result; use ratatui::buffer::Buffer; -use ratatui::layout::{Alignment, Rect}; +use ratatui::crossterm::event::{Event, KeyCode, KeyEvent, MouseEvent, MouseEventKind}; +use ratatui::layout::{Alignment, Position, Rect}; use ratatui::prelude::Style; -use ratatui::style::Color; -use ratatui::widgets::{Block, Borders, Widget}; +use ratatui::style::{Color, Modifier}; +use ratatui::widgets::{Block, Borders, StatefulWidget}; use std::fs; -use tui_tree_widget::{Tree, TreeItem}; -use uuid::Uuid; +use tui_tree_widget::{Tree, TreeItem, TreeState}; -#[derive(Clone, Debug)] +#[derive(Debug)] pub struct Explorer<'a> { root_path: std::path::PathBuf, tree_items: TreeItem<'a, String>, + tree_state: TreeState, } impl<'a> Explorer<'a> { @@ -20,6 +21,7 @@ impl<'a> Explorer<'a> { let explorer = Explorer { root_path: path.to_owned(), tree_items: Self::build_tree_from_path(path.to_owned()), + tree_state: TreeState::default(), }; explorer } @@ -36,19 +38,32 @@ impl<'a> Explorer<'a> { if path.is_dir() { children.push(Self::build_tree_from_path(path)); } else { - children.push(TreeItem::new_leaf( - Uuid::new_v4().to_string(), - path.file_name() - .expect("Failed to get file name from path.") - .to_string_lossy() - .to_string(), - )); + if let Ok(path) = std::path::absolute(&path) { + let path_str = path.to_string_lossy().to_string(); + children.push(TreeItem::new_leaf( + path_str, + path.file_name() + .expect("Failed to get file name from path.") + .to_string_lossy() + .to_string(), + )); + } } } } + let abs = std::path::absolute(&path) + .expect( + format!( + "Failed to find absolute path for TreeItem: {}", + path.to_string_lossy().to_string() + ) + .as_str(), + ) + .to_string_lossy() + .to_string(); TreeItem::new( - Uuid::new_v4().to_string(), + abs, path.file_name() .expect("Failed to get file name from path.") .to_string_lossy() @@ -57,26 +72,38 @@ impl<'a> Explorer<'a> { ) .expect("Failed to build tree from path.") } -} -impl<'a> Widget for &Explorer<'a> { - fn render(self, area: Rect, buf: &mut Buffer) { - Tree::new(&self.tree_items.children()) - .expect("Failed to build tree.") - .style(Style::default()) - .block( - Block::default() - .borders(Borders::ALL) - .title( - self.root_path - .file_name() - .expect("Failed to get file name from path.") - .to_string_lossy(), - ) - .title_style(Style::default().fg(Color::Green)) - .title_alignment(Alignment::Center), - ) - .render(area, buf); + pub fn render(&mut self, area: Rect, buf: &mut Buffer) { + StatefulWidget::render( + Tree::new(&self.tree_items.children()) + .expect("Failed to build tree.") + .style(Style::default()) + .block( + Block::default() + .borders(Borders::ALL) + .title( + self.root_path + .file_name() + .expect("Failed to get file name from path.") + .to_string_lossy(), + ) + .title_style(Style::default().fg(Color::Green)) + .title_alignment(Alignment::Center), + ) + .highlight_style( + Style::new() + .fg(Color::Black) + .bg(Color::Rgb(57, 59, 64)) + .add_modifier(Modifier::BOLD), + ), + area, + buf, + &mut self.tree_state, + ) + } + + pub fn selected(&self) -> Option<&String> { + self.tree_state.selected().last() } } @@ -84,4 +111,54 @@ impl<'a> Component for Explorer<'a> { fn id(&self) -> &str { "explorer" } + fn handle_event(&mut self, event: Event) -> Action { + if let Some(key_event) = event.as_key_event() { + // Handle events here that should not be passed on to the vim emulation handler. + match self.handle_key_events(key_event) { + Action::Handled => return Action::Handled, + _ => {} + } + } + if let Some(mouse_event) = event.as_mouse_event() { + match self.handle_mouse_events(mouse_event) { + Action::Handled => return Action::Handled, + _ => {} + } + } + Action::Pass + } + + fn handle_key_events(&mut self, key: KeyEvent) -> Action { + let changed = match key.code { + KeyCode::Up => self.tree_state.key_up(), + KeyCode::Char('k') => self.tree_state.key_up(), + KeyCode::Down => self.tree_state.key_down(), + KeyCode::Char('j') => self.tree_state.key_down(), + KeyCode::Left => self.tree_state.key_left(), + KeyCode::Char('h') => self.tree_state.key_left(), + KeyCode::Right => self.tree_state.key_right(), + KeyCode::Char('l') => self.tree_state.key_right(), + KeyCode::Enter => self.tree_state.toggle_selected(), + _ => false, + }; + if changed { + return Action::Handled; + } + Action::Noop + } + + fn handle_mouse_events(&mut self, mouse: MouseEvent) -> Action { + let changed = match mouse.kind { + MouseEventKind::ScrollDown => self.tree_state.scroll_down(1), + MouseEventKind::ScrollUp => self.tree_state.scroll_up(1), + MouseEventKind::Down(_button) => self + .tree_state + .click_at(Position::new(mouse.column, mouse.row)), + _ => false, + }; + if changed { + return Action::Handled; + } + Action::Noop + } } -- 2.47.2 From bccc5a35e2e92220113969b57ea5303a306c687a Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Mon, 19 Jan 2026 18:01:35 -0500 Subject: [PATCH 43/73] [tui] Add function for refreshing editor contents. It's still temporary, but at least it isn't done ad-hoc. --- src/tui/app.rs | 29 ++++++++++++++++++----------- src/tui/editor.rs | 17 ++++++++++++++--- 2 files changed, 32 insertions(+), 14 deletions(-) diff --git a/src/tui/app.rs b/src/tui/app.rs index 90e37c3..67b0bd5 100644 --- a/src/tui/app.rs +++ b/src/tui/app.rs @@ -8,6 +8,7 @@ use ratatui::layout::{Constraint, Direction, Layout, Rect}; use ratatui::prelude::{Color, Style, Widget}; use ratatui::widgets::{Block, Borders, Padding, Paragraph, Tabs, Wrap}; use ratatui::{DefaultTerminal, symbols}; +use std::path::PathBuf; use std::time::Duration; pub struct App<'a> { @@ -16,7 +17,7 @@ pub struct App<'a> { } impl<'a> App<'a> { - pub(crate) fn new(root_path: std::path::PathBuf) -> Self { + pub(crate) fn new(root_path: PathBuf) -> Self { let mut app = Self { explorer: Explorer::new(&root_path), editor: Editor::new(), @@ -90,6 +91,21 @@ impl<'a> App<'a> { .wrap(Wrap { trim: false }) .render(area, buf); } + + /// Refresh the contents of the editor to match the selected TreeItem in the file Explorer. + /// If the selected item is not a file, this does nothing. + fn refresh_editor_contents(&mut self) { + if let Some(current_file_path) = self.editor.file_path.clone() { + if let Some(selected_path_string) = self.explorer.selected() { + let selected_pathbuf = PathBuf::from(selected_path_string); + if std::path::absolute(&selected_pathbuf).unwrap().is_file() + && selected_pathbuf != current_file_path + { + self.editor.set_contents(&selected_pathbuf.into()).ok(); + } + } + } + } } // TODO: Separate complex components into their own widgets. @@ -129,16 +145,7 @@ impl<'a> Widget for &mut App<'a> { self.explorer.render(horizontal[0], buf); self.draw_tabs(editor_layout[0], buf); - - if let Some(editor) = self.editor.file_path.clone() { - let editor_abs = std::path::absolute(editor).unwrap(); - if let Some(selected) = self.explorer.selected() { - let selected_abs = std::path::absolute(selected).unwrap(); - if selected_abs.is_file() && selected_abs != editor_abs { - self.editor.set_contents(&selected_abs).ok(); - } - } - } + self.refresh_editor_contents(); self.editor.render(editor_layout[1], buf); } } diff --git a/src/tui/editor.rs b/src/tui/editor.rs index 2ff4f8c..3677f3b 100644 --- a/src/tui/editor.rs +++ b/src/tui/editor.rs @@ -9,6 +9,7 @@ use ratatui::crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers}; use ratatui::layout::{Alignment, Rect}; use ratatui::prelude::{Color, Style}; use ratatui::widgets::{Block, Borders, Padding, Widget}; +use syntect::parsing::SyntaxSet; // TODO: Consider using editor-command https://docs.rs/editor-command/latest/editor_command/ // TODO: Title should be detected programming language name @@ -52,20 +53,30 @@ impl Editor { impl Widget for &mut Editor { fn render(self, area: Rect, buf: &mut Buffer) { - // TODO: Use current file extension for syntax highlighting here. + let lang = self + .file_path + .as_ref() + .and_then(|p| p.extension()) + .map(|e| e.to_str().unwrap_or("md")) + .unwrap_or("md"); + let lang_name = SyntaxSet::load_defaults_nonewlines() + .find_syntax_by_extension(lang) + .map(|s| s.name.to_string()) + .unwrap_or_else(|| "Unknown".to_string()); + EditorView::new(&mut self.state) .wrap(true) .theme( EditorTheme::default().block( Block::default() - .title("Rust") + .title(lang_name.to_owned()) .title_style(Style::default().fg(Color::Yellow)) .title_alignment(Alignment::Right) .borders(Borders::ALL) .padding(Padding::new(0, 0, 0, 1)), ), ) - .syntax_highlighter(SyntaxHighlighter::new("dracula", "rs").ok()) + .syntax_highlighter(SyntaxHighlighter::new("dracula", lang).ok()) .tab_width(2) .line_numbers(LineNumbers::Absolute) .render(area, buf); -- 2.47.2 From d2846e1e4ec7c5201555e936e3f4d8af65b4d9c7 Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Tue, 20 Jan 2026 12:00:24 -0500 Subject: [PATCH 44/73] [tui] Set tab title to file name. Also update to use anyhow::Result in some places. --- src/gui.rs | 4 ++-- src/main.rs | 6 ++---- src/tui/app.rs | 12 ++++++++++-- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/gui.rs b/src/gui.rs index 62c6172..b80df58 100644 --- a/src/gui.rs +++ b/src/gui.rs @@ -1,10 +1,10 @@ +use anyhow::Result; use cxx_qt_lib::QString; -use std::error::Error; pub mod colors; pub mod filesystem; -pub fn run(root_path: std::path::PathBuf) -> Result<(), Box> { +pub fn run(root_path: std::path::PathBuf) -> Result<()> { println!("Starting the GUI editor at {:?}", root_path); use cxx_qt_lib::{QGuiApplication, QQmlApplicationEngine, QUrl}; diff --git a/src/main.rs b/src/main.rs index cc84b45..4fa7a06 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,4 @@ -// TODO: Header - -use std::error::Error; +use anyhow::Result; use std::process::{Command, Stdio}; use structopt::StructOpt; @@ -25,7 +23,7 @@ struct Cli { pub gui: bool, } -fn main() -> Result<(), Box> { +fn main() -> Result<()> { let args = Cli::from_args(); let root_path = match args.path { diff --git a/src/tui/app.rs b/src/tui/app.rs index 67b0bd5..cedb9f8 100644 --- a/src/tui/app.rs +++ b/src/tui/app.rs @@ -65,8 +65,16 @@ impl<'a> App<'a> { } fn draw_tabs(&self, area: Rect, buf: &mut Buffer) { - // TODO: Tabs should be opened from file explorer - Tabs::new(["file.md", "file.cpp"]) + // Determine the tab title from the current file (or use a fallback). + let title = self + .editor + .file_path + .as_ref() + .and_then(|p| p.file_name()) + .and_then(|s| s.to_str()) + .unwrap_or("Untitled"); + + Tabs::new(vec![title]) .divider(symbols::DOT) .block( Block::default() -- 2.47.2 From 2713d29285dff55b221a711ffdf6fd137ef046fc Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Tue, 20 Jan 2026 12:05:03 -0500 Subject: [PATCH 45/73] [tui] Store SyntaxSet in the Editor. --- src/tui/editor.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/tui/editor.rs b/src/tui/editor.rs index 3677f3b..e78f66d 100644 --- a/src/tui/editor.rs +++ b/src/tui/editor.rs @@ -19,6 +19,7 @@ pub struct Editor { pub state: EditorState, pub event_handler: EditorEventHandler, pub file_path: Option, + syntax_set: SyntaxSet, } impl Editor { @@ -27,6 +28,7 @@ impl Editor { state: EditorState::default(), event_handler: EditorEventHandler::default(), file_path: None, + syntax_set: SyntaxSet::load_defaults_nonewlines(), } } @@ -59,10 +61,11 @@ impl Widget for &mut Editor { .and_then(|p| p.extension()) .map(|e| e.to_str().unwrap_or("md")) .unwrap_or("md"); - let lang_name = SyntaxSet::load_defaults_nonewlines() + let lang_name = self + .syntax_set .find_syntax_by_extension(lang) .map(|s| s.name.to_string()) - .unwrap_or_else(|| "Unknown".to_string()); + .unwrap_or("Unknown".to_string()); EditorView::new(&mut self.state) .wrap(true) -- 2.47.2 From ecd94a262116c0081507a11633b58fc004539efc Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Tue, 20 Jan 2026 12:24:20 -0500 Subject: [PATCH 46/73] Update to use clap. Structopt is deprecated. Also removed some unused dependencies. --- Cargo.lock | 228 +++++++++++++++++++++------------------------------- Cargo.toml | 4 +- src/main.rs | 12 +-- 3 files changed, 97 insertions(+), 147 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b73c783..50acfb9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -24,12 +24,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] -name = "ansi_term" -version = "0.12.1" +name = "anstream" +version = "0.6.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" dependencies = [ - "winapi", + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", ] [[package]] @@ -38,6 +44,35 @@ version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + [[package]] name = "anyhow" version = "1.0.100" @@ -73,17 +108,6 @@ dependencies = [ "bytemuck", ] -[[package]] -name = "atty" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -dependencies = [ - "hermit-abi", - "libc", - "winapi", -] - [[package]] name = "autocfg" version = "1.5.0" @@ -201,21 +225,6 @@ dependencies = [ "thiserror 1.0.69", ] -[[package]] -name = "clap" -version = "2.34.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" -dependencies = [ - "ansi_term", - "atty", - "bitflags 1.3.2", - "strsim 0.8.0", - "textwrap", - "unicode-width 0.1.14", - "vec_map", -] - [[package]] name = "clap" version = "4.5.54" @@ -223,6 +232,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394" dependencies = [ "clap_builder", + "clap_derive", ] [[package]] @@ -231,9 +241,22 @@ version = "4.5.54" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00" dependencies = [ + "anstream", "anstyle", "clap_lex", - "strsim 0.11.1", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.114", ] [[package]] @@ -247,6 +270,7 @@ name = "clide" version = "0.1.0" dependencies = [ "anyhow", + "clap", "cxx", "cxx-qt", "cxx-qt-build", @@ -255,11 +279,8 @@ dependencies = [ "edtui", "log", "ratatui", - "structopt", - "strum", "syntect", "tui-tree-widget", - "uuid", ] [[package]] @@ -292,6 +313,12 @@ dependencies = [ "unicode-width 0.2.2", ] +[[package]] +name = "colorchoice" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + [[package]] name = "compact_str" version = "0.9.0" @@ -513,7 +540,7 @@ version = "1.0.192" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3efb93799095bccd4f763ca07997dc39a69e5e61ab52d2c407d4988d21ce144d" dependencies = [ - "clap 4.5.54", + "clap", "codespan-reporting 0.13.1", "indexmap", "proc-macro2", @@ -558,7 +585,7 @@ dependencies = [ "ident_case", "proc-macro2", "quote", - "strsim 0.11.1", + "strsim", "syn 2.0.114", ] @@ -725,9 +752,9 @@ checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59" [[package]] name = "euclid" -version = "0.22.11" +version = "0.22.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad9cdb4b747e485a12abb0e6566612956c7a1bafa3bdb8d682c5b6d403589e48" +checksum = "df61bf483e837f88d5c2291dcf55c67be7e676b3a51acc48db3a7b163b91ed63" dependencies = [ "num-traits", ] @@ -887,30 +914,12 @@ dependencies = [ "foldhash", ] -[[package]] -name = "heck" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" -dependencies = [ - "unicode-segmentation", -] - [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" -[[package]] -name = "hermit-abi" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] - [[package]] name = "hex" version = "0.4.3" @@ -969,6 +978,12 @@ dependencies = [ "syn 2.0.114", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + [[package]] name = "itertools" version = "0.13.0" @@ -1021,7 +1036,7 @@ checksum = "8fe90c1150662e858c7d5f945089b7517b0a80d8bf7ba4b1b5ffc984e7230a5b" dependencies = [ "hashbrown", "portable-atomic", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -1318,6 +1333,12 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + [[package]] name = "onig" version = "6.5.1" @@ -1523,30 +1544,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn 1.0.109", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", -] - [[package]] name = "proc-macro2" version = "1.0.105" @@ -1649,7 +1646,7 @@ dependencies = [ "kasuari", "lru", "strum", - "thiserror 2.0.17", + "thiserror 2.0.18", "unicode-segmentation", "unicode-truncate", "unicode-width 0.2.2", @@ -1723,7 +1720,7 @@ checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" dependencies = [ "getrandom 0.2.17", "libredox", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -1931,42 +1928,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" -[[package]] -name = "strsim" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" - [[package]] name = "strsim" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" -[[package]] -name = "structopt" -version = "0.3.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c6b5c64445ba8094a6ab0c3cd2ad323e07171012d9c98b0b15651daf1787a10" -dependencies = [ - "clap 2.34.0", - "lazy_static", - "structopt-derive", -] - -[[package]] -name = "structopt-derive" -version = "0.4.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0" -dependencies = [ - "heck 0.3.3", - "proc-macro-error", - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "strum" version = "0.27.2" @@ -1982,7 +1949,7 @@ version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" dependencies = [ - "heck 0.5.0", + "heck", "proc-macro2", "quote", "syn 2.0.114", @@ -2026,7 +1993,7 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "thiserror 2.0.17", + "thiserror 2.0.18", "walkdir", "yaml-rust", ] @@ -2103,15 +2070,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "textwrap" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" -dependencies = [ - "unicode-width 0.1.14", -] - [[package]] name = "thiserror" version = "1.0.69" @@ -2123,11 +2081,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl 2.0.17", + "thiserror-impl 2.0.18", ] [[package]] @@ -2143,9 +2101,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", @@ -2275,12 +2233,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "vec_map" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" - [[package]] name = "version_check" version = "0.9.5" @@ -2425,7 +2377,7 @@ checksum = "5f2ab60e120fd6eaa68d9567f3226e876684639d22a4219b313ff69ec0ccd5ac" dependencies = [ "log", "ordered-float", - "strsim 0.11.1", + "strsim", "thiserror 1.0.69", "wezterm-dynamic-derive", ] @@ -2628,9 +2580,9 @@ dependencies = [ [[package]] name = "zmij" -version = "1.0.15" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94f63c051f4fe3c1509da62131a678643c5b6fbdc9273b2b79d4378ebda003d2" +checksum = "dfcd145825aace48cff44a8844de64bf75feec3080e0aa5cdbde72961ae51a65" [[package]] name = "zune-core" diff --git a/Cargo.toml b/Cargo.toml index 8fc100c..1c9c448 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,13 +10,11 @@ cxx-qt-lib = { version="0.7", features = ["qt_full", "qt_gui"] } log = { version = "0.4.27", features = [] } dirs = "6.0.0" syntect = "5.2.0" -structopt = "0.3.26" +clap = { version = "4.5.54", features = ["derive"] } ratatui = "0.30.0" anyhow = "1.0.100" tui-tree-widget = "0.24.0" -uuid = { version = "1.19.0", features = ["v4"] } edtui = "0.11.1" -strum = "0.27.2" [build-dependencies] # The link_qt_object_files feature is required for statically linking Qt 6. diff --git a/src/main.rs b/src/main.rs index 4fa7a06..6906741 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,30 +1,30 @@ use anyhow::Result; +use clap::Parser; use std::process::{Command, Stdio}; -use structopt::StructOpt; pub mod gui; pub mod tui; /// Command line interface IDE with full GUI and headless modes. /// If no flags are provided, the GUI editor is launched in a separate process. /// If no path is provided, the current directory is used. -#[derive(StructOpt, Debug)] +#[derive(Parser, Debug)] #[structopt(name = "clide", verbatim_doc_comment)] struct Cli { /// The root directory for the project to open with the clide editor. - #[structopt(parse(from_os_str))] + #[arg(value_parser = clap::value_parser!(std::path::PathBuf))] pub path: Option, /// Run clide in headless mode. - #[structopt(name = "tui", short, long)] + #[arg(value_name = "tui", short, long)] pub tui: bool, /// Run the clide GUI in the current process, blocking the terminal and showing all output streams. - #[structopt(name = "gui", short, long)] + #[arg(value_name = "gui", short, long)] pub gui: bool, } fn main() -> Result<()> { - let args = Cli::from_args(); + let args = Cli::parse(); let root_path = match args.path { // If the CLI was provided a directory, convert it to absolute. -- 2.47.2 From 3ffdcc28655a7be543d99e889dd2b7e5dffe1182 Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Tue, 20 Jan 2026 12:43:45 -0500 Subject: [PATCH 47/73] [gui] Update cxx-qt dependencies to 0.8.0. --- Cargo.lock | 56 +++++++++++++++++++----------------------------------- Cargo.toml | 6 +++--- build.rs | 43 ++++++++++++++++++----------------------- 3 files changed, 42 insertions(+), 63 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 50acfb9..c9fc058 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -467,9 +467,9 @@ dependencies = [ [[package]] name = "cxx-qt" -version = "0.7.3" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb4ce9106b3ee7ef85f77d5f69ec30ec3037ea1edacd3e29a61b26ff47ecc637" +checksum = "ec7c6dea4b551221e1df4349af7ae6af2c906c16860bdab5cada5a957b43cbbc" dependencies = [ "cxx", "cxx-qt-build", @@ -481,9 +481,9 @@ dependencies = [ [[package]] name = "cxx-qt-build" -version = "0.7.3" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23a7884708e645dc34a2c475bd8c505a2ffeb7c9cb0843f5c580c002c939ea30" +checksum = "c70cbc19fb351a0413632b326ad4862baea7f50d641d2d360b29ad0f772547bc" dependencies = [ "cc", "codespan-reporting 0.11.1", @@ -494,17 +494,17 @@ dependencies = [ "quote", "serde", "serde_json", - "version_check", ] [[package]] name = "cxx-qt-gen" -version = "0.7.3" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea6b431dc58e4a9b7a55b675be0941331cc2cab0a494e4742ebf7bb393c3fc39" +checksum = "33744d84f696836347071ad73da233f758f98e5c0b348e2855140173b36bffa2" dependencies = [ "clang-format", "convert_case 0.6.0", + "cxx-gen", "indoc", "proc-macro2", "quote", @@ -513,9 +513,9 @@ dependencies = [ [[package]] name = "cxx-qt-lib" -version = "0.7.3" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "527b46c28be6bf3dd02f1567706641fe247a8798defab8268c84602955741217" +checksum = "7eacfc219a287c422619b7704166bfd3b9b842367bd9124ad5557e6150a2d658" dependencies = [ "cxx", "cxx-qt", @@ -525,12 +525,13 @@ dependencies = [ [[package]] name = "cxx-qt-macro" -version = "0.7.3" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "971d8811fd1c8dd06c17284edcfc8e9663dac30d7e3d52159405f836a81923f1" +checksum = "3cb8ce32a983d56470101ff8e61c8a700ba37805b6e942186c6f3dd5d6ad44f6" dependencies = [ "cxx-qt-gen", "proc-macro2", + "quote", "syn 2.0.114", ] @@ -984,15 +985,6 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" -[[package]] -name = "itertools" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" -dependencies = [ - "either", -] - [[package]] name = "itertools" version = "0.14.0" @@ -1564,13 +1556,15 @@ dependencies = [ [[package]] name = "qt-build-utils" -version = "0.7.3" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63c7e24653d0b3084180066306b26e532b152470b13a050c2d2960284d4b9f53" +checksum = "0cfd41d4f115dfc940ea3ea31b3aed77233ad09ab8859a227ed61323025590af" dependencies = [ + "anyhow", "cc", + "semver", + "serde", "thiserror 1.0.69", - "versions", ] [[package]] @@ -1642,7 +1636,7 @@ dependencies = [ "compact_str", "hashbrown", "indoc", - "itertools 0.14.0", + "itertools", "kasuari", "lru", "strum", @@ -1694,7 +1688,7 @@ dependencies = [ "hashbrown", "indoc", "instability", - "itertools 0.14.0", + "itertools", "line-clipping", "ratatui-core", "strum", @@ -2198,7 +2192,7 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16b380a1238663e5f8a691f9039c73e1cdae598a30e9855f541d29b08b53e9a5" dependencies = [ - "itertools 0.14.0", + "itertools", "unicode-segmentation", "unicode-width 0.2.2", ] @@ -2239,16 +2233,6 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" -[[package]] -name = "versions" -version = "6.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f25d498b63d1fdb376b4250f39ab3a5ee8d103957346abacd911e2d8b612c139" -dependencies = [ - "itertools 0.13.0", - "nom", -] - [[package]] name = "vtparse" version = "0.6.2" diff --git a/Cargo.toml b/Cargo.toml index 1c9c448..a002f91 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,8 +5,8 @@ edition = "2024" [dependencies] cxx = "1.0.95" -cxx-qt = "0.7" -cxx-qt-lib = { version="0.7", features = ["qt_full", "qt_gui"] } +cxx-qt = "0.8.0" +cxx-qt-lib = { version = "0.8.0", features = ["qt_full", "qt_gui", "qt_qml"] } log = { version = "0.4.27", features = [] } dirs = "6.0.0" syntect = "5.2.0" @@ -18,4 +18,4 @@ edtui = "0.11.1" [build-dependencies] # The link_qt_object_files feature is required for statically linking Qt 6. -cxx-qt-build = { version = "0.7", features = [ "link_qt_object_files" ] } +cxx-qt-build = { version = "0.8.0", features = ["link_qt_object_files"] } diff --git a/build.rs b/build.rs index 87a98ba..1d20876 100644 --- a/build.rs +++ b/build.rs @@ -1,28 +1,23 @@ use cxx_qt_build::{CxxQtBuilder, QmlModule}; fn main() { - CxxQtBuilder::new() - // Link Qt's Network library - // - Qt Core is always linked - // - Qt Gui is linked by enabling the qt_gui Cargo feature of cxx-qt-lib. - // - Qt Qml is linked by enabling the qt_qml Cargo feature of cxx-qt-lib. - // - Qt Qml requires linking Qt Network on macOS - .qt_module("Network") - .qt_module("Gui") - .qt_module("Svg") - .qt_module("Xml") - .qml_module(QmlModule { - uri: "clide.module", - rust_files: &["src/gui/colors.rs", "src/gui/filesystem.rs"], - qml_files: &[ - "qml/main.qml", - "qml/ClideAboutWindow.qml", - "qml/ClideTreeView.qml", - "qml/ClideProjectView.qml", - "qml/ClideEditor.qml", - "qml/ClideMenuBar.qml", - ], - ..Default::default() - }) - .build(); + CxxQtBuilder::new_qml_module(QmlModule::new("clide.module").qml_files(&[ + "qml/main.qml", + "qml/ClideAboutWindow.qml", + "qml/ClideTreeView.qml", + "qml/ClideProjectView.qml", + "qml/ClideEditor.qml", + "qml/ClideMenuBar.qml", + ])) + // Link Qt's Network library + // - Qt Core is always linked + // - Qt Gui is linked by enabling the qt_gui Cargo feature of cxx-qt-lib. + // - Qt Qml is linked by enabling the qt_qml Cargo feature of cxx-qt-lib. + // - Qt Qml requires linking Qt Network on macOS + .qt_module("Network") + .qt_module("Gui") + .qt_module("Svg") + .qt_module("Xml") + .files(["src/gui/colors.rs", "src/gui/filesystem.rs"]) + .build(); } -- 2.47.2 From ce2949159ce658c2ceb768ccc56a8cc5b4494ca2 Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Tue, 20 Jan 2026 16:03:38 -0500 Subject: [PATCH 48/73] [tui] Add AppComponent enum for storing all components. --- src/tui/app.rs | 148 ++++++++++++++++++++++++++++++-------------- src/tui/editor.rs | 5 +- src/tui/explorer.rs | 7 ++- 3 files changed, 108 insertions(+), 52 deletions(-) diff --git a/src/tui/app.rs b/src/tui/app.rs index cedb9f8..7fa5d26 100644 --- a/src/tui/app.rs +++ b/src/tui/app.rs @@ -1,6 +1,7 @@ use crate::tui::component::{Action, Component}; use crate::tui::editor::Editor; use crate::tui::explorer::Explorer; +use anyhow::{Result, anyhow}; use ratatui::buffer::Buffer; use ratatui::crossterm::event; use ratatui::crossterm::event::{Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers}; @@ -11,23 +12,71 @@ use ratatui::{DefaultTerminal, symbols}; use std::path::PathBuf; use std::time::Duration; +pub enum AppComponents<'a> { + AppEditor(Editor), + AppExplorer(Explorer<'a>), + AppComponent(Box), +} + pub struct App<'a> { - explorer: Explorer<'a>, - editor: Editor, + components: Vec>, } impl<'a> App<'a> { pub(crate) fn new(root_path: PathBuf) -> Self { let mut app = Self { - explorer: Explorer::new(&root_path), - editor: Editor::new(), + components: vec![ + AppComponents::AppExplorer(Explorer::new(&root_path)), + AppComponents::AppEditor(Editor::new()), + ], }; - app.editor + app.get_editor_mut() + .unwrap() .set_contents(&root_path.join("src/tui/app.rs")) .expect("Failed to set editor contents."); app } + fn get_explorer(&self) -> Result<&Explorer<'a>> { + for component in &self.components { + if let AppComponents::AppExplorer(explorer) = component { + return Ok(explorer); + } + } + Err(anyhow::anyhow!("Failed to find project explorer widget.")) + } + + fn get_explorer_mut(&mut self) -> Result<&mut Explorer<'a>> { + for component in &mut self.components { + if let AppComponents::AppExplorer(explorer) = component { + return Ok(explorer); + } + } + Err(anyhow::anyhow!("Failed to find project explorer widget.")) + } + + fn get_editor(&self) -> Option<&Editor> { + for component in &self.components { + if let AppComponents::AppEditor(editor) = component { + return Some(editor); + } + } + + // There is no editor currently opened. + None + } + + fn get_editor_mut(&mut self) -> Option<&mut Editor> { + for component in &mut self.components { + if let AppComponents::AppEditor(editor) = component { + return Some(editor); + } + } + + // There is no editor currently opened. + None + } + fn get_event(&mut self) -> Option { if !event::poll(Duration::from_millis(250)).expect("event poll failed") { return None; @@ -36,7 +85,7 @@ impl<'a> App<'a> { event::read().ok() } - pub fn run(mut self, mut terminal: DefaultTerminal) -> anyhow::Result<()> { + pub fn run(mut self, mut terminal: DefaultTerminal) -> Result<()> { loop { // TODO: Handle events based on which component is active. terminal.draw(|f| { @@ -66,15 +115,16 @@ impl<'a> App<'a> { fn draw_tabs(&self, area: Rect, buf: &mut Buffer) { // Determine the tab title from the current file (or use a fallback). - let title = self - .editor - .file_path - .as_ref() - .and_then(|p| p.file_name()) - .and_then(|s| s.to_str()) - .unwrap_or("Untitled"); + let mut title: Option<&str> = None; + if let Some(editor) = self.get_editor() { + title = editor + .file_path + .as_ref() + .and_then(|p| p.file_name()) + .and_then(|s| s.to_str()) + } - Tabs::new(vec![title]) + Tabs::new(vec![title.unwrap_or("Unknown")]) .divider(symbols::DOT) .block( Block::default() @@ -102,17 +152,22 @@ impl<'a> App<'a> { /// Refresh the contents of the editor to match the selected TreeItem in the file Explorer. /// If the selected item is not a file, this does nothing. - fn refresh_editor_contents(&mut self) { - if let Some(current_file_path) = self.editor.file_path.clone() { - if let Some(selected_path_string) = self.explorer.selected() { - let selected_pathbuf = PathBuf::from(selected_path_string); - if std::path::absolute(&selected_pathbuf).unwrap().is_file() - && selected_pathbuf != current_file_path - { - self.editor.set_contents(&selected_pathbuf.into()).ok(); - } + fn refresh_editor_contents(&mut self) -> Result<()> { + // Use the currently selected TreeItem or get an absolute path to this source file. + let selected_pathbuf = match self.get_explorer()?.selected() { + Ok(path) => PathBuf::from(path), + Err(_) => PathBuf::from(std::path::absolute(file!())?.to_string_lossy().to_string()), + }; + let editor = self + .get_editor_mut() + .expect("Failed to get active editor while refreshing contents."); + if let Some(current_file_path) = editor.file_path.clone() { + if selected_pathbuf == current_file_path || !selected_pathbuf.is_file() { + return Ok(()); } + return editor.set_contents(&selected_pathbuf); } + Err(anyhow!("Failed to refresh editor contents")) } } @@ -149,12 +204,13 @@ impl<'a> Widget for &mut App<'a> { self.draw_status(vertical[0], buf); self.draw_terminal(vertical[2], buf); - - self.explorer.render(horizontal[0], buf); - + if let Ok(explorer) = self.get_explorer_mut() { + explorer.render(horizontal[0], buf); + } self.draw_tabs(editor_layout[0], buf); - self.refresh_editor_contents(); - self.editor.render(editor_layout[1], buf); + self.refresh_editor_contents() + .expect("Failed to refresh editor contents."); + self.get_editor_mut().unwrap().render(editor_layout[1], buf); } } @@ -169,34 +225,35 @@ impl<'a> Component for App<'a> { /// /// App could then provide helpers for altering Component state based on TUI grouping.. /// (such as editor tabs, file explorer, status bars, etc..) + /// + /// Handles events for the App and delegates to attached Components. fn handle_event(&mut self, event: Event) -> Action { // Handle events in the primary application. if let Some(key_event) = event.as_key_event() { match self.handle_key_events(key_event) { Action::Quit => return Action::Quit, - Action::Handled => { - // dbg!(format!("Handled event: {:?}", self.id())); - return Action::Handled; - } + Action::Handled => return Action::Handled, _ => {} } } - self.explorer.handle_event(event.clone()); - self.editor.handle_event(event.clone()); // Handle events for all components. - // for component in &mut self.components { - // dbg!(format!("Handling event: {:?}", component.id())); - // // Actions returned here abort the input handling iteration. - // match component.handle_event(event.clone()) { - // Action::Quit => return Action::Quit, - // Action::Handled => return Action::Handled, - // _ => continue, - // } - // } + for component in &mut self.components { + let action = match component { + AppComponents::AppEditor(editor) => editor.handle_event(event.clone()), + AppComponents::AppExplorer(explorer) => explorer.handle_event(event.clone()), + AppComponents::AppComponent(comp) => comp.handle_event(event.clone()), + }; + // Actions returned here abort the input handling iteration. + match action { + Action::Quit | Action::Handled => return action, + _ => {} + } + } Action::Noop } + /// Handles key events for the App Component only. fn handle_key_events(&mut self, key: KeyEvent) -> Action { match key { KeyEvent { @@ -205,10 +262,7 @@ impl<'a> Component for App<'a> { kind: KeyEventKind::Press, state: _state, } => Action::Quit, - key_event => { - // Pass the key event to each component that can handle it. - self.explorer.handle_key_events(key_event) - } + _ => Action::Noop, } } } diff --git a/src/tui/editor.rs b/src/tui/editor.rs index e78f66d..71eae11 100644 --- a/src/tui/editor.rs +++ b/src/tui/editor.rs @@ -1,6 +1,6 @@ use crate::tui::component::{Action, Component}; -use anyhow::Result; +use anyhow::{Context, Result}; use edtui::{ EditorEventHandler, EditorState, EditorTheme, EditorView, LineNumbers, Lines, SyntaxHighlighter, }; @@ -40,9 +40,8 @@ impl Editor { .collect(); self.file_path = Some(path.clone()); self.state.lines = Lines::new(lines); - return Ok(()); } - Err(anyhow::Error::msg("Failed to set editor file contents")) + Ok(()) } pub fn save(&self) -> Result<()> { diff --git a/src/tui/explorer.rs b/src/tui/explorer.rs index 0c843b1..4b72648 100644 --- a/src/tui/explorer.rs +++ b/src/tui/explorer.rs @@ -102,8 +102,11 @@ impl<'a> Explorer<'a> { ) } - pub fn selected(&self) -> Option<&String> { - self.tree_state.selected().last() + pub fn selected(&self) -> Result { + if let Some(path) = self.tree_state.selected().last() { + return Ok(std::path::absolute(path)?.to_str().unwrap().to_string()); + } + Err(anyhow::anyhow!("Failed to get selected TreeItem")) } } -- 2.47.2 From 42a40fe7f315377816fb9c86b950b68c892ea633 Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Tue, 20 Jan 2026 17:19:13 -0500 Subject: [PATCH 49/73] [tui] Remove most usage of expect(). Still not quite sure what to do about some pieces in QML bindings for the GUI. --- src/main.rs | 15 ++++++---- src/tui.rs | 11 ++++---- src/tui/app.rs | 62 ++++++++++++++++++++---------------------- src/tui/component.rs | 17 ++++++------ src/tui/editor.rs | 16 +++++------ src/tui/explorer.rs | 65 ++++++++++++++++++++++---------------------- 6 files changed, 95 insertions(+), 91 deletions(-) diff --git a/src/main.rs b/src/main.rs index 6906741..e00528d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,10 @@ -use anyhow::Result; +use anyhow::{Context, Result}; use clap::Parser; +use ratatui::Terminal; +use ratatui::backend::CrosstermBackend; +use std::io::stdout; use std::process::{Command, Stdio}; +use crate::tui::Tui; pub mod gui; pub mod tui; @@ -30,16 +34,17 @@ fn main() -> Result<()> { // If the CLI was provided a directory, convert it to absolute. Some(path) => std::path::absolute(path)?, // If no path was provided, use the current directory. - None => std::env::current_dir().unwrap_or_else(|_| - // If we can't find the CWD, attempt to open the home directory. - dirs::home_dir().expect("Failed to access filesystem.")), + None => std::env::current_dir().unwrap_or( + // If we can't find the CWD, attempt to open the home directory. + dirs::home_dir().context("Failed to obtain home directory")?, + ), }; match args.gui { true => gui::run(root_path), false => match args.tui { // Open the TUI editor if requested, otherwise use the QML GUI by default. - true => Ok(tui::Tui::new(root_path).start()?), + true => Ok(Tui::new(root_path)?.start()?), false => { // Relaunch the CLIDE GUI in a separate process. Command::new(std::env::current_exe()?) diff --git a/src/tui.rs b/src/tui.rs index d10ff05..97721b7 100644 --- a/src/tui.rs +++ b/src/tui.rs @@ -20,12 +20,11 @@ pub struct Tui { } impl Tui { - pub fn new(root_path: std::path::PathBuf) -> Self { - Self { - terminal: Terminal::new(CrosstermBackend::new(stdout())) - .expect("Failed to initialize terminal"), + pub fn new(root_path: std::path::PathBuf) -> Result { + Ok(Self { + terminal: Terminal::new(CrosstermBackend::new(stdout()))?, root_path, - } + }) } pub fn start(self) -> Result<()> { @@ -38,7 +37,7 @@ impl Tui { )?; enable_raw_mode()?; - let app_result = app::App::new(self.root_path) + let app_result = app::App::new(self.root_path)? .run(self.terminal) .context("Failed to start the TUI editor."); Self::stop()?; diff --git a/src/tui/app.rs b/src/tui/app.rs index 7fa5d26..1ea198e 100644 --- a/src/tui/app.rs +++ b/src/tui/app.rs @@ -1,7 +1,7 @@ use crate::tui::component::{Action, Component}; use crate::tui::editor::Editor; use crate::tui::explorer::Explorer; -use anyhow::{Result, anyhow}; +use anyhow::{Context, Result, anyhow}; use ratatui::buffer::Buffer; use ratatui::crossterm::event; use ratatui::crossterm::event::{Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers}; @@ -23,18 +23,21 @@ pub struct App<'a> { } impl<'a> App<'a> { - pub(crate) fn new(root_path: PathBuf) -> Self { + pub fn new(root_path: PathBuf) -> Result { let mut app = Self { components: vec![ - AppComponents::AppExplorer(Explorer::new(&root_path)), + AppComponents::AppExplorer(Explorer::new(&root_path)?), AppComponents::AppEditor(Editor::new()), ], }; app.get_editor_mut() .unwrap() .set_contents(&root_path.join("src/tui/app.rs")) - .expect("Failed to set editor contents."); - app + .context(format!( + "Failed to initialize editor contents to path: {}", + root_path.to_string_lossy() + ))?; + Ok(app) } fn get_explorer(&self) -> Result<&Explorer<'a>> { @@ -77,27 +80,22 @@ impl<'a> App<'a> { None } - fn get_event(&mut self) -> Option { - if !event::poll(Duration::from_millis(250)).expect("event poll failed") { - return None; - } - - event::read().ok() - } - pub fn run(mut self, mut terminal: DefaultTerminal) -> Result<()> { loop { - // TODO: Handle events based on which component is active. + self.refresh_editor_contents() + .context("Failed to refresh editor contents.")?; + terminal.draw(|f| { f.render_widget(&mut self, f.area()); })?; - if let Some(event) = self.get_event() { - match self.handle_event(event) { + // TODO: Handle events based on which component is active. + if event::poll(Duration::from_millis(250)).context("event poll failed")? { + match self.handle_event(event::read()?)? { Action::Quit => break, Action::Handled => {} _ => { - // panic!("Unhandled event: {:?}", event); + // anyhow::anyhow!("Unhandled event: {:?}", event); } } } @@ -160,7 +158,7 @@ impl<'a> App<'a> { }; let editor = self .get_editor_mut() - .expect("Failed to get active editor while refreshing contents."); + .context("Failed to get active editor while refreshing contents.")?; if let Some(current_file_path) = editor.file_path.clone() { if selected_pathbuf == current_file_path || !selected_pathbuf.is_file() { return Ok(()); @@ -208,8 +206,6 @@ impl<'a> Widget for &mut App<'a> { explorer.render(horizontal[0], buf); } self.draw_tabs(editor_layout[0], buf); - self.refresh_editor_contents() - .expect("Failed to refresh editor contents."); self.get_editor_mut().unwrap().render(editor_layout[1], buf); } } @@ -227,12 +223,14 @@ impl<'a> Component for App<'a> { /// (such as editor tabs, file explorer, status bars, etc..) /// /// Handles events for the App and delegates to attached Components. - fn handle_event(&mut self, event: Event) -> Action { + fn handle_event(&mut self, event: Event) -> Result { // Handle events in the primary application. if let Some(key_event) = event.as_key_event() { - match self.handle_key_events(key_event) { - Action::Quit => return Action::Quit, - Action::Handled => return Action::Handled, + let res = self + .handle_key_events(key_event) + .context("Failed to handle key events for primary App Component."); + match res { + Ok(Action::Quit) | Ok(Action::Handled) => return res, _ => {} } } @@ -240,29 +238,29 @@ impl<'a> Component for App<'a> { // Handle events for all components. for component in &mut self.components { let action = match component { - AppComponents::AppEditor(editor) => editor.handle_event(event.clone()), - AppComponents::AppExplorer(explorer) => explorer.handle_event(event.clone()), - AppComponents::AppComponent(comp) => comp.handle_event(event.clone()), + AppComponents::AppEditor(editor) => editor.handle_event(event.clone())?, + AppComponents::AppExplorer(explorer) => explorer.handle_event(event.clone())?, + AppComponents::AppComponent(comp) => comp.handle_event(event.clone())?, }; // Actions returned here abort the input handling iteration. match action { - Action::Quit | Action::Handled => return action, + Action::Quit | Action::Handled => return Ok(action), _ => {} } } - Action::Noop + Ok(Action::Noop) } /// Handles key events for the App Component only. - fn handle_key_events(&mut self, key: KeyEvent) -> Action { + fn handle_key_events(&mut self, key: KeyEvent) -> Result { match key { KeyEvent { code: KeyCode::Char('c'), modifiers: KeyModifiers::CONTROL, kind: KeyEventKind::Press, state: _state, - } => Action::Quit, - _ => Action::Noop, + } => Ok(Action::Quit), + _ => Ok(Action::Noop), } } } diff --git a/src/tui/component.rs b/src/tui/component.rs index 4af5b70..99dd7c9 100644 --- a/src/tui/component.rs +++ b/src/tui/component.rs @@ -1,5 +1,6 @@ #![allow(dead_code, unused_variables)] +use anyhow::Result; use ratatui::crossterm::event::{Event, KeyEvent, MouseEvent}; pub enum Action { @@ -25,22 +26,22 @@ pub trait Component { /// This is used for lookup in a container of Components. fn id(&self) -> &str; - fn handle_event(&mut self, event: Event) -> Action { + fn handle_event(&mut self, event: Event) -> Result { match event { Event::Key(key_event) => self.handle_key_events(key_event), - _ => Action::Noop, + _ => Ok(Action::Noop), } } - fn handle_key_events(&mut self, key: KeyEvent) -> Action { - Action::Noop + fn handle_key_events(&mut self, key: KeyEvent) -> Result { + Ok(Action::Noop) } - fn handle_mouse_events(&mut self, mouse: MouseEvent) -> Action { - Action::Noop + fn handle_mouse_events(&mut self, mouse: MouseEvent) -> Result { + Ok(Action::Noop) } - fn update(&mut self, action: Action) -> Action { - Action::Noop + fn update(&mut self, action: Action) -> Result { + Ok(Action::Noop) } } diff --git a/src/tui/editor.rs b/src/tui/editor.rs index 71eae11..411edfd 100644 --- a/src/tui/editor.rs +++ b/src/tui/editor.rs @@ -90,32 +90,32 @@ impl Component for Editor { "editor" } - fn handle_event(&mut self, event: Event) -> Action { + fn handle_event(&mut self, event: Event) -> Result { if let Some(key_event) = event.as_key_event() { // Handle events here that should not be passed on to the vim emulation handler. - match self.handle_key_events(key_event) { - Action::Handled => return Action::Handled, + match self.handle_key_events(key_event)? { + Action::Handled => return Ok(Action::Handled), _ => {} } } self.event_handler.on_event(event, &mut self.state); - Action::Pass + Ok(Action::Pass) } /// The events for the vim emulation should be handled by EditorEventHandler::on_event. /// These events are custom to the clide application. - fn handle_key_events(&mut self, key: KeyEvent) -> Action { + fn handle_key_events(&mut self, key: KeyEvent) -> Result { match key { KeyEvent { code: KeyCode::Char('s'), modifiers: KeyModifiers::CONTROL, .. } => { - self.save().expect("Failed to save file."); - Action::Handled + self.save().context("Failed to save file.")?; + Ok(Action::Handled) } // For other events not handled here, pass to the vim emulation handler. - _ => Action::Noop, + _ => Ok(Action::Noop), } } } diff --git a/src/tui/explorer.rs b/src/tui/explorer.rs index 4b72648..aa39ae5 100644 --- a/src/tui/explorer.rs +++ b/src/tui/explorer.rs @@ -1,5 +1,5 @@ use crate::tui::component::{Action, Component}; -use anyhow::Result; +use anyhow::{Context, Result}; use ratatui::buffer::Buffer; use ratatui::crossterm::event::{Event, KeyCode, KeyEvent, MouseEvent, MouseEventKind}; use ratatui::layout::{Alignment, Position, Rect}; @@ -17,33 +17,36 @@ pub struct Explorer<'a> { } impl<'a> Explorer<'a> { - pub fn new(path: &std::path::PathBuf) -> Self { + pub fn new(path: &std::path::PathBuf) -> Result { let explorer = Explorer { root_path: path.to_owned(), - tree_items: Self::build_tree_from_path(path.to_owned()), + tree_items: Self::build_tree_from_path(path.to_owned())?, tree_state: TreeState::default(), }; - explorer + Ok(explorer) } - fn build_tree_from_path(path: std::path::PathBuf) -> TreeItem<'static, String> { + fn build_tree_from_path(path: std::path::PathBuf) -> Result> { let mut children = vec![]; if let Ok(entries) = fs::read_dir(&path) { let mut paths = entries .map(|res| res.map(|e| e.path())) .collect::, std::io::Error>>() - .expect(""); + .context(format!( + "Failed to build vector of paths under directory: {:?}", + path + ))?; paths.sort(); for path in paths { if path.is_dir() { - children.push(Self::build_tree_from_path(path)); + children.push(Self::build_tree_from_path(path)?); } else { if let Ok(path) = std::path::absolute(&path) { let path_str = path.to_string_lossy().to_string(); children.push(TreeItem::new_leaf( path_str, path.file_name() - .expect("Failed to get file name from path.") + .context("Failed to get file name from path.")? .to_string_lossy() .to_string(), )); @@ -53,13 +56,10 @@ impl<'a> Explorer<'a> { } let abs = std::path::absolute(&path) - .expect( - format!( - "Failed to find absolute path for TreeItem: {}", - path.to_string_lossy().to_string() - ) - .as_str(), - ) + .context(format!( + "Failed to find absolute path for TreeItem: {:?}", + path + ))? .to_string_lossy() .to_string(); TreeItem::new( @@ -70,13 +70,13 @@ impl<'a> Explorer<'a> { .to_string(), children, ) - .expect("Failed to build tree from path.") + .context("Failed to build tree from path.") } - pub fn render(&mut self, area: Rect, buf: &mut Buffer) { + pub fn render(&mut self, area: Rect, buf: &mut Buffer) -> Result<()> { StatefulWidget::render( Tree::new(&self.tree_items.children()) - .expect("Failed to build tree.") + .context("Failed to build file Explorer Tree.")? .style(Style::default()) .block( Block::default() @@ -84,7 +84,7 @@ impl<'a> Explorer<'a> { .title( self.root_path .file_name() - .expect("Failed to get file name from path.") + .context("Failed to get file name from path.")? .to_string_lossy(), ) .title_style(Style::default().fg(Color::Green)) @@ -99,7 +99,8 @@ impl<'a> Explorer<'a> { area, buf, &mut self.tree_state, - ) + ); + Ok(()) } pub fn selected(&self) -> Result { @@ -114,24 +115,24 @@ impl<'a> Component for Explorer<'a> { fn id(&self) -> &str { "explorer" } - fn handle_event(&mut self, event: Event) -> Action { + fn handle_event(&mut self, event: Event) -> Result { if let Some(key_event) = event.as_key_event() { // Handle events here that should not be passed on to the vim emulation handler. - match self.handle_key_events(key_event) { - Action::Handled => return Action::Handled, + match self.handle_key_events(key_event)? { + Action::Handled => return Ok(Action::Handled), _ => {} } } if let Some(mouse_event) = event.as_mouse_event() { - match self.handle_mouse_events(mouse_event) { - Action::Handled => return Action::Handled, + match self.handle_mouse_events(mouse_event)? { + Action::Handled => return Ok(Action::Handled), _ => {} } } - Action::Pass + Ok(Action::Pass) } - fn handle_key_events(&mut self, key: KeyEvent) -> Action { + fn handle_key_events(&mut self, key: KeyEvent) -> Result { let changed = match key.code { KeyCode::Up => self.tree_state.key_up(), KeyCode::Char('k') => self.tree_state.key_up(), @@ -145,12 +146,12 @@ impl<'a> Component for Explorer<'a> { _ => false, }; if changed { - return Action::Handled; + return Ok(Action::Handled); } - Action::Noop + Ok(Action::Noop) } - fn handle_mouse_events(&mut self, mouse: MouseEvent) -> Action { + fn handle_mouse_events(&mut self, mouse: MouseEvent) -> Result { let changed = match mouse.kind { MouseEventKind::ScrollDown => self.tree_state.scroll_down(1), MouseEventKind::ScrollUp => self.tree_state.scroll_up(1), @@ -160,8 +161,8 @@ impl<'a> Component for Explorer<'a> { _ => false, }; if changed { - return Action::Handled; + return Ok(Action::Handled); } - Action::Noop + Ok(Action::Noop) } } -- 2.47.2 From 1e635ee05980c7fdee1a9764e6a516d72e0c88ed Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Tue, 20 Jan 2026 17:37:15 -0500 Subject: [PATCH 50/73] [tui] Use anyhow::bail!() macro. --- src/tui/app.rs | 16 ++++++++++------ src/tui/editor.rs | 4 ++-- src/tui/explorer.rs | 4 ++-- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/tui/app.rs b/src/tui/app.rs index 1ea198e..574497c 100644 --- a/src/tui/app.rs +++ b/src/tui/app.rs @@ -1,7 +1,7 @@ use crate::tui::component::{Action, Component}; use crate::tui::editor::Editor; use crate::tui::explorer::Explorer; -use anyhow::{Context, Result, anyhow}; +use anyhow::{Context, Result, anyhow, bail}; use ratatui::buffer::Buffer; use ratatui::crossterm::event; use ratatui::crossterm::event::{Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers}; @@ -46,7 +46,7 @@ impl<'a> App<'a> { return Ok(explorer); } } - Err(anyhow::anyhow!("Failed to find project explorer widget.")) + bail!("Failed to find project explorer widget.") } fn get_explorer_mut(&mut self) -> Result<&mut Explorer<'a>> { @@ -55,7 +55,7 @@ impl<'a> App<'a> { return Ok(explorer); } } - Err(anyhow::anyhow!("Failed to find project explorer widget.")) + bail!("Failed to find project explorer widget.") } fn get_editor(&self) -> Option<&Editor> { @@ -95,7 +95,7 @@ impl<'a> App<'a> { Action::Quit => break, Action::Handled => {} _ => { - // anyhow::anyhow!("Unhandled event: {:?}", event); + // bail!("Unhandled event: {:?}", event); } } } @@ -165,7 +165,7 @@ impl<'a> App<'a> { } return editor.set_contents(&selected_pathbuf); } - Err(anyhow!("Failed to refresh editor contents")) + bail!("Failed to refresh editor contents") } } @@ -203,7 +203,11 @@ impl<'a> Widget for &mut App<'a> { self.draw_status(vertical[0], buf); self.draw_terminal(vertical[2], buf); if let Ok(explorer) = self.get_explorer_mut() { - explorer.render(horizontal[0], buf); + // TODO: What to do about errors during rendering? + // Once there is a debug console, maybe log it and discard? Panic isn't great. + explorer + .render(horizontal[0], buf) + .expect("Failed to render Explorer"); } self.draw_tabs(editor_layout[0], buf); self.get_editor_mut().unwrap().render(editor_layout[1], buf); diff --git a/src/tui/editor.rs b/src/tui/editor.rs index 411edfd..d6dc3a7 100644 --- a/src/tui/editor.rs +++ b/src/tui/editor.rs @@ -1,6 +1,6 @@ use crate::tui::component::{Action, Component}; -use anyhow::{Context, Result}; +use anyhow::{bail, Context, Result}; use edtui::{ EditorEventHandler, EditorState, EditorTheme, EditorView, LineNumbers, Lines, SyntaxHighlighter, }; @@ -48,7 +48,7 @@ impl Editor { if let Some(path) = &self.file_path { return std::fs::write(path, self.state.lines.to_string()).map_err(|e| e.into()); }; - Err(anyhow::anyhow!("File not saved. No file path set.")) + bail!("File not saved. No file path set.") } } diff --git a/src/tui/explorer.rs b/src/tui/explorer.rs index aa39ae5..c63e810 100644 --- a/src/tui/explorer.rs +++ b/src/tui/explorer.rs @@ -1,5 +1,5 @@ use crate::tui::component::{Action, Component}; -use anyhow::{Context, Result}; +use anyhow::{bail, Context, Result}; use ratatui::buffer::Buffer; use ratatui::crossterm::event::{Event, KeyCode, KeyEvent, MouseEvent, MouseEventKind}; use ratatui::layout::{Alignment, Position, Rect}; @@ -107,7 +107,7 @@ impl<'a> Explorer<'a> { if let Some(path) = self.tree_state.selected().last() { return Ok(std::path::absolute(path)?.to_str().unwrap().to_string()); } - Err(anyhow::anyhow!("Failed to get selected TreeItem")) + bail!("Failed to get selected TreeItem") } } -- 2.47.2 From 7149ad0118ea6892ec3ba156dbb17b02219fc98a Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Tue, 20 Jan 2026 20:14:25 -0500 Subject: [PATCH 51/73] [tui] Add debug console. The input will not be handled correctly until #8 is complete, but the input logic is there and was tested. Fixes #5. --- Cargo.lock | 129 ++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 1 + src/tui.rs | 19 +++++++ src/tui/app.rs | 62 ++++++++++++--------- src/tui/editor.rs | 4 +- src/tui/explorer.rs | 4 +- src/tui/logger.rs | 76 ++++++++++++++++++++++++++ 7 files changed, 266 insertions(+), 29 deletions(-) create mode 100644 src/tui/logger.rs diff --git a/Cargo.lock b/Cargo.lock index c9fc058..795325c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -23,6 +23,15 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anstream" version = "0.6.21" @@ -216,6 +225,17 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +[[package]] +name = "chrono" +version = "0.4.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118" +dependencies = [ + "iana-time-zone", + "num-traits", + "windows-link", +] + [[package]] name = "clang-format" version = "0.3.0" @@ -280,6 +300,7 @@ dependencies = [ "log", "ratatui", "syntect", + "tui-logger", "tui-tree-widget", ] @@ -351,6 +372,12 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + [[package]] name = "cpufeatures" version = "0.2.17" @@ -729,6 +756,16 @@ dependencies = [ "syn 2.0.114", ] +[[package]] +name = "env_filter" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bf3c259d255ca70051b30e2e95b5446cdb8949ac4cd22c0d7fd634d89f568e2" +dependencies = [ + "log", + "regex", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -927,6 +964,30 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "iana-time-zone" +version = "0.1.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "ident_case" version = "1.0.1" @@ -2151,6 +2212,21 @@ dependencies = [ "time-core", ] +[[package]] +name = "tui-logger" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9384df20a5244a6ab204bc4b6959b41f37f0ee7b5e0f2feb7a8a78f58e684d06" +dependencies = [ + "chrono", + "env_filter", + "lazy_static", + "log", + "parking_lot", + "ratatui", + "unicode-segmentation", +] + [[package]] name = "tui-tree-widget" version = "0.24.0" @@ -2421,12 +2497,65 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + [[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-sys" version = "0.60.2" diff --git a/Cargo.toml b/Cargo.toml index a002f91..de9de16 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ clap = { version = "4.5.54", features = ["derive"] } ratatui = "0.30.0" anyhow = "1.0.100" tui-tree-widget = "0.24.0" +tui-logger = "0.18.1" edtui = "0.11.1" [build-dependencies] diff --git a/src/tui.rs b/src/tui.rs index 97721b7..83fc16e 100644 --- a/src/tui.rs +++ b/src/tui.rs @@ -2,8 +2,10 @@ mod app; mod component; mod editor; mod explorer; +mod logger; use anyhow::{Context, Result}; +use log::{LevelFilter, debug}; use ratatui::Terminal; use ratatui::backend::CrosstermBackend; use ratatui::crossterm::event::{ @@ -12,7 +14,11 @@ use ratatui::crossterm::event::{ use ratatui::crossterm::terminal::{ EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode, }; +use std::env; use std::io::{Stdout, stdout}; +use tui_logger::{ + TuiLoggerFile, TuiLoggerLevelOutput, init_logger, set_default_level, set_log_file, +}; pub struct Tui { terminal: Terminal>, @@ -21,6 +27,19 @@ pub struct Tui { impl Tui { pub fn new(root_path: std::path::PathBuf) -> Result { + init_logger(LevelFilter::Trace)?; + set_default_level(LevelFilter::Trace); + debug!(target:"Tui", "Logging initialized"); + + let mut dir = env::temp_dir(); + dir.push("clide.log"); + let file_options = TuiLoggerFile::new(dir.to_str().unwrap()) + .output_level(Some(TuiLoggerLevelOutput::Abbreviated)) + .output_file(false) + .output_separator(':'); + set_log_file(file_options); + debug!(target:"Tui", "Logging to file: {}", dir.to_str().unwrap()); + Ok(Self { terminal: Terminal::new(CrosstermBackend::new(stdout()))?, root_path, diff --git a/src/tui/app.rs b/src/tui/app.rs index 574497c..f10891a 100644 --- a/src/tui/app.rs +++ b/src/tui/app.rs @@ -1,7 +1,9 @@ use crate::tui::component::{Action, Component}; use crate::tui::editor::Editor; use crate::tui::explorer::Explorer; +use crate::tui::logger::Logger; use anyhow::{Context, Result, anyhow, bail}; +use log::{debug, error, info, trace, warn}; use ratatui::buffer::Buffer; use ratatui::crossterm::event; use ratatui::crossterm::event::{Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers}; @@ -12,9 +14,13 @@ use ratatui::{DefaultTerminal, symbols}; use std::path::PathBuf; use std::time::Duration; +// TODO: Need a way to dynamically run Widget::render on all widgets. +// TODO: + Need a way to map Rect to Component::id() to position each widget? +// TODO: Need a way to dynamically run Component methods on all widgets. pub enum AppComponents<'a> { AppEditor(Editor), AppExplorer(Explorer<'a>), + AppLogger(Logger), AppComponent(Box), } @@ -28,6 +34,7 @@ impl<'a> App<'a> { components: vec![ AppComponents::AppExplorer(Explorer::new(&root_path)?), AppComponents::AppEditor(Editor::new()), + AppComponents::AppLogger(Logger::new()), ], }; app.get_editor_mut() @@ -133,21 +140,6 @@ impl<'a> App<'a> { .render(area, buf); } - fn draw_terminal(&self, area: Rect, buf: &mut Buffer) { - // TODO: Title should be detected shell name - // TODO: Contents should be shell output - Paragraph::new("shaun@pc:~/Code/clide$ ") - .style(Style::default()) - .block( - Block::default() - .title("Bash") - .title_style(Style::default().fg(Color::DarkGray)) - .borders(Borders::ALL), - ) - .wrap(Wrap { trim: false }) - .render(area, buf); - } - /// Refresh the contents of the editor to match the selected TreeItem in the file Explorer. /// If the selected item is not a file, this does nothing. fn refresh_editor_contents(&mut self) -> Result<()> { @@ -201,22 +193,27 @@ impl<'a> Widget for &mut App<'a> { .split(horizontal[1]); self.draw_status(vertical[0], buf); - self.draw_terminal(vertical[2], buf); - if let Ok(explorer) = self.get_explorer_mut() { - // TODO: What to do about errors during rendering? - // Once there is a debug console, maybe log it and discard? Panic isn't great. - explorer - .render(horizontal[0], buf) - .expect("Failed to render Explorer"); - } self.draw_tabs(editor_layout[0], buf); - self.get_editor_mut().unwrap().render(editor_layout[1], buf); + for component in &mut self.components { + match component { + AppComponents::AppEditor(editor) => editor.render(editor_layout[1], buf), + AppComponents::AppExplorer(explorer) => { + // TODO: What to do about errors during rendering? + // Once there is a debug console, maybe log it and discard? Panic isn't great. + explorer + .render(horizontal[0], buf) + .expect("Failed to render Explorer"); + } + AppComponents::AppLogger(logger) => logger.render(vertical[2], buf), + AppComponents::AppComponent(_) => {} + } + } } } impl<'a> Component for App<'a> { fn id(&self) -> &str { - "app" + "App" } /// TODO: Get active widget with some Component trait function helper? @@ -245,6 +242,7 @@ impl<'a> Component for App<'a> { AppComponents::AppEditor(editor) => editor.handle_event(event.clone())?, AppComponents::AppExplorer(explorer) => explorer.handle_event(event.clone())?, AppComponents::AppComponent(comp) => comp.handle_event(event.clone())?, + AppComponents::AppLogger(logger) => logger.handle_event(event.clone())?, }; // Actions returned here abort the input handling iteration. match action { @@ -258,6 +256,20 @@ impl<'a> Component for App<'a> { /// Handles key events for the App Component only. fn handle_key_events(&mut self, key: KeyEvent) -> Result { match key { + KeyEvent { + code: KeyCode::Char('l'), + modifiers: KeyModifiers::CONTROL, + kind: KeyEventKind::Press, + state: _state, + } => { + // Some example logs for testing. + error!(target:self.id(), "an error"); + warn!(target:self.id(), "a warning"); + info!(target:self.id(), "a two line info\nsecond line"); + debug!(target:self.id(), "a debug"); + trace!(target:self.id(), "a trace"); + Ok(Action::Noop) + } KeyEvent { code: KeyCode::Char('c'), modifiers: KeyModifiers::CONTROL, diff --git a/src/tui/editor.rs b/src/tui/editor.rs index d6dc3a7..7dbf5fb 100644 --- a/src/tui/editor.rs +++ b/src/tui/editor.rs @@ -1,6 +1,6 @@ use crate::tui::component::{Action, Component}; -use anyhow::{bail, Context, Result}; +use anyhow::{Context, Result, bail}; use edtui::{ EditorEventHandler, EditorState, EditorTheme, EditorView, LineNumbers, Lines, SyntaxHighlighter, }; @@ -87,7 +87,7 @@ impl Widget for &mut Editor { impl Component for Editor { fn id(&self) -> &str { - "editor" + "Editor" } fn handle_event(&mut self, event: Event) -> Result { diff --git a/src/tui/explorer.rs b/src/tui/explorer.rs index c63e810..751d725 100644 --- a/src/tui/explorer.rs +++ b/src/tui/explorer.rs @@ -1,5 +1,5 @@ use crate::tui::component::{Action, Component}; -use anyhow::{bail, Context, Result}; +use anyhow::{Context, Result, bail}; use ratatui::buffer::Buffer; use ratatui::crossterm::event::{Event, KeyCode, KeyEvent, MouseEvent, MouseEventKind}; use ratatui::layout::{Alignment, Position, Rect}; @@ -113,7 +113,7 @@ impl<'a> Explorer<'a> { impl<'a> Component for Explorer<'a> { fn id(&self) -> &str { - "explorer" + "Explorer" } fn handle_event(&mut self, event: Event) -> Result { if let Some(key_event) = event.as_key_event() { diff --git a/src/tui/logger.rs b/src/tui/logger.rs new file mode 100644 index 0000000..53f2cd7 --- /dev/null +++ b/src/tui/logger.rs @@ -0,0 +1,76 @@ +use crate::tui::component::{Action, Component}; +use ratatui::buffer::Buffer; +use ratatui::crossterm::event::{Event, KeyCode, KeyEvent}; +use ratatui::layout::Rect; +use ratatui::style::{Color, Style}; +use ratatui::widgets::Widget; +use tui_logger::{TuiLoggerLevelOutput, TuiLoggerSmartWidget, TuiWidgetEvent, TuiWidgetState}; + +/// Any log written as info!(target:self.id(), "message") will work with this logger. +/// The logger is bound to info!, debug!, error!, trace! macros within Tui::new(). +pub struct Logger { + state: TuiWidgetState, +} + +impl Logger { + pub fn new() -> Self { + Self { + state: TuiWidgetState::new(), + } + } +} + +impl Widget for &Logger { + fn render(self, area: Rect, buf: &mut Buffer) + where + Self: Sized, + { + // TODO: Use output_file? + TuiLoggerSmartWidget::default() + .style_error(Style::default().fg(Color::Red)) + .style_debug(Style::default().fg(Color::Green)) + .style_warn(Style::default().fg(Color::Yellow)) + .style_trace(Style::default().fg(Color::Magenta)) + .style_info(Style::default().fg(Color::Cyan)) + .output_separator(':') + .output_timestamp(Some("%H:%M:%S".to_string())) + .output_level(Some(TuiLoggerLevelOutput::Abbreviated)) + .output_target(true) + .output_file(true) + .output_line(true) + .state(&self.state) + .render(area, buf); + } +} + +impl Component for Logger { + fn id(&self) -> &str { + "Logger" + } + + fn handle_event(&mut self, event: Event) -> anyhow::Result { + if let Some(key_event) = event.as_key_event() { + return self.handle_key_events(key_event); + } + Ok(Action::Noop) + } + + fn handle_key_events(&mut self, key: KeyEvent) -> anyhow::Result { + match key.code { + KeyCode::Char(' ') => self.state.transition(TuiWidgetEvent::SpaceKey), + KeyCode::Esc => self.state.transition(TuiWidgetEvent::EscapeKey), + KeyCode::PageUp => self.state.transition(TuiWidgetEvent::PrevPageKey), + KeyCode::PageDown => self.state.transition(TuiWidgetEvent::NextPageKey), + KeyCode::Up => self.state.transition(TuiWidgetEvent::UpKey), + KeyCode::Down => self.state.transition(TuiWidgetEvent::DownKey), + KeyCode::Left => self.state.transition(TuiWidgetEvent::LeftKey), + KeyCode::Right => self.state.transition(TuiWidgetEvent::RightKey), + KeyCode::Char('+') => self.state.transition(TuiWidgetEvent::PlusKey), + KeyCode::Char('-') => self.state.transition(TuiWidgetEvent::MinusKey), + KeyCode::Char('h') => self.state.transition(TuiWidgetEvent::HideKey), + KeyCode::Char('f') => self.state.transition(TuiWidgetEvent::FocusKey), + _ => (), + } + Ok(Action::Pass) + } +} -- 2.47.2 From 4d81cd51a6767cd8bc067504f992604b7f6f115d Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Tue, 20 Jan 2026 20:43:01 -0500 Subject: [PATCH 52/73] [tui] Add ComponentOf trait. I think it will help with fetching a component by type from the Components vector attached to App? --- src/tui/app.rs | 113 ++++++++++++++++++++++++++++++------------------- 1 file changed, 70 insertions(+), 43 deletions(-) diff --git a/src/tui/app.rs b/src/tui/app.rs index f10891a..eae2494 100644 --- a/src/tui/app.rs +++ b/src/tui/app.rs @@ -24,6 +24,59 @@ pub enum AppComponents<'a> { AppComponent(Box), } +/// Usage: get_component_mut::() OR get_component::() +/// +/// Implementing this trait for each AppComponent allows for easy lookup in the vector. +trait ComponentOf { + fn as_ref(&self) -> Option<&T>; + fn as_mut(&mut self) -> Option<&mut T>; +} + +impl<'a> ComponentOf for AppComponents<'a> { + fn as_ref(&self) -> Option<&Logger> { + if let AppComponents::AppLogger(ref e) = *self { + return Some(e); + } + None + } + fn as_mut(&mut self) -> Option<&mut Logger> { + if let AppComponents::AppLogger(ref mut e) = *self { + return Some(e); + } + None + } +} + +impl<'a> ComponentOf for AppComponents<'a> { + fn as_ref(&self) -> Option<&Editor> { + if let AppComponents::AppEditor(ref e) = *self { + return Some(e); + } + None + } + fn as_mut(&mut self) -> Option<&mut Editor> { + if let AppComponents::AppEditor(ref mut e) = *self { + return Some(e); + } + None + } +} + +impl<'a> ComponentOf> for AppComponents<'a> { + fn as_ref(&self) -> Option<&Explorer<'a>> { + if let AppComponents::AppExplorer(ref e) = *self { + return Some(e); + } + None + } + fn as_mut(&mut self) -> Option<&mut Explorer<'a>> { + if let AppComponents::AppExplorer(ref mut e) = *self { + return Some(e); + } + None + } +} + pub struct App<'a> { components: Vec>, } @@ -37,7 +90,7 @@ impl<'a> App<'a> { AppComponents::AppLogger(Logger::new()), ], }; - app.get_editor_mut() + app.get_component_mut::() .unwrap() .set_contents(&root_path.join("src/tui/app.rs")) .context(format!( @@ -47,44 +100,18 @@ impl<'a> App<'a> { Ok(app) } - fn get_explorer(&self) -> Result<&Explorer<'a>> { - for component in &self.components { - if let AppComponents::AppExplorer(explorer) = component { - return Ok(explorer); - } - } - bail!("Failed to find project explorer widget.") + fn get_component(&self) -> Option<&T> + where + AppComponents<'a>: ComponentOf, + { + self.components.iter().find_map(|c| c.as_ref()) } - fn get_explorer_mut(&mut self) -> Result<&mut Explorer<'a>> { - for component in &mut self.components { - if let AppComponents::AppExplorer(explorer) = component { - return Ok(explorer); - } - } - bail!("Failed to find project explorer widget.") - } - - fn get_editor(&self) -> Option<&Editor> { - for component in &self.components { - if let AppComponents::AppEditor(editor) = component { - return Some(editor); - } - } - - // There is no editor currently opened. - None - } - - fn get_editor_mut(&mut self) -> Option<&mut Editor> { - for component in &mut self.components { - if let AppComponents::AppEditor(editor) = component { - return Some(editor); - } - } - - // There is no editor currently opened. - None + fn get_component_mut(&mut self) -> Option<&mut T> + where + AppComponents<'a>: ComponentOf, + { + self.components.iter_mut().find_map(|c| c.as_mut()) } pub fn run(mut self, mut terminal: DefaultTerminal) -> Result<()> { @@ -121,7 +148,7 @@ impl<'a> App<'a> { fn draw_tabs(&self, area: Rect, buf: &mut Buffer) { // Determine the tab title from the current file (or use a fallback). let mut title: Option<&str> = None; - if let Some(editor) = self.get_editor() { + if let Some(editor) = self.get_component::() { title = editor .file_path .as_ref() @@ -144,12 +171,12 @@ impl<'a> App<'a> { /// If the selected item is not a file, this does nothing. fn refresh_editor_contents(&mut self) -> Result<()> { // Use the currently selected TreeItem or get an absolute path to this source file. - let selected_pathbuf = match self.get_explorer()?.selected() { + let selected_pathbuf = match self.get_component::().unwrap().selected() { Ok(path) => PathBuf::from(path), Err(_) => PathBuf::from(std::path::absolute(file!())?.to_string_lossy().to_string()), }; let editor = self - .get_editor_mut() + .get_component_mut::() .context("Failed to get active editor while refreshing contents.")?; if let Some(current_file_path) = editor.file_path.clone() { if selected_pathbuf == current_file_path || !selected_pathbuf.is_file() { @@ -194,15 +221,15 @@ impl<'a> Widget for &mut App<'a> { self.draw_status(vertical[0], buf); self.draw_tabs(editor_layout[0], buf); + let id = self.id().to_string(); for component in &mut self.components { match component { AppComponents::AppEditor(editor) => editor.render(editor_layout[1], buf), AppComponents::AppExplorer(explorer) => { - // TODO: What to do about errors during rendering? - // Once there is a debug console, maybe log it and discard? Panic isn't great. explorer .render(horizontal[0], buf) - .expect("Failed to render Explorer"); + .context("Failed to render Explorer") + .unwrap_or_else(|e| error!(target:id.as_str(), "{}", e)); } AppComponents::AppLogger(logger) => logger.render(vertical[2], buf), AppComponents::AppComponent(_) => {} -- 2.47.2 From a4413cd0527c2142fdc58244fe06d85337971883 Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Wed, 21 Jan 2026 20:28:24 -0500 Subject: [PATCH 53/73] [tui] Clean up logging. --- src/gui.rs | 3 ++- src/gui/filesystem.rs | 6 ++--- src/main.rs | 19 +++++++++------ src/tui.rs | 7 +++--- src/tui/app.rs | 54 ++++--------------------------------------- src/tui/editor.rs | 16 +++++++++++++ src/tui/explorer.rs | 16 +++++++++++++ src/tui/logger.rs | 16 +++++++++++++ 8 files changed, 73 insertions(+), 64 deletions(-) diff --git a/src/gui.rs b/src/gui.rs index b80df58..6aefef4 100644 --- a/src/gui.rs +++ b/src/gui.rs @@ -1,11 +1,12 @@ use anyhow::Result; use cxx_qt_lib::QString; +use log::trace; pub mod colors; pub mod filesystem; pub fn run(root_path: std::path::PathBuf) -> Result<()> { - println!("Starting the GUI editor at {:?}", root_path); + trace!(target:"gui::run()", "Starting the GUI editor at {root_path:?}"); use cxx_qt_lib::{QGuiApplication, QQmlApplicationEngine, QUrl}; diff --git a/src/gui/filesystem.rs b/src/gui/filesystem.rs index 2c39d7f..557203b 100644 --- a/src/gui/filesystem.rs +++ b/src/gui/filesystem.rs @@ -73,10 +73,10 @@ impl qobject::FileSystem { return QString::default(); } if !fs::metadata(path.to_string()) - .expect(format!("Failed to get file metadata {}", path).as_str()) + .expect(format!("Failed to get file metadata {path:?}").as_str()) .is_file() { - warn!("Attempted to open file {} that is not a valid file", path); + warn!(target:"FileSystem", "Attempted to open file {path:?} that is not a valid file"); return QString::default(); } let ss = SyntaxSet::load_defaults_nonewlines(); @@ -118,7 +118,7 @@ impl qobject::FileSystem { 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()) + .expect(format!("Failed to get metadata for path {path:?}").as_str()) .is_dir() { self.set_root_path(path) diff --git a/src/main.rs b/src/main.rs index e00528d..b672903 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,8 @@ +use crate::tui::Tui; use anyhow::{Context, Result}; use clap::Parser; -use ratatui::Terminal; -use ratatui::backend::CrosstermBackend; -use std::io::stdout; +use log::{info, trace}; use std::process::{Command, Stdio}; -use crate::tui::Tui; pub mod gui; pub mod tui; @@ -39,14 +37,21 @@ fn main() -> Result<()> { dirs::home_dir().context("Failed to obtain home directory")?, ), }; + info!(target:"main()", "Root path detected: {root_path:?}"); match args.gui { - true => gui::run(root_path), + true => { + trace!(target:"main()", "Starting GUI"); + gui::run(root_path) + } false => match args.tui { // Open the TUI editor if requested, otherwise use the QML GUI by default. - true => Ok(Tui::new(root_path)?.start()?), + true => { + trace!(target:"main()", "Starting TUI"); + Ok(Tui::new(root_path)?.start()?) + } false => { - // Relaunch the CLIDE GUI in a separate process. + trace!(target:"main()", "Starting GUI in a new process"); Command::new(std::env::current_exe()?) .args(&["--gui", root_path.to_str().unwrap()]) .stdout(Stdio::null()) diff --git a/src/tui.rs b/src/tui.rs index 83fc16e..356f9e0 100644 --- a/src/tui.rs +++ b/src/tui.rs @@ -5,7 +5,7 @@ mod explorer; mod logger; use anyhow::{Context, Result}; -use log::{LevelFilter, debug}; +use log::{LevelFilter, debug, info}; use ratatui::Terminal; use ratatui::backend::CrosstermBackend; use ratatui::crossterm::event::{ @@ -38,7 +38,7 @@ impl Tui { .output_file(false) .output_separator(':'); set_log_file(file_options); - debug!(target:"Tui", "Logging to file: {}", dir.to_str().unwrap()); + debug!(target:"Tui", "Logging to file: {dir:?}"); Ok(Self { terminal: Terminal::new(CrosstermBackend::new(stdout()))?, @@ -47,7 +47,7 @@ impl Tui { } pub fn start(self) -> Result<()> { - println!("Starting the TUI editor at {:?}", self.root_path); + info!(target:"Tui", "Starting the TUI editor at {:?}", self.root_path); ratatui::crossterm::execute!( stdout(), EnterAlternateScreen, @@ -64,6 +64,7 @@ impl Tui { } fn stop() -> Result<()> { + info!(target:"Tui", "Stopping the TUI editor"); disable_raw_mode()?; ratatui::crossterm::execute!( stdout(), diff --git a/src/tui/app.rs b/src/tui/app.rs index eae2494..55b319e 100644 --- a/src/tui/app.rs +++ b/src/tui/app.rs @@ -2,14 +2,14 @@ use crate::tui::component::{Action, Component}; use crate::tui::editor::Editor; use crate::tui::explorer::Explorer; use crate::tui::logger::Logger; -use anyhow::{Context, Result, anyhow, bail}; +use anyhow::{Context, Result, bail}; use log::{debug, error, info, trace, warn}; use ratatui::buffer::Buffer; use ratatui::crossterm::event; use ratatui::crossterm::event::{Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers}; use ratatui::layout::{Constraint, Direction, Layout, Rect}; use ratatui::prelude::{Color, Style, Widget}; -use ratatui::widgets::{Block, Borders, Padding, Paragraph, Tabs, Wrap}; +use ratatui::widgets::{Block, Borders, Padding, Tabs}; use ratatui::{DefaultTerminal, symbols}; use std::path::PathBuf; use std::time::Duration; @@ -27,56 +27,11 @@ pub enum AppComponents<'a> { /// Usage: get_component_mut::() OR get_component::() /// /// Implementing this trait for each AppComponent allows for easy lookup in the vector. -trait ComponentOf { +pub(crate) trait ComponentOf { fn as_ref(&self) -> Option<&T>; fn as_mut(&mut self) -> Option<&mut T>; } -impl<'a> ComponentOf for AppComponents<'a> { - fn as_ref(&self) -> Option<&Logger> { - if let AppComponents::AppLogger(ref e) = *self { - return Some(e); - } - None - } - fn as_mut(&mut self) -> Option<&mut Logger> { - if let AppComponents::AppLogger(ref mut e) = *self { - return Some(e); - } - None - } -} - -impl<'a> ComponentOf for AppComponents<'a> { - fn as_ref(&self) -> Option<&Editor> { - if let AppComponents::AppEditor(ref e) = *self { - return Some(e); - } - None - } - fn as_mut(&mut self) -> Option<&mut Editor> { - if let AppComponents::AppEditor(ref mut e) = *self { - return Some(e); - } - None - } -} - -impl<'a> ComponentOf> for AppComponents<'a> { - fn as_ref(&self) -> Option<&Explorer<'a>> { - if let AppComponents::AppExplorer(ref e) = *self { - return Some(e); - } - None - } - fn as_mut(&mut self) -> Option<&mut Explorer<'a>> { - if let AppComponents::AppExplorer(ref mut e) = *self { - return Some(e); - } - None - } -} - pub struct App<'a> { components: Vec>, } @@ -94,8 +49,7 @@ impl<'a> App<'a> { .unwrap() .set_contents(&root_path.join("src/tui/app.rs")) .context(format!( - "Failed to initialize editor contents to path: {}", - root_path.to_string_lossy() + "Failed to initialize editor contents to path: {root_path:?}" ))?; Ok(app) } diff --git a/src/tui/editor.rs b/src/tui/editor.rs index 7dbf5fb..d8c695e 100644 --- a/src/tui/editor.rs +++ b/src/tui/editor.rs @@ -1,5 +1,6 @@ use crate::tui::component::{Action, Component}; +use crate::tui::app::{AppComponents, ComponentOf}; use anyhow::{Context, Result, bail}; use edtui::{ EditorEventHandler, EditorState, EditorTheme, EditorView, LineNumbers, Lines, SyntaxHighlighter, @@ -22,6 +23,21 @@ pub struct Editor { syntax_set: SyntaxSet, } +impl<'a> ComponentOf for AppComponents<'a> { + fn as_ref(&self) -> Option<&Editor> { + if let AppComponents::AppEditor(ref e) = *self { + return Some(e); + } + None + } + fn as_mut(&mut self) -> Option<&mut Editor> { + if let AppComponents::AppEditor(ref mut e) = *self { + return Some(e); + } + None + } +} + impl Editor { pub fn new() -> Self { Editor { diff --git a/src/tui/explorer.rs b/src/tui/explorer.rs index 751d725..7e83632 100644 --- a/src/tui/explorer.rs +++ b/src/tui/explorer.rs @@ -1,3 +1,4 @@ +use crate::tui::app::{AppComponents, ComponentOf}; use crate::tui::component::{Action, Component}; use anyhow::{Context, Result, bail}; use ratatui::buffer::Buffer; @@ -16,6 +17,21 @@ pub struct Explorer<'a> { tree_state: TreeState, } +impl<'a> ComponentOf> for AppComponents<'a> { + fn as_ref(&self) -> Option<&Explorer<'a>> { + if let AppComponents::AppExplorer(ref e) = *self { + return Some(e); + } + None + } + fn as_mut(&mut self) -> Option<&mut Explorer<'a>> { + if let AppComponents::AppExplorer(ref mut e) = *self { + return Some(e); + } + None + } +} + impl<'a> Explorer<'a> { pub fn new(path: &std::path::PathBuf) -> Result { let explorer = Explorer { diff --git a/src/tui/logger.rs b/src/tui/logger.rs index 53f2cd7..70a7d23 100644 --- a/src/tui/logger.rs +++ b/src/tui/logger.rs @@ -1,3 +1,4 @@ +use crate::tui::app::{AppComponents, ComponentOf}; use crate::tui::component::{Action, Component}; use ratatui::buffer::Buffer; use ratatui::crossterm::event::{Event, KeyCode, KeyEvent}; @@ -12,6 +13,21 @@ pub struct Logger { state: TuiWidgetState, } +impl<'a> ComponentOf for AppComponents<'a> { + fn as_ref(&self) -> Option<&Logger> { + if let AppComponents::AppLogger(ref e) = *self { + return Some(e); + } + None + } + fn as_mut(&mut self) -> Option<&mut Logger> { + if let AppComponents::AppLogger(ref mut e) = *self { + return Some(e); + } + None + } +} + impl Logger { pub fn new() -> Self { Self { -- 2.47.2 From 0c87fda7957285b0d105767bb9e359f43bbc14d6 Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Thu, 22 Jan 2026 19:23:21 -0500 Subject: [PATCH 54/73] [tui] Add basic support for focusing widgets. It's pretty bad but it allows to control which widget accepts input. --- Cargo.lock | 29 +++++++++--------- Cargo.toml | 1 + src/tui/app.rs | 70 ++++++++++++++++++++++++++++++++++++-------- src/tui/component.rs | 48 ++++++++++++++++++++++++++++++ src/tui/editor.rs | 8 ++++- src/tui/explorer.rs | 9 +++++- src/tui/logger.rs | 8 ++++- 7 files changed, 143 insertions(+), 30 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 795325c..063f626 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -299,6 +299,7 @@ dependencies = [ "edtui", "log", "ratatui", + "strum", "syntect", "tui-logger", "tui-tree-widget", @@ -451,9 +452,9 @@ dependencies = [ [[package]] name = "cxx" -version = "1.0.192" +version = "1.0.194" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbda285ba6e5866529faf76352bdf73801d9b44a6308d7cd58ca2379f378e994" +checksum = "747d8437319e3a2f43d93b341c137927ca70c0f5dabeea7a005a73665e247c7e" dependencies = [ "cc", "cxx-build", @@ -466,9 +467,9 @@ dependencies = [ [[package]] name = "cxx-build" -version = "1.0.192" +version = "1.0.194" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af9efde466c5d532d57efd92f861da3bdb7f61e369128ce8b4c3fe0c9de4fa4d" +checksum = "b0f4697d190a142477b16aef7da8a99bfdc41e7e8b1687583c0d23a79c7afc1e" dependencies = [ "cc", "codespan-reporting 0.13.1", @@ -481,9 +482,9 @@ dependencies = [ [[package]] name = "cxx-gen" -version = "0.7.192" +version = "0.7.194" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee08d1131e8f050a1d1acbb7c699e5c8d29c325dffc382331c280d99f98c2618" +checksum = "035b6c61a944483e8a4b2ad4fb8b13830d63491bd004943716ad16d85dcc64bc" dependencies = [ "codespan-reporting 0.13.1", "indexmap", @@ -564,9 +565,9 @@ dependencies = [ [[package]] name = "cxxbridge-cmd" -version = "1.0.192" +version = "1.0.194" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3efb93799095bccd4f763ca07997dc39a69e5e61ab52d2c407d4988d21ce144d" +checksum = "d0956799fa8678d4c50eed028f2de1c0552ae183c76e976cf7ca8c4e36a7c328" dependencies = [ "clap", "codespan-reporting 0.13.1", @@ -578,15 +579,15 @@ dependencies = [ [[package]] name = "cxxbridge-flags" -version = "1.0.192" +version = "1.0.194" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3092010228026e143b32a4463ed9fa8f86dca266af4bf5f3b2a26e113dbe4e45" +checksum = "23384a836ab4f0ad98ace7e3955ad2de39de42378ab487dc28d3990392cb283a" [[package]] name = "cxxbridge-macro" -version = "1.0.192" +version = "1.0.194" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31d72ebfcd351ae404fb00ff378dfc9571827a00722c9e735c9181aec320ba0a" +checksum = "e6acc6b5822b9526adfb4fc377b67128fdd60aac757cc4a741a6278603f763cf" dependencies = [ "indexmap", "proc-macro2", @@ -1599,9 +1600,9 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "proc-macro2" -version = "1.0.105" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] diff --git a/Cargo.toml b/Cargo.toml index de9de16..c9b44f5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ anyhow = "1.0.100" tui-tree-widget = "0.24.0" tui-logger = "0.18.1" edtui = "0.11.1" +strum = "0.27.2" [build-dependencies] # The link_qt_object_files feature is required for statically linking Qt 6. diff --git a/src/tui/app.rs b/src/tui/app.rs index 55b319e..bf244ab 100644 --- a/src/tui/app.rs +++ b/src/tui/app.rs @@ -1,4 +1,4 @@ -use crate::tui::component::{Action, Component}; +use crate::tui::component::{Action, Component, Focus, FocusState}; use crate::tui::editor::Editor; use crate::tui::explorer::Explorer; use crate::tui::logger::Logger; @@ -21,6 +21,7 @@ pub enum AppComponents<'a> { AppEditor(Editor), AppExplorer(Explorer<'a>), AppLogger(Logger), + #[allow(dead_code)] AppComponent(Box), } @@ -45,12 +46,13 @@ impl<'a> App<'a> { AppComponents::AppLogger(Logger::new()), ], }; - app.get_component_mut::() - .unwrap() + let editor = app.get_component_mut::().unwrap(); + editor .set_contents(&root_path.join("src/tui/app.rs")) .context(format!( "Failed to initialize editor contents to path: {root_path:?}" ))?; + editor.component_state.set_focus(Focus::Active); Ok(app) } @@ -219,13 +221,20 @@ impl<'a> Component for App<'a> { // Handle events for all components. for component in &mut self.components { - let action = match component { - AppComponents::AppEditor(editor) => editor.handle_event(event.clone())?, - AppComponents::AppExplorer(explorer) => explorer.handle_event(event.clone())?, - AppComponents::AppComponent(comp) => comp.handle_event(event.clone())?, - AppComponents::AppLogger(logger) => logger.handle_event(event.clone())?, + let c = match component { + AppComponents::AppEditor(e) => e as &mut dyn Component, + AppComponents::AppExplorer(e) => e as &mut dyn Component, + AppComponents::AppLogger(e) => e as &mut dyn Component, + AppComponents::AppComponent(e) => e.as_mut() as &mut dyn Component, }; - // Actions returned here abort the input handling iteration. + if !c.is_active() { + if let Some(mouse) = event.as_mouse_event() { + // Always handle mouse events for click interaction. + c.handle_mouse_events(mouse)?; + } + continue; + } + let action = c.handle_event(event.clone())?; match action { Action::Quit | Action::Handled => return Ok(action), _ => {} @@ -238,18 +247,53 @@ impl<'a> Component for App<'a> { fn handle_key_events(&mut self, key: KeyEvent) -> Result { match key { KeyEvent { - code: KeyCode::Char('l'), - modifiers: KeyModifiers::CONTROL, + code: KeyCode::Char('q'), + modifiers: KeyModifiers::ALT, + kind: KeyEventKind::Press, + state: _state, + } => { + self.get_component_mut::() + .unwrap() + .component_state + .toggle_focus(); + Ok(Action::Handled) + } + KeyEvent { + code: KeyCode::Char('w'), + modifiers: KeyModifiers::ALT, + kind: KeyEventKind::Press, + state: _state, + } => { + self.get_component_mut::() + .unwrap() + .component_state + .toggle_focus(); + Ok(Action::Handled) + } + KeyEvent { + code: KeyCode::Char('e'), + modifiers: KeyModifiers::ALT, + kind: KeyEventKind::Press, + state: _state, + } => { + self.get_component_mut::() + .unwrap() + .component_state + .toggle_focus(); + Ok(Action::Handled) + } + KeyEvent { + code: KeyCode::Char('l'), + modifiers: KeyModifiers::ALT, kind: KeyEventKind::Press, state: _state, } => { - // Some example logs for testing. error!(target:self.id(), "an error"); warn!(target:self.id(), "a warning"); info!(target:self.id(), "a two line info\nsecond line"); debug!(target:self.id(), "a debug"); trace!(target:self.id(), "a trace"); - Ok(Action::Noop) + Ok(Action::Handled) } KeyEvent { code: KeyCode::Char('c'), diff --git a/src/tui/component.rs b/src/tui/component.rs index 99dd7c9..0ac02dd 100644 --- a/src/tui/component.rs +++ b/src/tui/component.rs @@ -44,4 +44,52 @@ pub trait Component { fn update(&mut self, action: Action) -> Result { Ok(Action::Noop) } + + /// Override this method for creating components that conditionally handle input. + fn is_active(&self) -> bool { + true + } +} + +#[derive(Debug, Clone, Copy, Default)] +pub struct ComponentState { + pub(crate) focus: Focus, +} + +impl ComponentState { + fn new() -> Self { + Self { + focus: Focus::Active, + } + } +} + +#[derive(Debug, Clone, Copy, Default, PartialEq)] +pub enum Focus { + Active, + #[default] + Inactive, +} + +pub trait FocusState { + fn with_focus(self, focus: Focus) -> Self; + fn set_focus(&mut self, focus: Focus); + fn toggle_focus(&mut self); +} + +impl FocusState for ComponentState { + fn with_focus(self, focus: Focus) -> Self { + Self { focus } + } + + fn set_focus(&mut self, focus: Focus) { + self.focus = focus; + } + + fn toggle_focus(&mut self) { + match self.focus { + Focus::Active => self.set_focus(Focus::Inactive), + Focus::Inactive => self.set_focus(Focus::Active), + } + } } diff --git a/src/tui/editor.rs b/src/tui/editor.rs index d8c695e..5bba941 100644 --- a/src/tui/editor.rs +++ b/src/tui/editor.rs @@ -1,4 +1,4 @@ -use crate::tui::component::{Action, Component}; +use crate::tui::component::{Action, Component, ComponentState, Focus}; use crate::tui::app::{AppComponents, ComponentOf}; use anyhow::{Context, Result, bail}; @@ -21,6 +21,7 @@ pub struct Editor { pub event_handler: EditorEventHandler, pub file_path: Option, syntax_set: SyntaxSet, + pub(crate) component_state: ComponentState, } impl<'a> ComponentOf for AppComponents<'a> { @@ -45,6 +46,7 @@ impl Editor { event_handler: EditorEventHandler::default(), file_path: None, syntax_set: SyntaxSet::load_defaults_nonewlines(), + component_state: Default::default(), } } @@ -106,6 +108,10 @@ impl Component for Editor { "Editor" } + fn is_active(&self) -> bool { + self.component_state.focus == Focus::Active + } + fn handle_event(&mut self, event: Event) -> Result { if let Some(key_event) = event.as_key_event() { // Handle events here that should not be passed on to the vim emulation handler. diff --git a/src/tui/explorer.rs b/src/tui/explorer.rs index 7e83632..fcb4a05 100644 --- a/src/tui/explorer.rs +++ b/src/tui/explorer.rs @@ -1,5 +1,5 @@ use crate::tui::app::{AppComponents, ComponentOf}; -use crate::tui::component::{Action, Component}; +use crate::tui::component::{Action, Component, ComponentState, Focus}; use anyhow::{Context, Result, bail}; use ratatui::buffer::Buffer; use ratatui::crossterm::event::{Event, KeyCode, KeyEvent, MouseEvent, MouseEventKind}; @@ -15,6 +15,7 @@ pub struct Explorer<'a> { root_path: std::path::PathBuf, tree_items: TreeItem<'a, String>, tree_state: TreeState, + pub(crate) component_state: ComponentState, } impl<'a> ComponentOf> for AppComponents<'a> { @@ -38,6 +39,7 @@ impl<'a> Explorer<'a> { root_path: path.to_owned(), tree_items: Self::build_tree_from_path(path.to_owned())?, tree_state: TreeState::default(), + component_state: Default::default(), }; Ok(explorer) } @@ -131,6 +133,11 @@ impl<'a> Component for Explorer<'a> { fn id(&self) -> &str { "Explorer" } + + fn is_active(&self) -> bool { + self.component_state.focus == Focus::Active + } + fn handle_event(&mut self, event: Event) -> Result { if let Some(key_event) = event.as_key_event() { // Handle events here that should not be passed on to the vim emulation handler. diff --git a/src/tui/logger.rs b/src/tui/logger.rs index 70a7d23..abf33bb 100644 --- a/src/tui/logger.rs +++ b/src/tui/logger.rs @@ -1,5 +1,5 @@ use crate::tui::app::{AppComponents, ComponentOf}; -use crate::tui::component::{Action, Component}; +use crate::tui::component::{Action, Component, ComponentState, Focus}; use ratatui::buffer::Buffer; use ratatui::crossterm::event::{Event, KeyCode, KeyEvent}; use ratatui::layout::Rect; @@ -11,6 +11,7 @@ use tui_logger::{TuiLoggerLevelOutput, TuiLoggerSmartWidget, TuiWidgetEvent, Tui /// The logger is bound to info!, debug!, error!, trace! macros within Tui::new(). pub struct Logger { state: TuiWidgetState, + pub(crate) component_state: ComponentState, } impl<'a> ComponentOf for AppComponents<'a> { @@ -32,6 +33,7 @@ impl Logger { pub fn new() -> Self { Self { state: TuiWidgetState::new(), + component_state: Default::default(), } } } @@ -64,6 +66,10 @@ impl Component for Logger { "Logger" } + fn is_active(&self) -> bool { + self.component_state.focus == Focus::Active + } + fn handle_event(&mut self, event: Event) -> anyhow::Result { if let Some(key_event) = event.as_key_event() { return self.handle_key_events(key_event); -- 2.47.2 From a3c1065f96ceaa22bcf50f94dfab245cd85eb8b6 Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Thu, 22 Jan 2026 20:35:40 -0500 Subject: [PATCH 55/73] [tui] Add bottom status bar with help text. Fixes #3 --- src/tui/app.rs | 28 +++++++++++++++++++++++----- src/tui/component.rs | 14 ++++++++++++-- src/tui/editor.rs | 2 +- src/tui/explorer.rs | 2 +- src/tui/logger.rs | 6 +++++- 5 files changed, 42 insertions(+), 10 deletions(-) diff --git a/src/tui/app.rs b/src/tui/app.rs index bf244ab..a356f5b 100644 --- a/src/tui/app.rs +++ b/src/tui/app.rs @@ -9,7 +9,8 @@ use ratatui::crossterm::event; use ratatui::crossterm::event::{Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers}; use ratatui::layout::{Constraint, Direction, Layout, Rect}; use ratatui::prelude::{Color, Style, Widget}; -use ratatui::widgets::{Block, Borders, Padding, Tabs}; +use ratatui::text::Text; +use ratatui::widgets::{Block, Borders, Padding, Paragraph, Tabs, Wrap}; use ratatui::{DefaultTerminal, symbols}; use std::path::PathBuf; use std::time::Duration; @@ -93,7 +94,7 @@ impl<'a> App<'a> { Ok(()) } - fn draw_status(&self, area: Rect, buf: &mut Buffer) { + fn draw_top_status(&self, area: Rect, buf: &mut Buffer) { // TODO: Status bar should have drop down menus Tabs::new(["File", "Edit", "View", "Help"]) .style(Style::default()) @@ -101,6 +102,21 @@ impl<'a> App<'a> { .render(area, buf); } + fn draw_bottom_status(&self, area: Rect, buf: &mut Buffer) { + // TODO: Set help text based on most recent component enabled. + Paragraph::new( + self.get_component::() + .unwrap() + .component_state + .help_text + .clone(), + ) + .style(Color::Gray) + .wrap(Wrap { trim: false }) + .centered() + .render(area, buf); + } + fn draw_tabs(&self, area: Rect, buf: &mut Buffer) { // Determine the tab title from the current file (or use a fallback). let mut title: Option<&str> = None; @@ -153,9 +169,10 @@ impl<'a> Widget for &mut App<'a> { let vertical = Layout::default() .direction(Direction::Vertical) .constraints([ - Constraint::Length(3), // status bar + Constraint::Length(3), // top status bar Constraint::Percentage(70), // horizontal layout Constraint::Percentage(30), // terminal + Constraint::Length(3), // bottom status bar ]) .split(area); @@ -175,7 +192,8 @@ impl<'a> Widget for &mut App<'a> { ]) .split(horizontal[1]); - self.draw_status(vertical[0], buf); + self.draw_top_status(vertical[0], buf); + self.draw_bottom_status(vertical[3], buf); self.draw_tabs(editor_layout[0], buf); let id = self.id().to_string(); for component in &mut self.components { @@ -225,7 +243,7 @@ impl<'a> Component for App<'a> { AppComponents::AppEditor(e) => e as &mut dyn Component, AppComponents::AppExplorer(e) => e as &mut dyn Component, AppComponents::AppLogger(e) => e as &mut dyn Component, - AppComponents::AppComponent(e) => e.as_mut() as &mut dyn Component, + AppComponents::AppComponent(e) => e.as_mut(), }; if !c.is_active() { if let Some(mouse) = event.as_mouse_event() { diff --git a/src/tui/component.rs b/src/tui/component.rs index 0ac02dd..e06b86b 100644 --- a/src/tui/component.rs +++ b/src/tui/component.rs @@ -51,17 +51,24 @@ pub trait Component { } } -#[derive(Debug, Clone, Copy, Default)] +#[derive(Debug, Clone, Default)] pub struct ComponentState { pub(crate) focus: Focus, + pub(crate) help_text: String, } impl ComponentState { fn new() -> Self { Self { focus: Focus::Active, + help_text: String::new(), } } + + pub(crate) fn with_help_text(mut self, help_text: &str) -> Self { + self.help_text = help_text.into(); + self + } } #[derive(Debug, Clone, Copy, Default, PartialEq)] @@ -79,7 +86,10 @@ pub trait FocusState { impl FocusState for ComponentState { fn with_focus(self, focus: Focus) -> Self { - Self { focus } + Self { + focus, + help_text: self.help_text, + } } fn set_focus(&mut self, focus: Focus) { diff --git a/src/tui/editor.rs b/src/tui/editor.rs index 5bba941..282cd1e 100644 --- a/src/tui/editor.rs +++ b/src/tui/editor.rs @@ -46,7 +46,7 @@ impl Editor { event_handler: EditorEventHandler::default(), file_path: None, syntax_set: SyntaxSet::load_defaults_nonewlines(), - component_state: Default::default(), + component_state: ComponentState::default().with_help_text("TODO: Vim help text"), } } diff --git a/src/tui/explorer.rs b/src/tui/explorer.rs index fcb4a05..d914da5 100644 --- a/src/tui/explorer.rs +++ b/src/tui/explorer.rs @@ -39,7 +39,7 @@ impl<'a> Explorer<'a> { root_path: path.to_owned(), tree_items: Self::build_tree_from_path(path.to_owned())?, tree_state: TreeState::default(), - component_state: Default::default(), + component_state: ComponentState::default().with_help_text("TODO: Explorer help text."), }; Ok(explorer) } diff --git a/src/tui/logger.rs b/src/tui/logger.rs index abf33bb..3c859b0 100644 --- a/src/tui/logger.rs +++ b/src/tui/logger.rs @@ -33,7 +33,11 @@ impl Logger { pub fn new() -> Self { Self { state: TuiWidgetState::new(), - component_state: Default::default(), + component_state: ComponentState::default().with_help_text(concat!( + "Q: Quit | Tab: Switch state | ↑/↓: Select target | f: Focus target", + " | ←/→: Display level | +/-: Filter level | Space: Toggle hidden targets", + " | h: Hide target selector | PageUp/Down: Scroll | Esc: Cancel scroll" + )), } } } -- 2.47.2 From 029e0b2952a300034ff090162c5d5ed69eb285c4 Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Sat, 24 Jan 2026 10:47:29 -0500 Subject: [PATCH 56/73] [tui] Remove AppComponent data. It just seems to be simpler this way. --- src/tui.rs | 11 +- src/tui/app.rs | 242 +++++++++++++++++++------------------------ src/tui/component.rs | 4 - src/tui/editor.rs | 33 ++---- src/tui/explorer.rs | 39 +++---- src/tui/logger.rs | 35 ++----- 6 files changed, 142 insertions(+), 222 deletions(-) diff --git a/src/tui.rs b/src/tui.rs index 356f9e0..fc67c72 100644 --- a/src/tui.rs +++ b/src/tui.rs @@ -33,10 +33,13 @@ impl Tui { let mut dir = env::temp_dir(); dir.push("clide.log"); - let file_options = TuiLoggerFile::new(dir.to_str().unwrap()) - .output_level(Some(TuiLoggerLevelOutput::Abbreviated)) - .output_file(false) - .output_separator(':'); + let file_options = TuiLoggerFile::new( + dir.to_str() + .context("Failed to set temp directory for file logging")?, + ) + .output_level(Some(TuiLoggerLevelOutput::Abbreviated)) + .output_file(false) + .output_separator(':'); set_log_file(file_options); debug!(target:"Tui", "Logging to file: {dir:?}"); diff --git a/src/tui/app.rs b/src/tui/app.rs index a356f5b..d2140c7 100644 --- a/src/tui/app.rs +++ b/src/tui/app.rs @@ -1,15 +1,15 @@ +use crate::tui::app::AppComponent::{AppEditor, AppExplorer, AppLogger}; use crate::tui::component::{Action, Component, Focus, FocusState}; use crate::tui::editor::Editor; use crate::tui::explorer::Explorer; use crate::tui::logger::Logger; -use anyhow::{Context, Result, bail}; +use anyhow::{Context, Result}; use log::{debug, error, info, trace, warn}; use ratatui::buffer::Buffer; use ratatui::crossterm::event; use ratatui::crossterm::event::{Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers}; use ratatui::layout::{Constraint, Direction, Layout, Rect}; use ratatui::prelude::{Color, Style, Widget}; -use ratatui::text::Text; use ratatui::widgets::{Block, Borders, Padding, Paragraph, Tabs, Wrap}; use ratatui::{DefaultTerminal, symbols}; use std::path::PathBuf; @@ -17,61 +17,50 @@ use std::time::Duration; // TODO: Need a way to dynamically run Widget::render on all widgets. // TODO: + Need a way to map Rect to Component::id() to position each widget? -// TODO: Need a way to dynamically run Component methods on all widgets. -pub enum AppComponents<'a> { - AppEditor(Editor), - AppExplorer(Explorer<'a>), - AppLogger(Logger), - #[allow(dead_code)] - AppComponent(Box), -} - -/// Usage: get_component_mut::() OR get_component::() -/// -/// Implementing this trait for each AppComponent allows for easy lookup in the vector. -pub(crate) trait ComponentOf { - fn as_ref(&self) -> Option<&T>; - fn as_mut(&mut self) -> Option<&mut T>; +// TODO: Need a good way to dynamically run Component methods on all widgets. +#[derive(PartialEq)] +pub enum AppComponent { + AppEditor, + AppExplorer, + AppLogger, } pub struct App<'a> { - components: Vec>, + editor: Editor, + explorer: Explorer<'a>, + logger: Logger, + last_active: AppComponent, } impl<'a> App<'a> { + pub fn id() -> &'static str { + "App" + } + pub fn new(root_path: PathBuf) -> Result { - let mut app = Self { - components: vec![ - AppComponents::AppExplorer(Explorer::new(&root_path)?), - AppComponents::AppEditor(Editor::new()), - AppComponents::AppLogger(Logger::new()), - ], + let app = Self { + editor: Editor::new(), + explorer: Explorer::new(&root_path)?, + logger: Logger::new(), + last_active: AppEditor, }; - let editor = app.get_component_mut::().unwrap(); - editor + Ok(app) + } + + /// Logic that should be executed once on application startup. + pub fn start(&mut self) -> Result<()> { + let root_path = self.explorer.root_path.clone(); + self.editor .set_contents(&root_path.join("src/tui/app.rs")) .context(format!( "Failed to initialize editor contents to path: {root_path:?}" ))?; - editor.component_state.set_focus(Focus::Active); - Ok(app) - } - - fn get_component(&self) -> Option<&T> - where - AppComponents<'a>: ComponentOf, - { - self.components.iter().find_map(|c| c.as_ref()) - } - - fn get_component_mut(&mut self) -> Option<&mut T> - where - AppComponents<'a>: ComponentOf, - { - self.components.iter_mut().find_map(|c| c.as_mut()) + self.editor.component_state.set_focus(Focus::Active); + Ok(()) } pub fn run(mut self, mut terminal: DefaultTerminal) -> Result<()> { + self.start()?; loop { self.refresh_editor_contents() .context("Failed to refresh editor contents.")?; @@ -80,7 +69,6 @@ impl<'a> App<'a> { f.render_widget(&mut self, f.area()); })?; - // TODO: Handle events based on which component is active. if event::poll(Duration::from_millis(250)).context("event poll failed")? { match self.handle_event(event::read()?)? { Action::Quit => break, @@ -103,32 +91,29 @@ impl<'a> App<'a> { } fn draw_bottom_status(&self, area: Rect, buf: &mut Buffer) { - // TODO: Set help text based on most recent component enabled. - Paragraph::new( - self.get_component::() - .unwrap() - .component_state - .help_text - .clone(), - ) - .style(Color::Gray) - .wrap(Wrap { trim: false }) - .centered() - .render(area, buf); + // Determine help text from the most recently focused component. + let help = match self.last_active { + AppEditor => self.editor.component_state.help_text.clone(), + AppExplorer => self.explorer.component_state.help_text.clone(), + AppLogger => self.logger.component_state.help_text.clone(), + }; + Paragraph::new(help) + .style(Color::Gray) + .wrap(Wrap { trim: false }) + .centered() + .render(area, buf); } fn draw_tabs(&self, area: Rect, buf: &mut Buffer) { // Determine the tab title from the current file (or use a fallback). - let mut title: Option<&str> = None; - if let Some(editor) = self.get_component::() { - title = editor - .file_path - .as_ref() - .and_then(|p| p.file_name()) - .and_then(|s| s.to_str()) - } - - Tabs::new(vec![title.unwrap_or("Unknown")]) + if let Some(title) = self.editor.file_path.clone() { + Tabs::new(vec![ + title + .file_name() + .map(|f| f.to_str()) + .unwrap_or(Some("Unknown")) + .unwrap(), + ]) .divider(symbols::DOT) .block( Block::default() @@ -137,30 +122,44 @@ impl<'a> App<'a> { ) .highlight_style(Style::default().fg(Color::LightRed)) .render(area, buf); + } else { + error!(target:Self::id(), "Failed to get Editor file_path while drawing Tabs widget."); + } + } + + fn change_focus(&mut self, focus: AppComponent) { + if self.last_active == AppEditor { + self.editor.state.cursor.row = 0; + self.editor.state.cursor.col = 0; + } + match focus { + AppEditor => self.editor.component_state.set_focus(Focus::Active), + AppExplorer => self.explorer.component_state.set_focus(Focus::Active), + AppLogger => self.logger.component_state.set_focus(Focus::Active), + } + self.last_active = focus; } /// Refresh the contents of the editor to match the selected TreeItem in the file Explorer. /// If the selected item is not a file, this does nothing. fn refresh_editor_contents(&mut self) -> Result<()> { // Use the currently selected TreeItem or get an absolute path to this source file. - let selected_pathbuf = match self.get_component::().unwrap().selected() { + let selected_pathbuf = match self.explorer.selected() { Ok(path) => PathBuf::from(path), Err(_) => PathBuf::from(std::path::absolute(file!())?.to_string_lossy().to_string()), }; - let editor = self - .get_component_mut::() - .context("Failed to get active editor while refreshing contents.")?; - if let Some(current_file_path) = editor.file_path.clone() { - if selected_pathbuf == current_file_path || !selected_pathbuf.is_file() { - return Ok(()); - } - return editor.set_contents(&selected_pathbuf); + let current_file_path = self + .editor + .file_path + .clone() + .context("Failed to get Editor current file_path")?; + if selected_pathbuf == current_file_path || !selected_pathbuf.is_file() { + return Ok(()); } - bail!("Failed to refresh editor contents") + self.editor.set_contents(&selected_pathbuf) } } -// TODO: Separate complex components into their own widgets. impl<'a> Widget for &mut App<'a> { fn render(self, area: Rect, buf: &mut Buffer) where @@ -195,35 +194,17 @@ impl<'a> Widget for &mut App<'a> { self.draw_top_status(vertical[0], buf); self.draw_bottom_status(vertical[3], buf); self.draw_tabs(editor_layout[0], buf); - let id = self.id().to_string(); - for component in &mut self.components { - match component { - AppComponents::AppEditor(editor) => editor.render(editor_layout[1], buf), - AppComponents::AppExplorer(explorer) => { - explorer - .render(horizontal[0], buf) - .context("Failed to render Explorer") - .unwrap_or_else(|e| error!(target:id.as_str(), "{}", e)); - } - AppComponents::AppLogger(logger) => logger.render(vertical[2], buf), - AppComponents::AppComponent(_) => {} - } - } + let id = App::id().to_string(); + self.editor.render(editor_layout[1], buf); + self.explorer + .render(horizontal[0], buf) + .context("Failed to render Explorer") + .unwrap_or_else(|e| error!(target:id.as_str(), "{}", e)); + self.logger.render(vertical[2], buf); } } impl<'a> Component for App<'a> { - fn id(&self) -> &str { - "App" - } - - /// TODO: Get active widget with some Component trait function helper? - /// trait Component { fn get_state() -> ComponentState; } - /// if component.get_state() = ComponentState::Active { component.handle_event(); } - /// - /// App could then provide helpers for altering Component state based on TUI grouping.. - /// (such as editor tabs, file explorer, status bars, etc..) - /// /// Handles events for the App and delegates to attached Components. fn handle_event(&mut self, event: Event) -> Result { // Handle events in the primary application. @@ -238,25 +219,21 @@ impl<'a> Component for App<'a> { } // Handle events for all components. - for component in &mut self.components { - let c = match component { - AppComponents::AppEditor(e) => e as &mut dyn Component, - AppComponents::AppExplorer(e) => e as &mut dyn Component, - AppComponents::AppLogger(e) => e as &mut dyn Component, - AppComponents::AppComponent(e) => e.as_mut(), - }; - if !c.is_active() { - if let Some(mouse) = event.as_mouse_event() { - // Always handle mouse events for click interaction. - c.handle_mouse_events(mouse)?; - } - continue; - } - let action = c.handle_event(event.clone())?; - match action { - Action::Quit | Action::Handled => return Ok(action), - _ => {} - } + let action = match self.last_active { + AppEditor => self.editor.handle_event(event)?, + AppExplorer => self.explorer.handle_event(event)?, + AppLogger => self.logger.handle_event(event)?, + }; + // if !c.is_active() { + // if let Some(mouse) = event.as_mouse_event() { + // // Always handle mouse events for click interaction. + // c.handle_mouse_events(mouse)?; + // } + // continue; + // } + match action { + Action::Quit | Action::Handled => return Ok(action), + _ => {} } Ok(Action::Noop) } @@ -270,10 +247,7 @@ impl<'a> Component for App<'a> { kind: KeyEventKind::Press, state: _state, } => { - self.get_component_mut::() - .unwrap() - .component_state - .toggle_focus(); + self.change_focus(AppExplorer); Ok(Action::Handled) } KeyEvent { @@ -282,10 +256,7 @@ impl<'a> Component for App<'a> { kind: KeyEventKind::Press, state: _state, } => { - self.get_component_mut::() - .unwrap() - .component_state - .toggle_focus(); + self.change_focus(AppEditor); Ok(Action::Handled) } KeyEvent { @@ -294,10 +265,7 @@ impl<'a> Component for App<'a> { kind: KeyEventKind::Press, state: _state, } => { - self.get_component_mut::() - .unwrap() - .component_state - .toggle_focus(); + self.change_focus(AppLogger); Ok(Action::Handled) } KeyEvent { @@ -306,11 +274,11 @@ impl<'a> Component for App<'a> { kind: KeyEventKind::Press, state: _state, } => { - error!(target:self.id(), "an error"); - warn!(target:self.id(), "a warning"); - info!(target:self.id(), "a two line info\nsecond line"); - debug!(target:self.id(), "a debug"); - trace!(target:self.id(), "a trace"); + error!(target:App::id(), "an error"); + warn!(target:App::id(), "a warning"); + info!(target:App::id(), "a two line info\nsecond line"); + debug!(target:App::id(), "a debug"); + trace!(target:App::id(), "a trace"); Ok(Action::Handled) } KeyEvent { diff --git a/src/tui/component.rs b/src/tui/component.rs index e06b86b..0a98b84 100644 --- a/src/tui/component.rs +++ b/src/tui/component.rs @@ -22,10 +22,6 @@ pub enum Action { } pub trait Component { - /// Returns a unique identifier for the component. - /// This is used for lookup in a container of Components. - fn id(&self) -> &str; - fn handle_event(&mut self, event: Event) -> Result { match event { Event::Key(key_event) => self.handle_key_events(key_event), diff --git a/src/tui/editor.rs b/src/tui/editor.rs index 282cd1e..c5c6b11 100644 --- a/src/tui/editor.rs +++ b/src/tui/editor.rs @@ -1,6 +1,4 @@ use crate::tui::component::{Action, Component, ComponentState, Focus}; - -use crate::tui::app::{AppComponents, ComponentOf}; use anyhow::{Context, Result, bail}; use edtui::{ EditorEventHandler, EditorState, EditorTheme, EditorView, LineNumbers, Lines, SyntaxHighlighter, @@ -24,22 +22,11 @@ pub struct Editor { pub(crate) component_state: ComponentState, } -impl<'a> ComponentOf for AppComponents<'a> { - fn as_ref(&self) -> Option<&Editor> { - if let AppComponents::AppEditor(ref e) = *self { - return Some(e); - } - None - } - fn as_mut(&mut self) -> Option<&mut Editor> { - if let AppComponents::AppEditor(ref mut e) = *self { - return Some(e); - } - None - } -} - impl Editor { + pub fn id() -> &'static str { + "Editor" + } + pub fn new() -> Self { Editor { state: EditorState::default(), @@ -104,14 +91,6 @@ impl Widget for &mut Editor { } impl Component for Editor { - fn id(&self) -> &str { - "Editor" - } - - fn is_active(&self) -> bool { - self.component_state.focus == Focus::Active - } - fn handle_event(&mut self, event: Event) -> Result { if let Some(key_event) = event.as_key_event() { // Handle events here that should not be passed on to the vim emulation handler. @@ -140,4 +119,8 @@ impl Component for Editor { _ => Ok(Action::Noop), } } + + fn is_active(&self) -> bool { + self.component_state.focus == Focus::Active + } } diff --git a/src/tui/explorer.rs b/src/tui/explorer.rs index d914da5..9de4895 100644 --- a/src/tui/explorer.rs +++ b/src/tui/explorer.rs @@ -1,4 +1,3 @@ -use crate::tui::app::{AppComponents, ComponentOf}; use crate::tui::component::{Action, Component, ComponentState, Focus}; use anyhow::{Context, Result, bail}; use ratatui::buffer::Buffer; @@ -12,28 +11,17 @@ use tui_tree_widget::{Tree, TreeItem, TreeState}; #[derive(Debug)] pub struct Explorer<'a> { - root_path: std::path::PathBuf, + pub(crate) root_path: std::path::PathBuf, tree_items: TreeItem<'a, String>, tree_state: TreeState, pub(crate) component_state: ComponentState, } -impl<'a> ComponentOf> for AppComponents<'a> { - fn as_ref(&self) -> Option<&Explorer<'a>> { - if let AppComponents::AppExplorer(ref e) = *self { - return Some(e); - } - None - } - fn as_mut(&mut self) -> Option<&mut Explorer<'a>> { - if let AppComponents::AppExplorer(ref mut e) = *self { - return Some(e); - } - None - } -} - impl<'a> Explorer<'a> { + pub fn id() -> &'static str { + "Explorer" + } + pub fn new(path: &std::path::PathBuf) -> Result { let explorer = Explorer { root_path: path.to_owned(), @@ -123,21 +111,16 @@ impl<'a> Explorer<'a> { pub fn selected(&self) -> Result { if let Some(path) = self.tree_state.selected().last() { - return Ok(std::path::absolute(path)?.to_str().unwrap().to_string()); + return Ok(std::path::absolute(path)? + .to_str() + .context("Failed to get absolute path to selected TreeItem")? + .to_string()); } bail!("Failed to get selected TreeItem") } } impl<'a> Component for Explorer<'a> { - fn id(&self) -> &str { - "Explorer" - } - - fn is_active(&self) -> bool { - self.component_state.focus == Focus::Active - } - fn handle_event(&mut self, event: Event) -> Result { if let Some(key_event) = event.as_key_event() { // Handle events here that should not be passed on to the vim emulation handler. @@ -188,4 +171,8 @@ impl<'a> Component for Explorer<'a> { } Ok(Action::Noop) } + + fn is_active(&self) -> bool { + self.component_state.focus == Focus::Active + } } diff --git a/src/tui/logger.rs b/src/tui/logger.rs index 3c859b0..d3f37e9 100644 --- a/src/tui/logger.rs +++ b/src/tui/logger.rs @@ -1,4 +1,3 @@ -use crate::tui::app::{AppComponents, ComponentOf}; use crate::tui::component::{Action, Component, ComponentState, Focus}; use ratatui::buffer::Buffer; use ratatui::crossterm::event::{Event, KeyCode, KeyEvent}; @@ -14,27 +13,16 @@ pub struct Logger { pub(crate) component_state: ComponentState, } -impl<'a> ComponentOf for AppComponents<'a> { - fn as_ref(&self) -> Option<&Logger> { - if let AppComponents::AppLogger(ref e) = *self { - return Some(e); - } - None - } - fn as_mut(&mut self) -> Option<&mut Logger> { - if let AppComponents::AppLogger(ref mut e) = *self { - return Some(e); - } - None - } -} - impl Logger { + pub fn id() -> &'static str { + "Logger" + } + pub fn new() -> Self { Self { state: TuiWidgetState::new(), component_state: ComponentState::default().with_help_text(concat!( - "Q: Quit | Tab: Switch state | ↑/↓: Select target | f: Focus target", + "Q: Quit | ↑/↓: Select target | f: Focus target", " | ←/→: Display level | +/-: Filter level | Space: Toggle hidden targets", " | h: Hide target selector | PageUp/Down: Scroll | Esc: Cancel scroll" )), @@ -47,7 +35,6 @@ impl Widget for &Logger { where Self: Sized, { - // TODO: Use output_file? TuiLoggerSmartWidget::default() .style_error(Style::default().fg(Color::Red)) .style_debug(Style::default().fg(Color::Green)) @@ -66,14 +53,6 @@ impl Widget for &Logger { } impl Component for Logger { - fn id(&self) -> &str { - "Logger" - } - - fn is_active(&self) -> bool { - self.component_state.focus == Focus::Active - } - fn handle_event(&mut self, event: Event) -> anyhow::Result { if let Some(key_event) = event.as_key_event() { return self.handle_key_events(key_event); @@ -99,4 +78,8 @@ impl Component for Logger { } Ok(Action::Pass) } + + fn is_active(&self) -> bool { + self.component_state.focus == Focus::Active + } } -- 2.47.2 From aa4bf8aea69ebc9d68699a00861921c7175be1db Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Sat, 24 Jan 2026 12:29:24 -0500 Subject: [PATCH 57/73] [tui] Add help text for last focused widget. + Fill in TODO help text for all widgets. --- src/tui/app.rs | 17 ++++++++++++----- src/tui/editor.rs | 3 ++- src/tui/explorer.rs | 23 +++++++++++++---------- src/tui/logger.rs | 22 ++++++++++++---------- 4 files changed, 39 insertions(+), 26 deletions(-) diff --git a/src/tui/app.rs b/src/tui/app.rs index d2140c7..bc97bb0 100644 --- a/src/tui/app.rs +++ b/src/tui/app.rs @@ -97,11 +97,18 @@ impl<'a> App<'a> { AppExplorer => self.explorer.component_state.help_text.clone(), AppLogger => self.logger.component_state.help_text.clone(), }; - Paragraph::new(help) - .style(Color::Gray) - .wrap(Wrap { trim: false }) - .centered() - .render(area, buf); + Paragraph::new( + concat!( + "ALT+Q: Focus project explorer | ALT+W: Focus editor | ALT+E: Focus logger |", + " CTRL+C: Quit\n" + ) + .to_string() + + help.as_str(), + ) + .style(Color::Gray) + .wrap(Wrap { trim: false }) + .centered() + .render(area, buf); } fn draw_tabs(&self, area: Rect, buf: &mut Buffer) { diff --git a/src/tui/editor.rs b/src/tui/editor.rs index c5c6b11..6db9759 100644 --- a/src/tui/editor.rs +++ b/src/tui/editor.rs @@ -33,7 +33,8 @@ impl Editor { event_handler: EditorEventHandler::default(), file_path: None, syntax_set: SyntaxSet::load_defaults_nonewlines(), - component_state: ComponentState::default().with_help_text("TODO: Vim help text"), + component_state: ComponentState::default() + .with_help_text("CTRL+S: Save file | Any other input is handled by vim"), } } diff --git a/src/tui/explorer.rs b/src/tui/explorer.rs index 9de4895..0d5ac8f 100644 --- a/src/tui/explorer.rs +++ b/src/tui/explorer.rs @@ -7,11 +7,12 @@ use ratatui::prelude::Style; use ratatui::style::{Color, Modifier}; use ratatui::widgets::{Block, Borders, StatefulWidget}; use std::fs; +use std::path::PathBuf; use tui_tree_widget::{Tree, TreeItem, TreeState}; #[derive(Debug)] pub struct Explorer<'a> { - pub(crate) root_path: std::path::PathBuf, + pub(crate) root_path: PathBuf, tree_items: TreeItem<'a, String>, tree_state: TreeState, pub(crate) component_state: ComponentState, @@ -27,7 +28,9 @@ impl<'a> Explorer<'a> { root_path: path.to_owned(), tree_items: Self::build_tree_from_path(path.to_owned())?, tree_state: TreeState::default(), - component_state: ComponentState::default().with_help_text("TODO: Explorer help text."), + component_state: ComponentState::default().with_help_text( + "(↑/k)/(↓/j): Select item | ←/h: Close folder | →/l/Enter: Open folder", + ), }; Ok(explorer) } @@ -140,14 +143,14 @@ impl<'a> Component for Explorer<'a> { fn handle_key_events(&mut self, key: KeyEvent) -> Result { let changed = match key.code { - KeyCode::Up => self.tree_state.key_up(), - KeyCode::Char('k') => self.tree_state.key_up(), - KeyCode::Down => self.tree_state.key_down(), - KeyCode::Char('j') => self.tree_state.key_down(), - KeyCode::Left => self.tree_state.key_left(), - KeyCode::Char('h') => self.tree_state.key_left(), - KeyCode::Right => self.tree_state.key_right(), - KeyCode::Char('l') => self.tree_state.key_right(), + KeyCode::Up | KeyCode::Char('k') => self.tree_state.key_up(), + KeyCode::Down | KeyCode::Char('j') => self.tree_state.key_down(), + KeyCode::Left | KeyCode::Char('h') => { + // Do not call key_left(); Calling it on a closed folder clears the selection. + let key = self.tree_state.selected().to_owned(); + self.tree_state.close(key.as_ref()) + } + KeyCode::Right | KeyCode::Char('l') => self.tree_state.key_right(), KeyCode::Enter => self.tree_state.toggle_selected(), _ => false, }; diff --git a/src/tui/logger.rs b/src/tui/logger.rs index d3f37e9..809f072 100644 --- a/src/tui/logger.rs +++ b/src/tui/logger.rs @@ -19,12 +19,14 @@ impl Logger { } pub fn new() -> Self { + let state = TuiWidgetState::new(); + state.transition(TuiWidgetEvent::HideKey); Self { - state: TuiWidgetState::new(), + state, component_state: ComponentState::default().with_help_text(concat!( - "Q: Quit | ↑/↓: Select target | f: Focus target", - " | ←/→: Display level | +/-: Filter level | Space: Toggle hidden targets", - " | h: Hide target selector | PageUp/Down: Scroll | Esc: Cancel scroll" + "Space: Hide/show logging target selector panel | (↑/k)/(↓/j): Select target |", + " (←/h)/(→/l): Display level | f: Focus target | +/-: Filter level |", + " v: Toggle filtered targets visibility | PageUp/Down: Scroll | Esc: Cancel scroll" )), } } @@ -62,17 +64,17 @@ impl Component for Logger { fn handle_key_events(&mut self, key: KeyEvent) -> anyhow::Result { match key.code { - KeyCode::Char(' ') => self.state.transition(TuiWidgetEvent::SpaceKey), + KeyCode::Char('v') => self.state.transition(TuiWidgetEvent::SpaceKey), KeyCode::Esc => self.state.transition(TuiWidgetEvent::EscapeKey), KeyCode::PageUp => self.state.transition(TuiWidgetEvent::PrevPageKey), KeyCode::PageDown => self.state.transition(TuiWidgetEvent::NextPageKey), - KeyCode::Up => self.state.transition(TuiWidgetEvent::UpKey), - KeyCode::Down => self.state.transition(TuiWidgetEvent::DownKey), - KeyCode::Left => self.state.transition(TuiWidgetEvent::LeftKey), - KeyCode::Right => self.state.transition(TuiWidgetEvent::RightKey), + KeyCode::Up | KeyCode::Char('k') => self.state.transition(TuiWidgetEvent::UpKey), + KeyCode::Down | KeyCode::Char('j') => self.state.transition(TuiWidgetEvent::DownKey), + KeyCode::Left | KeyCode::Char('h') => self.state.transition(TuiWidgetEvent::LeftKey), + KeyCode::Right | KeyCode::Char('l') => self.state.transition(TuiWidgetEvent::RightKey), KeyCode::Char('+') => self.state.transition(TuiWidgetEvent::PlusKey), KeyCode::Char('-') => self.state.transition(TuiWidgetEvent::MinusKey), - KeyCode::Char('h') => self.state.transition(TuiWidgetEvent::HideKey), + KeyCode::Char(' ') => self.state.transition(TuiWidgetEvent::HideKey), KeyCode::Char('f') => self.state.transition(TuiWidgetEvent::FocusKey), _ => (), } -- 2.47.2 From dd55d7fc5fb3a991bc334085d7a47d14f8b21c1c Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Sat, 24 Jan 2026 12:47:17 -0500 Subject: [PATCH 58/73] [tui] Handle mouse input for all widgets. This way you can still click to interact with the file explorer while editing a file, for example, without changing widget focus. --- src/tui/app.rs | 13 ++++++------- src/tui/editor.rs | 6 ++---- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/src/tui/app.rs b/src/tui/app.rs index bc97bb0..db339ef 100644 --- a/src/tui/app.rs +++ b/src/tui/app.rs @@ -224,6 +224,12 @@ impl<'a> Component for App<'a> { _ => {} } } + // Components should always handle mouse events for click interaction. + if let Some(mouse) = event.as_mouse_event() { + self.editor.handle_mouse_events(mouse)?; + self.explorer.handle_mouse_events(mouse)?; + self.logger.handle_mouse_events(mouse)?; + } // Handle events for all components. let action = match self.last_active { @@ -231,13 +237,6 @@ impl<'a> Component for App<'a> { AppExplorer => self.explorer.handle_event(event)?, AppLogger => self.logger.handle_event(event)?, }; - // if !c.is_active() { - // if let Some(mouse) = event.as_mouse_event() { - // // Always handle mouse events for click interaction. - // c.handle_mouse_events(mouse)?; - // } - // continue; - // } match action { Action::Quit | Action::Handled => return Ok(action), _ => {} diff --git a/src/tui/editor.rs b/src/tui/editor.rs index 6db9759..c49abea 100644 --- a/src/tui/editor.rs +++ b/src/tui/editor.rs @@ -10,10 +10,6 @@ use ratatui::prelude::{Color, Style}; use ratatui::widgets::{Block, Borders, Padding, Widget}; use syntect::parsing::SyntaxSet; -// TODO: Consider using editor-command https://docs.rs/editor-command/latest/editor_command/ -// TODO: Title should be detected programming language name -// TODO: Content should be file contents -// TODO: Vimrc should be used pub struct Editor { pub state: EditorState, pub event_handler: EditorEventHandler, @@ -46,6 +42,8 @@ impl Editor { .collect(); self.file_path = Some(path.clone()); self.state.lines = Lines::new(lines); + self.state.cursor.row = 0; + self.state.cursor.col = 0; } Ok(()) } -- 2.47.2 From 82ad3ab29fad954ffc2afd7807ff1a28a4b60596 Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Sat, 24 Jan 2026 14:22:42 -0500 Subject: [PATCH 59/73] [tui] Add TitleBar struct to handle rendering. This will support adding drop-down menus. For now, the widget just highlights which item you selected in the title bar with left / right keys. --- src/tui.rs | 1 + src/tui/app.rs | 39 ++++++++++----- src/tui/title_bar.rs | 111 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 138 insertions(+), 13 deletions(-) create mode 100644 src/tui/title_bar.rs diff --git a/src/tui.rs b/src/tui.rs index fc67c72..6deb805 100644 --- a/src/tui.rs +++ b/src/tui.rs @@ -3,6 +3,7 @@ mod component; mod editor; mod explorer; mod logger; +mod title_bar; use anyhow::{Context, Result}; use log::{LevelFilter, debug, info}; diff --git a/src/tui/app.rs b/src/tui/app.rs index db339ef..ff8f2b5 100644 --- a/src/tui/app.rs +++ b/src/tui/app.rs @@ -3,11 +3,15 @@ use crate::tui::component::{Action, Component, Focus, FocusState}; use crate::tui::editor::Editor; use crate::tui::explorer::Explorer; use crate::tui::logger::Logger; +use crate::tui::title_bar::TitleBar; +use AppComponent::AppTitleBar; use anyhow::{Context, Result}; use log::{debug, error, info, trace, warn}; use ratatui::buffer::Buffer; use ratatui::crossterm::event; -use ratatui::crossterm::event::{Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers}; +use ratatui::crossterm::event::{ + Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers, MouseButton, MouseEventKind, +}; use ratatui::layout::{Constraint, Direction, Layout, Rect}; use ratatui::prelude::{Color, Style, Widget}; use ratatui::widgets::{Block, Borders, Padding, Paragraph, Tabs, Wrap}; @@ -23,12 +27,14 @@ pub enum AppComponent { AppEditor, AppExplorer, AppLogger, + AppTitleBar, } pub struct App<'a> { editor: Editor, explorer: Explorer<'a>, logger: Logger, + title_bar: TitleBar, last_active: AppComponent, } @@ -42,6 +48,7 @@ impl<'a> App<'a> { editor: Editor::new(), explorer: Explorer::new(&root_path)?, logger: Logger::new(), + title_bar: TitleBar::new(), last_active: AppEditor, }; Ok(app) @@ -82,20 +89,13 @@ impl<'a> App<'a> { Ok(()) } - fn draw_top_status(&self, area: Rect, buf: &mut Buffer) { - // TODO: Status bar should have drop down menus - Tabs::new(["File", "Edit", "View", "Help"]) - .style(Style::default()) - .block(Block::default().borders(Borders::ALL)) - .render(area, buf); - } - fn draw_bottom_status(&self, area: Rect, buf: &mut Buffer) { // Determine help text from the most recently focused component. let help = match self.last_active { AppEditor => self.editor.component_state.help_text.clone(), AppExplorer => self.explorer.component_state.help_text.clone(), AppLogger => self.logger.component_state.help_text.clone(), + AppTitleBar => self.title_bar.component_state.help_text.clone(), }; Paragraph::new( concat!( @@ -143,6 +143,7 @@ impl<'a> App<'a> { AppEditor => self.editor.component_state.set_focus(Focus::Active), AppExplorer => self.explorer.component_state.set_focus(Focus::Active), AppLogger => self.logger.component_state.set_focus(Focus::Active), + AppTitleBar => self.title_bar.component_state.set_focus(Focus::Active), } self.last_active = focus; } @@ -198,7 +199,7 @@ impl<'a> Widget for &mut App<'a> { ]) .split(horizontal[1]); - self.draw_top_status(vertical[0], buf); + self.title_bar.render(vertical[0], buf); self.draw_bottom_status(vertical[3], buf); self.draw_tabs(editor_layout[0], buf); let id = App::id().to_string(); @@ -226,9 +227,11 @@ impl<'a> Component for App<'a> { } // Components should always handle mouse events for click interaction. if let Some(mouse) = event.as_mouse_event() { - self.editor.handle_mouse_events(mouse)?; - self.explorer.handle_mouse_events(mouse)?; - self.logger.handle_mouse_events(mouse)?; + if mouse.kind == MouseEventKind::Down(MouseButton::Left) { + self.editor.handle_mouse_events(mouse)?; + self.explorer.handle_mouse_events(mouse)?; + self.logger.handle_mouse_events(mouse)?; + } } // Handle events for all components. @@ -236,6 +239,7 @@ impl<'a> Component for App<'a> { AppEditor => self.editor.handle_event(event)?, AppExplorer => self.explorer.handle_event(event)?, AppLogger => self.logger.handle_event(event)?, + AppTitleBar => self.title_bar.handle_event(event)?, }; match action { Action::Quit | Action::Handled => return Ok(action), @@ -274,6 +278,15 @@ impl<'a> Component for App<'a> { self.change_focus(AppLogger); Ok(Action::Handled) } + KeyEvent { + code: KeyCode::Char('r'), + modifiers: KeyModifiers::ALT, + kind: KeyEventKind::Press, + state: _state, + } => { + self.change_focus(AppTitleBar); + Ok(Action::Handled) + } KeyEvent { code: KeyCode::Char('l'), modifiers: KeyModifiers::ALT, diff --git a/src/tui/title_bar.rs b/src/tui/title_bar.rs new file mode 100644 index 0000000..b4659d0 --- /dev/null +++ b/src/tui/title_bar.rs @@ -0,0 +1,111 @@ +use crate::tui::component::{Action, Component, ComponentState}; +use ratatui::buffer::Buffer; +use ratatui::crossterm::event::{KeyCode, KeyEvent}; +use ratatui::layout::Rect; +use ratatui::style::{Color, Style}; +use ratatui::text::Line; +use ratatui::widgets::{Block, Borders, Tabs, Widget}; +use strum::{EnumIter, FromRepr, IntoEnumIterator}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, FromRepr, EnumIter)] +enum TitleBarItem { + File, + Edit, + View, + Help, +} + +impl TitleBarItem { + pub fn next(mut self) -> Self { + let cur = self as usize; + let next = cur.saturating_add(1); + Self::from_repr(next).unwrap_or(self) + } + + pub fn prev(self) -> Self { + let cur = self as usize; + let prev = cur.saturating_sub(1); + Self::from_repr(prev).unwrap_or(self) + } + + pub fn id(&self) -> &str { + match self { + TitleBarItem::File => "File", + TitleBarItem::Edit => "Edit", + TitleBarItem::View => "View", + TitleBarItem::Help => "Help", + } + } +} + +pub struct TitleBar { + selected: TitleBarItem, + opened: Option, + pub(crate) component_state: ComponentState, +} + +impl TitleBar { + pub fn new() -> Self { + Self { + selected: TitleBarItem::File, + opened: None, + component_state: ComponentState::default() + .with_help_text(concat!("TODO: Title bar help text.").as_ref()), + } + } + + fn render_title_bar(&self, area: Rect, buf: &mut Buffer) { + let titles: Vec = TitleBarItem::iter() + .map(|item| Line::from(item.id().to_owned())) + .collect(); + let tabs_style = Style::default(); + let highlight_style = if self.opened.is_some() { + Style::default().bg(Color::Blue).fg(Color::White) + } else { + Style::default().bg(Color::Cyan).fg(Color::Black) + }; + Tabs::new(titles) + .style(tabs_style) + .block(Block::default().borders(Borders::ALL)) + .highlight_style(highlight_style) + .select(self.selected as usize) + .render(area, buf); + } +} + +impl Widget for &TitleBar { + fn render(self, area: Rect, buf: &mut Buffer) + where + Self: Sized, + { + let title_bar_area = Rect { + x: area.x, + y: area.y, + width: area.width, + height: 3, + }; + self.render_title_bar(title_bar_area, buf); + } +} + +impl Component for TitleBar { + fn handle_key_events(&mut self, key: KeyEvent) -> anyhow::Result { + match key.code { + // KeyCode::Up | KeyCode::Char('k') => self.selected.key_up(), + // KeyCode::Down | KeyCode::Char('j') => self.selected.key_down(), + KeyCode::Left | KeyCode::Char('h') => { + self.selected = self.selected.prev(); + Ok(Action::Handled) + } + KeyCode::Right | KeyCode::Char('l') => { + self.selected = self.selected.next(); + Ok(Action::Handled) + } + KeyCode::Enter => { + self.opened = Some(self.selected); + Ok(Action::Handled) + } + _ => Ok(Action::Noop), + } + } +} -- 2.47.2 From 78c13f576695da49cebe16f7eb37ec8346718dde Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Sat, 24 Jan 2026 15:33:48 -0500 Subject: [PATCH 60/73] [tui] Add TitleBar popups for drop-down menus. --- src/tui/app.rs | 4 +- src/tui/title_bar.rs | 124 ++++++++++++++++++++++++++++++++++--------- 2 files changed, 103 insertions(+), 25 deletions(-) diff --git a/src/tui/app.rs b/src/tui/app.rs index ff8f2b5..67665b4 100644 --- a/src/tui/app.rs +++ b/src/tui/app.rs @@ -199,7 +199,6 @@ impl<'a> Widget for &mut App<'a> { ]) .split(horizontal[1]); - self.title_bar.render(vertical[0], buf); self.draw_bottom_status(vertical[3], buf); self.draw_tabs(editor_layout[0], buf); let id = App::id().to_string(); @@ -209,6 +208,9 @@ impl<'a> Widget for &mut App<'a> { .context("Failed to render Explorer") .unwrap_or_else(|e| error!(target:id.as_str(), "{}", e)); self.logger.render(vertical[2], buf); + + // The title bar is rendered last to overlay any popups created for drop-down menus. + self.title_bar.render(vertical[0], buf); } } diff --git a/src/tui/title_bar.rs b/src/tui/title_bar.rs index b4659d0..a214580 100644 --- a/src/tui/title_bar.rs +++ b/src/tui/title_bar.rs @@ -2,21 +2,22 @@ use crate::tui::component::{Action, Component, ComponentState}; use ratatui::buffer::Buffer; use ratatui::crossterm::event::{KeyCode, KeyEvent}; use ratatui::layout::Rect; -use ratatui::style::{Color, Style}; +use ratatui::style::{Color, Modifier, Style}; use ratatui::text::Line; -use ratatui::widgets::{Block, Borders, Tabs, Widget}; +use ratatui::widgets::{ + Block, Borders, Clear, List, ListItem, ListState, StatefulWidget, Tabs, Widget, +}; use strum::{EnumIter, FromRepr, IntoEnumIterator}; #[derive(Debug, Clone, Copy, PartialEq, Eq, FromRepr, EnumIter)] enum TitleBarItem { File, - Edit, View, Help, } impl TitleBarItem { - pub fn next(mut self) -> Self { + pub fn next(self) -> Self { let cur = self as usize; let next = cur.saturating_add(1); Self::from_repr(next).unwrap_or(self) @@ -31,26 +32,35 @@ impl TitleBarItem { pub fn id(&self) -> &str { match self { TitleBarItem::File => "File", - TitleBarItem::Edit => "Edit", TitleBarItem::View => "View", TitleBarItem::Help => "Help", } } + + pub fn options(&self) -> &[&str] { + match self { + TitleBarItem::File => &["Save", "Reload"], + TitleBarItem::View => &["Show/hide explorer", "Show/hide logger"], + TitleBarItem::Help => &["About"], + } + } } pub struct TitleBar { selected: TitleBarItem, opened: Option, pub(crate) component_state: ComponentState, + list_state: ListState, } impl TitleBar { + const DEFAULT_HELP: &str = "(←/h)/(→/l): Select option | Enter: Choose selection"; pub fn new() -> Self { Self { selected: TitleBarItem::File, opened: None, - component_state: ComponentState::default() - .with_help_text(concat!("TODO: Title bar help text.").as_ref()), + component_state: ComponentState::default().with_help_text(Self::DEFAULT_HELP), + list_state: ListState::default().with_selected(Some(0)), } } @@ -71,10 +81,44 @@ impl TitleBar { .select(self.selected as usize) .render(area, buf); } -} -impl Widget for &TitleBar { - fn render(self, area: Rect, buf: &mut Buffer) + fn render_drop_down( + &mut self, + title_bar_anchor: Rect, + area: Rect, + buf: &mut Buffer, + opened: TitleBarItem, + ) { + let popup_area = Self::rect_under_option(title_bar_anchor, area, 40, 10); + Clear::default().render(popup_area, buf); + let options = opened.options().iter().map(|i| ListItem::new(*i)); + StatefulWidget::render( + List::new(options) + .block(Block::bordered().title(self.selected.id())) + .highlight_style( + Style::default() + .bg(Color::Blue) + .fg(Color::White) + .add_modifier(Modifier::BOLD), + ) + .highlight_symbol(">> "), + popup_area, + buf, + &mut self.list_state, + ); + } + + fn rect_under_option(anchor: Rect, area: Rect, width: u16, height: u16) -> Rect { + // TODO: X offset for item option? It's fine as-is, but it might look nicer. + Rect { + x: anchor.x, + y: anchor.y + anchor.height, + width: width.min(area.width), + height, + } + } + + pub fn render(&mut self, area: Rect, buf: &mut Buffer) where Self: Sized, { @@ -85,27 +129,59 @@ impl Widget for &TitleBar { height: 3, }; self.render_title_bar(title_bar_area, buf); + if let Some(opened) = self.opened { + self.render_drop_down(title_bar_area, area, buf, opened); + } } } impl Component for TitleBar { fn handle_key_events(&mut self, key: KeyEvent) -> anyhow::Result { - match key.code { - // KeyCode::Up | KeyCode::Char('k') => self.selected.key_up(), - // KeyCode::Down | KeyCode::Char('j') => self.selected.key_down(), - KeyCode::Left | KeyCode::Char('h') => { - self.selected = self.selected.prev(); - Ok(Action::Handled) + if self.opened.is_some() { + // Keybinds for popup menu. + match key.code { + KeyCode::Up | KeyCode::Char('k') => { + self.list_state.select_previous(); + Ok(Action::Handled) + } + KeyCode::Down | KeyCode::Char('j') => { + self.list_state.select_next(); + Ok(Action::Handled) + } + KeyCode::Enter => { + // TODO: Handle action for the item. + Ok(Action::Handled) + } + KeyCode::Esc | KeyCode::Char('q') => { + self.opened = None; + self.component_state.help_text = Self::DEFAULT_HELP.to_string(); + self.list_state.select_first(); + Ok(Action::Handled) + } + _ => Ok(Action::Noop), } - KeyCode::Right | KeyCode::Char('l') => { - self.selected = self.selected.next(); - Ok(Action::Handled) + } else { + // Keybinds for title bar. + match key.code { + KeyCode::Left | KeyCode::Char('h') => { + self.selected = self.selected.prev(); + Ok(Action::Handled) + } + KeyCode::Right | KeyCode::Char('l') => { + self.selected = self.selected.next(); + Ok(Action::Handled) + } + KeyCode::Enter => { + self.opened = Some(self.selected); + self.component_state.help_text = concat!( + "(↑/k)/(↓/j): Select option | Enter: Choose selection |", + " ESC/Q: Close drop-down menu" + ) + .to_string(); + Ok(Action::Handled) + } + _ => Ok(Action::Noop), } - KeyCode::Enter => { - self.opened = Some(self.selected); - Ok(Action::Handled) - } - _ => Ok(Action::Noop), } } } -- 2.47.2 From 4e9aedd34c2f7c5fe39a37954487ae89b0db91a6 Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Sat, 24 Jan 2026 15:40:19 -0500 Subject: [PATCH 61/73] [tui] Renames. --- src/tui/app.rs | 26 +++++++++++--------------- src/tui/title_bar.rs | 32 ++++++++++++++++---------------- 2 files changed, 27 insertions(+), 31 deletions(-) diff --git a/src/tui/app.rs b/src/tui/app.rs index 67665b4..d071c73 100644 --- a/src/tui/app.rs +++ b/src/tui/app.rs @@ -3,8 +3,8 @@ use crate::tui::component::{Action, Component, Focus, FocusState}; use crate::tui::editor::Editor; use crate::tui::explorer::Explorer; use crate::tui::logger::Logger; -use crate::tui::title_bar::TitleBar; -use AppComponent::AppTitleBar; +use crate::tui::title_bar::MenuBar; +use AppComponent::AppMenuBar; use anyhow::{Context, Result}; use log::{debug, error, info, trace, warn}; use ratatui::buffer::Buffer; @@ -27,14 +27,14 @@ pub enum AppComponent { AppEditor, AppExplorer, AppLogger, - AppTitleBar, + AppMenuBar, } pub struct App<'a> { editor: Editor, explorer: Explorer<'a>, logger: Logger, - title_bar: TitleBar, + menu_bar: MenuBar, last_active: AppComponent, } @@ -48,7 +48,7 @@ impl<'a> App<'a> { editor: Editor::new(), explorer: Explorer::new(&root_path)?, logger: Logger::new(), - title_bar: TitleBar::new(), + menu_bar: MenuBar::new(), last_active: AppEditor, }; Ok(app) @@ -95,12 +95,12 @@ impl<'a> App<'a> { AppEditor => self.editor.component_state.help_text.clone(), AppExplorer => self.explorer.component_state.help_text.clone(), AppLogger => self.logger.component_state.help_text.clone(), - AppTitleBar => self.title_bar.component_state.help_text.clone(), + AppMenuBar => self.menu_bar.component_state.help_text.clone(), }; Paragraph::new( concat!( "ALT+Q: Focus project explorer | ALT+W: Focus editor | ALT+E: Focus logger |", - " CTRL+C: Quit\n" + " ALT+R: Focus menu bar | CTRL+C: Quit\n" ) .to_string() + help.as_str(), @@ -135,15 +135,11 @@ impl<'a> App<'a> { } fn change_focus(&mut self, focus: AppComponent) { - if self.last_active == AppEditor { - self.editor.state.cursor.row = 0; - self.editor.state.cursor.col = 0; - } match focus { AppEditor => self.editor.component_state.set_focus(Focus::Active), AppExplorer => self.explorer.component_state.set_focus(Focus::Active), AppLogger => self.logger.component_state.set_focus(Focus::Active), - AppTitleBar => self.title_bar.component_state.set_focus(Focus::Active), + AppMenuBar => self.menu_bar.component_state.set_focus(Focus::Active), } self.last_active = focus; } @@ -210,7 +206,7 @@ impl<'a> Widget for &mut App<'a> { self.logger.render(vertical[2], buf); // The title bar is rendered last to overlay any popups created for drop-down menus. - self.title_bar.render(vertical[0], buf); + self.menu_bar.render(vertical[0], buf); } } @@ -241,7 +237,7 @@ impl<'a> Component for App<'a> { AppEditor => self.editor.handle_event(event)?, AppExplorer => self.explorer.handle_event(event)?, AppLogger => self.logger.handle_event(event)?, - AppTitleBar => self.title_bar.handle_event(event)?, + AppMenuBar => self.menu_bar.handle_event(event)?, }; match action { Action::Quit | Action::Handled => return Ok(action), @@ -286,7 +282,7 @@ impl<'a> Component for App<'a> { kind: KeyEventKind::Press, state: _state, } => { - self.change_focus(AppTitleBar); + self.change_focus(AppMenuBar); Ok(Action::Handled) } KeyEvent { diff --git a/src/tui/title_bar.rs b/src/tui/title_bar.rs index a214580..cb615f5 100644 --- a/src/tui/title_bar.rs +++ b/src/tui/title_bar.rs @@ -10,13 +10,13 @@ use ratatui::widgets::{ use strum::{EnumIter, FromRepr, IntoEnumIterator}; #[derive(Debug, Clone, Copy, PartialEq, Eq, FromRepr, EnumIter)] -enum TitleBarItem { +enum MenuBarItem { File, View, Help, } -impl TitleBarItem { +impl MenuBarItem { pub fn next(self) -> Self { let cur = self as usize; let next = cur.saturating_add(1); @@ -31,33 +31,33 @@ impl TitleBarItem { pub fn id(&self) -> &str { match self { - TitleBarItem::File => "File", - TitleBarItem::View => "View", - TitleBarItem::Help => "Help", + MenuBarItem::File => "File", + MenuBarItem::View => "View", + MenuBarItem::Help => "Help", } } pub fn options(&self) -> &[&str] { match self { - TitleBarItem::File => &["Save", "Reload"], - TitleBarItem::View => &["Show/hide explorer", "Show/hide logger"], - TitleBarItem::Help => &["About"], + MenuBarItem::File => &["Save", "Reload"], + MenuBarItem::View => &["Show/hide explorer", "Show/hide logger"], + MenuBarItem::Help => &["About"], } } } -pub struct TitleBar { - selected: TitleBarItem, - opened: Option, +pub struct MenuBar { + selected: MenuBarItem, + opened: Option, pub(crate) component_state: ComponentState, list_state: ListState, } -impl TitleBar { +impl MenuBar { const DEFAULT_HELP: &str = "(←/h)/(→/l): Select option | Enter: Choose selection"; pub fn new() -> Self { Self { - selected: TitleBarItem::File, + selected: MenuBarItem::File, opened: None, component_state: ComponentState::default().with_help_text(Self::DEFAULT_HELP), list_state: ListState::default().with_selected(Some(0)), @@ -65,7 +65,7 @@ impl TitleBar { } fn render_title_bar(&self, area: Rect, buf: &mut Buffer) { - let titles: Vec = TitleBarItem::iter() + let titles: Vec = MenuBarItem::iter() .map(|item| Line::from(item.id().to_owned())) .collect(); let tabs_style = Style::default(); @@ -87,7 +87,7 @@ impl TitleBar { title_bar_anchor: Rect, area: Rect, buf: &mut Buffer, - opened: TitleBarItem, + opened: MenuBarItem, ) { let popup_area = Self::rect_under_option(title_bar_anchor, area, 40, 10); Clear::default().render(popup_area, buf); @@ -135,7 +135,7 @@ impl TitleBar { } } -impl Component for TitleBar { +impl Component for MenuBar { fn handle_key_events(&mut self, key: KeyEvent) -> anyhow::Result { if self.opened.is_some() { // Keybinds for popup menu. -- 2.47.2 From f531886255bfe2ca0c4cccbce8023766015b4f1e Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Sat, 24 Jan 2026 16:03:23 -0500 Subject: [PATCH 62/73] [tui] Handle MenuBar actions. Fixes #7. --- src/tui/app.rs | 1 + src/tui/title_bar.rs | 51 ++++++++++++++++++++++++++++++++++++++------ 2 files changed, 45 insertions(+), 7 deletions(-) diff --git a/src/tui/app.rs b/src/tui/app.rs index d071c73..2d971dd 100644 --- a/src/tui/app.rs +++ b/src/tui/app.rs @@ -241,6 +241,7 @@ impl<'a> Component for App<'a> { }; match action { Action::Quit | Action::Handled => return Ok(action), + Action::Save => self.editor.save()?, _ => {} } Ok(Action::Noop) diff --git a/src/tui/title_bar.rs b/src/tui/title_bar.rs index cb615f5..929bdff 100644 --- a/src/tui/title_bar.rs +++ b/src/tui/title_bar.rs @@ -1,4 +1,8 @@ +use crate::tui::component::Action::Pass; use crate::tui::component::{Action, Component, ComponentState}; +use crate::tui::title_bar::MenuBarItemOption::{ + About, Exit, Reload, Save, ShowHideExplorer, ShowHideLogger, +}; use ratatui::buffer::Buffer; use ratatui::crossterm::event::{KeyCode, KeyEvent}; use ratatui::layout::Rect; @@ -16,6 +20,29 @@ enum MenuBarItem { Help, } +#[derive(Debug, Clone, Copy, PartialEq, Eq, FromRepr, EnumIter)] +enum MenuBarItemOption { + Save, + Reload, + Exit, + ShowHideExplorer, + ShowHideLogger, + About, +} + +impl MenuBarItemOption { + fn id(&self) -> &str { + match self { + MenuBarItemOption::Save => "Save", + MenuBarItemOption::Reload => "Reload", + MenuBarItemOption::Exit => "Exit", + MenuBarItemOption::ShowHideExplorer => "Show / hide explorer", + MenuBarItemOption::ShowHideLogger => "Show / hide logger", + MenuBarItemOption::About => "About", + } + } +} + impl MenuBarItem { pub fn next(self) -> Self { let cur = self as usize; @@ -37,11 +64,11 @@ impl MenuBarItem { } } - pub fn options(&self) -> &[&str] { + pub fn options(&self) -> &[MenuBarItemOption] { match self { - MenuBarItem::File => &["Save", "Reload"], - MenuBarItem::View => &["Show/hide explorer", "Show/hide logger"], - MenuBarItem::Help => &["About"], + MenuBarItem::File => &[Save, Reload, Exit], + MenuBarItem::View => &[ShowHideExplorer, ShowHideLogger], + MenuBarItem::Help => &[About], } } } @@ -91,7 +118,7 @@ impl MenuBar { ) { let popup_area = Self::rect_under_option(title_bar_anchor, area, 40, 10); Clear::default().render(popup_area, buf); - let options = opened.options().iter().map(|i| ListItem::new(*i)); + let options = opened.options().iter().map(|i| ListItem::new(i.id())); StatefulWidget::render( List::new(options) .block(Block::bordered().title(self.selected.id())) @@ -149,8 +176,18 @@ impl Component for MenuBar { Ok(Action::Handled) } KeyCode::Enter => { - // TODO: Handle action for the item. - Ok(Action::Handled) + if let Some(selected) = self.list_state.selected() { + let seletion = self.selected.options()[selected]; + return match seletion { + Save => Ok(Action::Save), + Exit => Ok(Action::Quit), + Reload => Ok(Action::Noop), // TODO + ShowHideExplorer => Ok(Action::Noop), // TODO + ShowHideLogger => Ok(Action::Noop), // TODO + About => Ok(Action::Noop), // TODO + }; + } + Ok(Action::Noop) } KeyCode::Esc | KeyCode::Char('q') => { self.opened = None; -- 2.47.2 From 45d665f8f6ac70930caf73f5741c18fa92c4dac0 Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Sat, 24 Jan 2026 16:30:22 -0500 Subject: [PATCH 63/73] [tui] Implement Widget for Explorer and MenuBar. --- src/tui/app.rs | 5 +--- src/tui/explorer.rs | 59 +++++++++++++++++++++----------------------- src/tui/title_bar.rs | 4 ++- 3 files changed, 32 insertions(+), 36 deletions(-) diff --git a/src/tui/app.rs b/src/tui/app.rs index 2d971dd..a678aba 100644 --- a/src/tui/app.rs +++ b/src/tui/app.rs @@ -199,10 +199,7 @@ impl<'a> Widget for &mut App<'a> { self.draw_tabs(editor_layout[0], buf); let id = App::id().to_string(); self.editor.render(editor_layout[1], buf); - self.explorer - .render(horizontal[0], buf) - .context("Failed to render Explorer") - .unwrap_or_else(|e| error!(target:id.as_str(), "{}", e)); + self.explorer.render(horizontal[0], buf); self.logger.render(vertical[2], buf); // The title bar is rendered last to overlay any popups created for drop-down menus. diff --git a/src/tui/explorer.rs b/src/tui/explorer.rs index 0d5ac8f..dc77c6e 100644 --- a/src/tui/explorer.rs +++ b/src/tui/explorer.rs @@ -5,7 +5,7 @@ use ratatui::crossterm::event::{Event, KeyCode, KeyEvent, MouseEvent, MouseEvent use ratatui::layout::{Alignment, Position, Rect}; use ratatui::prelude::Style; use ratatui::style::{Color, Modifier}; -use ratatui::widgets::{Block, Borders, StatefulWidget}; +use ratatui::widgets::{Block, Borders, StatefulWidget, Widget}; use std::fs; use std::path::PathBuf; use tui_tree_widget::{Tree, TreeItem, TreeState}; @@ -82,36 +82,6 @@ impl<'a> Explorer<'a> { .context("Failed to build tree from path.") } - pub fn render(&mut self, area: Rect, buf: &mut Buffer) -> Result<()> { - StatefulWidget::render( - Tree::new(&self.tree_items.children()) - .context("Failed to build file Explorer Tree.")? - .style(Style::default()) - .block( - Block::default() - .borders(Borders::ALL) - .title( - self.root_path - .file_name() - .context("Failed to get file name from path.")? - .to_string_lossy(), - ) - .title_style(Style::default().fg(Color::Green)) - .title_alignment(Alignment::Center), - ) - .highlight_style( - Style::new() - .fg(Color::Black) - .bg(Color::Rgb(57, 59, 64)) - .add_modifier(Modifier::BOLD), - ), - area, - buf, - &mut self.tree_state, - ); - Ok(()) - } - pub fn selected(&self) -> Result { if let Some(path) = self.tree_state.selected().last() { return Ok(std::path::absolute(path)? @@ -123,6 +93,33 @@ impl<'a> Explorer<'a> { } } +impl<'a> Widget for &mut Explorer<'a> { + fn render(self, area: Rect, buf: &mut Buffer) { + if let Ok(tree) = Tree::new(&self.tree_items.children()) { + let file_name = self.root_path.file_name().unwrap_or("Unknown".as_ref()); + StatefulWidget::render( + tree.style(Style::default()) + .block( + Block::default() + .borders(Borders::ALL) + .title(file_name.to_string_lossy()) + .title_style(Style::default().fg(Color::Green)) + .title_alignment(Alignment::Center), + ) + .highlight_style( + Style::new() + .fg(Color::Black) + .bg(Color::Rgb(57, 59, 64)) + .add_modifier(Modifier::BOLD), + ), + area, + buf, + &mut self.tree_state, + ); + } + } +} + impl<'a> Component for Explorer<'a> { fn handle_event(&mut self, event: Event) -> Result { if let Some(key_event) = event.as_key_event() { diff --git a/src/tui/title_bar.rs b/src/tui/title_bar.rs index 929bdff..bb68068 100644 --- a/src/tui/title_bar.rs +++ b/src/tui/title_bar.rs @@ -144,8 +144,10 @@ impl MenuBar { height, } } +} - pub fn render(&mut self, area: Rect, buf: &mut Buffer) +impl Widget for &mut MenuBar { + fn render(self, area: Rect, buf: &mut Buffer) where Self: Sized, { -- 2.47.2 From 5d2a7fa0a161354d23e92006695f0d616f456907 Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Sat, 24 Jan 2026 16:31:36 -0500 Subject: [PATCH 64/73] [tui] Rename title_bar.rs --- src/tui.rs | 2 +- src/tui/app.rs | 2 +- src/tui/{title_bar.rs => menu_bar.rs} | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) rename src/tui/{title_bar.rs => menu_bar.rs} (99%) diff --git a/src/tui.rs b/src/tui.rs index 6deb805..fbc4133 100644 --- a/src/tui.rs +++ b/src/tui.rs @@ -3,7 +3,7 @@ mod component; mod editor; mod explorer; mod logger; -mod title_bar; +mod menu_bar; use anyhow::{Context, Result}; use log::{LevelFilter, debug, info}; diff --git a/src/tui/app.rs b/src/tui/app.rs index a678aba..08335f7 100644 --- a/src/tui/app.rs +++ b/src/tui/app.rs @@ -3,7 +3,7 @@ use crate::tui::component::{Action, Component, Focus, FocusState}; use crate::tui::editor::Editor; use crate::tui::explorer::Explorer; use crate::tui::logger::Logger; -use crate::tui::title_bar::MenuBar; +use crate::tui::menu_bar::MenuBar; use AppComponent::AppMenuBar; use anyhow::{Context, Result}; use log::{debug, error, info, trace, warn}; diff --git a/src/tui/title_bar.rs b/src/tui/menu_bar.rs similarity index 99% rename from src/tui/title_bar.rs rename to src/tui/menu_bar.rs index bb68068..d724782 100644 --- a/src/tui/title_bar.rs +++ b/src/tui/menu_bar.rs @@ -1,6 +1,6 @@ use crate::tui::component::Action::Pass; use crate::tui::component::{Action, Component, ComponentState}; -use crate::tui::title_bar::MenuBarItemOption::{ +use crate::tui::menu_bar::MenuBarItemOption::{ About, Exit, Reload, Save, ShowHideExplorer, ShowHideLogger, }; use ratatui::buffer::Buffer; -- 2.47.2 From 711f92b7dd041f259de43c6ed255db90e616b5aa Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Sat, 24 Jan 2026 19:41:38 -0500 Subject: [PATCH 65/73] [tui] Add EditorTab widget. + This adds support for tabbed editors wrapped by EditorTab widgets. + The Explorer widget now opens new EditorTabs when a file is selected with Enter. + The same file may not be opened multiple times. + Tabs can be switched with ALT+h or ALT+l (or ALT+ arrow keys) + Tabs cannot yet be closed :) Fixes #9 --- src/tui.rs | 1 + src/tui/app.rs | 153 +++++++++++++++++++++--------------------- src/tui/component.rs | 1 + src/tui/editor.rs | 7 +- src/tui/editor_tab.rs | 151 +++++++++++++++++++++++++++++++++++++++++ src/tui/explorer.rs | 22 ++++-- src/tui/menu_bar.rs | 15 ++--- 7 files changed, 259 insertions(+), 91 deletions(-) create mode 100644 src/tui/editor_tab.rs diff --git a/src/tui.rs b/src/tui.rs index fbc4133..c78579f 100644 --- a/src/tui.rs +++ b/src/tui.rs @@ -1,6 +1,7 @@ mod app; mod component; mod editor; +mod editor_tab; mod explorer; mod logger; mod menu_bar; diff --git a/src/tui/app.rs b/src/tui/app.rs index 08335f7..51250c5 100644 --- a/src/tui/app.rs +++ b/src/tui/app.rs @@ -1,21 +1,21 @@ use crate::tui::app::AppComponent::{AppEditor, AppExplorer, AppLogger}; use crate::tui::component::{Action, Component, Focus, FocusState}; -use crate::tui::editor::Editor; +use crate::tui::editor_tab::EditorTab; use crate::tui::explorer::Explorer; use crate::tui::logger::Logger; use crate::tui::menu_bar::MenuBar; use AppComponent::AppMenuBar; use anyhow::{Context, Result}; -use log::{debug, error, info, trace, warn}; +use log::error; +use ratatui::DefaultTerminal; use ratatui::buffer::Buffer; use ratatui::crossterm::event; use ratatui::crossterm::event::{ Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers, MouseButton, MouseEventKind, }; use ratatui::layout::{Constraint, Direction, Layout, Rect}; -use ratatui::prelude::{Color, Style, Widget}; -use ratatui::widgets::{Block, Borders, Padding, Paragraph, Tabs, Wrap}; -use ratatui::{DefaultTerminal, symbols}; +use ratatui::prelude::{Color, Widget}; +use ratatui::widgets::{Paragraph, Wrap}; use std::path::PathBuf; use std::time::Duration; @@ -31,7 +31,7 @@ pub enum AppComponent { } pub struct App<'a> { - editor: Editor, + editor_tabs: EditorTab, explorer: Explorer<'a>, logger: Logger, menu_bar: MenuBar, @@ -45,7 +45,7 @@ impl<'a> App<'a> { pub fn new(root_path: PathBuf) -> Result { let app = Self { - editor: Editor::new(), + editor_tabs: EditorTab::new(&root_path), explorer: Explorer::new(&root_path)?, logger: Logger::new(), menu_bar: MenuBar::new(), @@ -57,12 +57,16 @@ impl<'a> App<'a> { /// Logic that should be executed once on application startup. pub fn start(&mut self) -> Result<()> { let root_path = self.explorer.root_path.clone(); - self.editor + let editor = self + .editor_tabs + .current_editor_mut() + .context("Failed to get current editor in App::start")?; + editor .set_contents(&root_path.join("src/tui/app.rs")) .context(format!( "Failed to initialize editor contents to path: {root_path:?}" ))?; - self.editor.component_state.set_focus(Focus::Active); + editor.component_state.set_focus(Focus::Active); Ok(()) } @@ -92,7 +96,13 @@ impl<'a> App<'a> { fn draw_bottom_status(&self, area: Rect, buf: &mut Buffer) { // Determine help text from the most recently focused component. let help = match self.last_active { - AppEditor => self.editor.component_state.help_text.clone(), + AppEditor => match self.editor_tabs.current_editor() { + Some(editor) => editor.component_state.help_text.clone(), + None => { + error!(target:Self::id(), "Failed to get Editor while drawing bottom status bar"); + "Failed to get current Editor while getting widget help text".to_string() + } + }, AppExplorer => self.explorer.component_state.help_text.clone(), AppLogger => self.logger.component_state.help_text.clone(), AppMenuBar => self.menu_bar.component_state.help_text.clone(), @@ -111,32 +121,14 @@ impl<'a> App<'a> { .render(area, buf); } - fn draw_tabs(&self, area: Rect, buf: &mut Buffer) { - // Determine the tab title from the current file (or use a fallback). - if let Some(title) = self.editor.file_path.clone() { - Tabs::new(vec![ - title - .file_name() - .map(|f| f.to_str()) - .unwrap_or(Some("Unknown")) - .unwrap(), - ]) - .divider(symbols::DOT) - .block( - Block::default() - .borders(Borders::NONE) - .padding(Padding::new(0, 0, 0, 0)), - ) - .highlight_style(Style::default().fg(Color::LightRed)) - .render(area, buf); - } else { - error!(target:Self::id(), "Failed to get Editor file_path while drawing Tabs widget."); - } - } - fn change_focus(&mut self, focus: AppComponent) { match focus { - AppEditor => self.editor.component_state.set_focus(Focus::Active), + AppEditor => match self.editor_tabs.current_editor_mut() { + None => { + error!(target:Self::id(), "Failed to get current Editor while changing focus") + } + Some(editor) => editor.component_state.set_focus(Focus::Active), + }, AppExplorer => self.explorer.component_state.set_focus(Focus::Active), AppLogger => self.logger.component_state.set_focus(Focus::Active), AppMenuBar => self.menu_bar.component_state.set_focus(Focus::Active), @@ -147,20 +139,26 @@ impl<'a> App<'a> { /// Refresh the contents of the editor to match the selected TreeItem in the file Explorer. /// If the selected item is not a file, this does nothing. fn refresh_editor_contents(&mut self) -> Result<()> { + // TODO: This may be useful for a preview mode of the selected file prior to opening a tab. // Use the currently selected TreeItem or get an absolute path to this source file. - let selected_pathbuf = match self.explorer.selected() { - Ok(path) => PathBuf::from(path), - Err(_) => PathBuf::from(std::path::absolute(file!())?.to_string_lossy().to_string()), - }; - let current_file_path = self - .editor - .file_path - .clone() - .context("Failed to get Editor current file_path")?; - if selected_pathbuf == current_file_path || !selected_pathbuf.is_file() { - return Ok(()); - } - self.editor.set_contents(&selected_pathbuf) + // let selected_pathbuf = match self.explorer.selected() { + // Ok(path) => PathBuf::from(path), + // Err(_) => PathBuf::from(std::path::absolute(file!())?.to_string_lossy().to_string()), + // }; + // match self.editor_tabs.current_editor_mut() { + // None => bail!("Failed to get current Editor while refreshing editor contents"), + // Some(editor) => { + // let current_file_path = editor + // .file_path + // .clone() + // .context("Failed to get Editor current file_path")?; + // if selected_pathbuf == current_file_path || !selected_pathbuf.is_file() { + // return Ok(()); + // } + // editor.set_contents(&selected_pathbuf) + // } + // } + Ok(()) } } @@ -196,9 +194,8 @@ impl<'a> Widget for &mut App<'a> { .split(horizontal[1]); self.draw_bottom_status(vertical[3], buf); - self.draw_tabs(editor_layout[0], buf); - let id = App::id().to_string(); - self.editor.render(editor_layout[1], buf); + self.editor_tabs + .render(editor_layout[0], editor_layout[1], buf); self.explorer.render(horizontal[0], buf); self.logger.render(vertical[2], buf); @@ -220,28 +217,47 @@ impl<'a> Component for App<'a> { _ => {} } } + // Handle events for all components. + let action = match self.last_active { + AppEditor => self.editor_tabs.handle_event(event.clone())?, + AppExplorer => self.explorer.handle_event(event.clone())?, + AppLogger => self.logger.handle_event(event.clone())?, + AppMenuBar => self.menu_bar.handle_event(event.clone())?, + }; + + let editor = self + .editor_tabs + .current_editor_mut() + .context("Failed to get current editor while handling App events")?; // Components should always handle mouse events for click interaction. if let Some(mouse) = event.as_mouse_event() { if mouse.kind == MouseEventKind::Down(MouseButton::Left) { - self.editor.handle_mouse_events(mouse)?; + editor.handle_mouse_events(mouse)?; self.explorer.handle_mouse_events(mouse)?; self.logger.handle_mouse_events(mouse)?; } } - // Handle events for all components. - let action = match self.last_active { - AppEditor => self.editor.handle_event(event)?, - AppExplorer => self.explorer.handle_event(event)?, - AppLogger => self.logger.handle_event(event)?, - AppMenuBar => self.menu_bar.handle_event(event)?, - }; match action { - Action::Quit | Action::Handled => return Ok(action), - Action::Save => self.editor.save()?, - _ => {} + Action::Quit | Action::Handled => Ok(action), + Action::Save => match editor.save() { + Ok(_) => Ok(Action::Handled), + Err(_) => { + error!(target:Self::id(), "Failed to save editor contents"); + Ok(Action::Noop) + } + }, + Action::OpenTab => { + if let Ok(path) = self.explorer.selected() { + let path_buf = PathBuf::from(path); + self.editor_tabs.open_tab(&path_buf)?; + Ok(Action::Handled) + } else { + Ok(Action::Noop) + } + } + _ => Ok(Action::Noop), } - Ok(Action::Noop) } /// Handles key events for the App Component only. @@ -283,19 +299,6 @@ impl<'a> Component for App<'a> { self.change_focus(AppMenuBar); Ok(Action::Handled) } - KeyEvent { - code: KeyCode::Char('l'), - modifiers: KeyModifiers::ALT, - kind: KeyEventKind::Press, - state: _state, - } => { - error!(target:App::id(), "an error"); - warn!(target:App::id(), "a warning"); - info!(target:App::id(), "a two line info\nsecond line"); - debug!(target:App::id(), "a debug"); - trace!(target:App::id(), "a trace"); - Ok(Action::Handled) - } KeyEvent { code: KeyCode::Char('c'), modifiers: KeyModifiers::CONTROL, diff --git a/src/tui/component.rs b/src/tui/component.rs index 0a98b84..3e07f9d 100644 --- a/src/tui/component.rs +++ b/src/tui/component.rs @@ -19,6 +19,7 @@ pub enum Action { /// The input was handled by a Component and should not be passed to the next component. Handled, + OpenTab, } pub trait Component { diff --git a/src/tui/editor.rs b/src/tui/editor.rs index c49abea..b500de8 100644 --- a/src/tui/editor.rs +++ b/src/tui/editor.rs @@ -23,14 +23,17 @@ impl Editor { "Editor" } + // TODO: You shouldnt be able to construct the editor without a path? pub fn new() -> Self { Editor { state: EditorState::default(), event_handler: EditorEventHandler::default(), file_path: None, syntax_set: SyntaxSet::load_defaults_nonewlines(), - component_state: ComponentState::default() - .with_help_text("CTRL+S: Save file | Any other input is handled by vim"), + component_state: ComponentState::default().with_help_text(concat!( + "CTRL+S: Save file | ALT+(←/h): Previous tab | ALT+(l/→): Next tab |", + " All other input is handled by vim" + )), } } diff --git a/src/tui/editor_tab.rs b/src/tui/editor_tab.rs new file mode 100644 index 0000000..d06d2bc --- /dev/null +++ b/src/tui/editor_tab.rs @@ -0,0 +1,151 @@ +use crate::tui::component::{Action, Component}; +use crate::tui::editor::Editor; +use anyhow::{Context, Result}; +use log::trace; +use ratatui::buffer::Buffer; +use ratatui::crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers}; +use ratatui::layout::Rect; +use ratatui::prelude::{Color, Style}; +use ratatui::widgets::{Block, Borders, Padding, Tabs, Widget}; +use std::collections::HashMap; + +// Render the tabs with keys as titles +// Tab keys can be file names. +// Render the editor using the key as a reference for lookup +pub struct EditorTab { + pub(crate) editors: HashMap, + tab_order: Vec, + current_editor: usize, +} + +impl EditorTab { + fn id() -> &'static str { + "EditorTab" + } + + pub fn new(path: &std::path::PathBuf) -> Self { + trace!(target:Self::id(), "Building EditorTab with path '{path:?}'"); + let tab_order = vec![path.to_string_lossy().to_string()]; + Self { + editors: HashMap::from([(tab_order.first().unwrap().to_owned(), Editor::new())]), + tab_order, + current_editor: 0, + } + } + + pub fn next_editor(&mut self) { + self.current_editor = (self.current_editor + 1) % self.tab_order.len(); + } + + pub fn prev_editor(&mut self) { + self.current_editor = self + .current_editor + .checked_sub(1) + .unwrap_or(self.tab_order.len() - 1); + } + + pub fn current_editor(&self) -> Option<&Editor> { + self.editors.get(&self.tab_order[self.current_editor]) + } + + pub fn current_editor_mut(&mut self) -> Option<&mut Editor> { + self.editors.get_mut(&self.tab_order[self.current_editor]) + } + + pub fn open_tab(&mut self, path: &std::path::PathBuf) -> Result<()> { + if self + .editors + .contains_key(&path.to_string_lossy().to_string()) + { + return Ok(()); + } + + let path_str = path.to_string_lossy().to_string(); + self.tab_order.push(path_str.clone()); + let mut editor = Editor::new(); + editor.set_contents(path).context("Failed to open tab")?; + self.editors.insert(path_str, editor); + self.current_editor = self.tab_order.len() - 1; + Ok(()) + } + + pub fn render(&mut self, tabs_area: Rect, editor_area: Rect, buf: &mut Buffer) { + // TODO: Only file name is displayed in tab title, so files with the same name in different + // directories will appear confusing. + let tab_titles = self.tab_order.iter().map(|t| { + std::path::PathBuf::from(t) + .file_name() + .map(|f| f.to_string_lossy().to_string()) + .unwrap_or("Unknown".to_string()) + }); + Tabs::new(tab_titles) + .select(self.current_editor) + .divider("|") + .block( + Block::default() + .borders(Borders::NONE) + .padding(Padding::new(0, 0, 0, 0)), + ) + .highlight_style(Style::default().fg(Color::LightRed)) + .render(tabs_area, buf); + Widget::render(self, editor_area, buf); + } +} + +impl Widget for &mut EditorTab { + fn render(self, area: Rect, buf: &mut Buffer) + where + Self: Sized, + { + if let Some(editor) = self.current_editor_mut() { + editor.render(area, buf); + } + } +} + +impl Component for EditorTab { + fn handle_event(&mut self, event: Event) -> Result { + if let Some(key) = event.as_key_event() { + let action = self.handle_key_events(key)?; + match action { + Action::Quit | Action::Handled => return Ok(action), + _ => {} + } + } + self.current_editor_mut() + .context("Failed to get current editor")? + .handle_event(event) + } + + fn handle_key_events(&mut self, key: KeyEvent) -> Result { + match key { + KeyEvent { + code: KeyCode::Char('h'), + modifiers: KeyModifiers::ALT, + .. + } + | KeyEvent { + code: KeyCode::Left, + modifiers: KeyModifiers::ALT, + .. + } => { + self.prev_editor(); + Ok(Action::Handled) + } + KeyEvent { + code: KeyCode::Char('l'), + modifiers: KeyModifiers::ALT, + .. + } + | KeyEvent { + code: KeyCode::Right, + modifiers: KeyModifiers::ALT, + .. + } => { + self.next_editor(); + Ok(Action::Handled) + } + _ => Ok(Action::Noop), + } + } +} diff --git a/src/tui/explorer.rs b/src/tui/explorer.rs index dc77c6e..a44b9b0 100644 --- a/src/tui/explorer.rs +++ b/src/tui/explorer.rs @@ -23,19 +23,20 @@ impl<'a> Explorer<'a> { "Explorer" } - pub fn new(path: &std::path::PathBuf) -> Result { + pub fn new(path: &PathBuf) -> Result { let explorer = Explorer { root_path: path.to_owned(), tree_items: Self::build_tree_from_path(path.to_owned())?, tree_state: TreeState::default(), - component_state: ComponentState::default().with_help_text( - "(↑/k)/(↓/j): Select item | ←/h: Close folder | →/l/Enter: Open folder", - ), + component_state: ComponentState::default().with_help_text(concat!( + "(↑/k)/(↓/j): Select item | ←/h: Close folder | →/l: Open folder |", + " Enter: Open editor tab" + )), }; Ok(explorer) } - fn build_tree_from_path(path: std::path::PathBuf) -> Result> { + fn build_tree_from_path(path: PathBuf) -> Result> { let mut children = vec![]; if let Ok(entries) = fs::read_dir(&path) { let mut paths = entries @@ -126,6 +127,7 @@ impl<'a> Component for Explorer<'a> { // Handle events here that should not be passed on to the vim emulation handler. match self.handle_key_events(key_event)? { Action::Handled => return Ok(Action::Handled), + Action::OpenTab => return Ok(Action::OpenTab), _ => {} } } @@ -139,6 +141,15 @@ impl<'a> Component for Explorer<'a> { } fn handle_key_events(&mut self, key: KeyEvent) -> Result { + if key.code == KeyCode::Enter { + if let Ok(selected) = self.selected() { + if PathBuf::from(&selected).is_file() { + return Ok(Action::OpenTab); + } + } + return Ok(Action::Noop); + } + let changed = match key.code { KeyCode::Up | KeyCode::Char('k') => self.tree_state.key_up(), KeyCode::Down | KeyCode::Char('j') => self.tree_state.key_down(), @@ -148,7 +159,6 @@ impl<'a> Component for Explorer<'a> { self.tree_state.close(key.as_ref()) } KeyCode::Right | KeyCode::Char('l') => self.tree_state.key_right(), - KeyCode::Enter => self.tree_state.toggle_selected(), _ => false, }; if changed { diff --git a/src/tui/menu_bar.rs b/src/tui/menu_bar.rs index d724782..aa96009 100644 --- a/src/tui/menu_bar.rs +++ b/src/tui/menu_bar.rs @@ -1,4 +1,3 @@ -use crate::tui::component::Action::Pass; use crate::tui::component::{Action, Component, ComponentState}; use crate::tui::menu_bar::MenuBarItemOption::{ About, Exit, Reload, Save, ShowHideExplorer, ShowHideLogger, @@ -33,12 +32,12 @@ enum MenuBarItemOption { impl MenuBarItemOption { fn id(&self) -> &str { match self { - MenuBarItemOption::Save => "Save", - MenuBarItemOption::Reload => "Reload", - MenuBarItemOption::Exit => "Exit", - MenuBarItemOption::ShowHideExplorer => "Show / hide explorer", - MenuBarItemOption::ShowHideLogger => "Show / hide logger", - MenuBarItemOption::About => "About", + Save => "Save", + Reload => "Reload", + Exit => "Exit", + ShowHideExplorer => "Show / hide explorer", + ShowHideLogger => "Show / hide logger", + About => "About", } } } @@ -116,7 +115,7 @@ impl MenuBar { buf: &mut Buffer, opened: MenuBarItem, ) { - let popup_area = Self::rect_under_option(title_bar_anchor, area, 40, 10); + let popup_area = Self::rect_under_option(title_bar_anchor, area, 27, 10); Clear::default().render(popup_area, buf); let options = opened.options().iter().map(|i| ListItem::new(i.id())); StatefulWidget::render( -- 2.47.2 From 01eeb9f0acc9ff2f516087d83b704fc6b776547b Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Sun, 25 Jan 2026 09:07:41 -0500 Subject: [PATCH 66/73] [tui] Add more logging. --- src/tui.rs | 15 ++++++++++----- src/tui/app.rs | 16 +++++++++------- src/tui/component.rs | 6 ++++++ src/tui/editor.rs | 5 +++++ src/tui/editor_tab.rs | 14 ++++++++++---- src/tui/explorer.rs | 2 ++ src/tui/logger.rs | 2 ++ src/tui/menu_bar.rs | 12 ++++++++++-- 8 files changed, 54 insertions(+), 18 deletions(-) diff --git a/src/tui.rs b/src/tui.rs index c78579f..3199e70 100644 --- a/src/tui.rs +++ b/src/tui.rs @@ -7,7 +7,7 @@ mod logger; mod menu_bar; use anyhow::{Context, Result}; -use log::{LevelFilter, debug, info}; +use log::{LevelFilter, debug, info, trace}; use ratatui::Terminal; use ratatui::backend::CrosstermBackend; use ratatui::crossterm::event::{ @@ -28,10 +28,15 @@ pub struct Tui { } impl Tui { + pub fn id() -> &'static str { + "Tui" + } + pub fn new(root_path: std::path::PathBuf) -> Result { + trace!(target:Self::id(), "Building {}", Self::id()); init_logger(LevelFilter::Trace)?; set_default_level(LevelFilter::Trace); - debug!(target:"Tui", "Logging initialized"); + debug!(target:Self::id(), "Logging initialized"); let mut dir = env::temp_dir(); dir.push("clide.log"); @@ -43,7 +48,7 @@ impl Tui { .output_file(false) .output_separator(':'); set_log_file(file_options); - debug!(target:"Tui", "Logging to file: {dir:?}"); + debug!(target:Self::id(), "Logging to file: {dir:?}"); Ok(Self { terminal: Terminal::new(CrosstermBackend::new(stdout()))?, @@ -52,7 +57,7 @@ impl Tui { } pub fn start(self) -> Result<()> { - info!(target:"Tui", "Starting the TUI editor at {:?}", self.root_path); + info!(target:Self::id(), "Starting the TUI editor at {:?}", self.root_path); ratatui::crossterm::execute!( stdout(), EnterAlternateScreen, @@ -69,7 +74,7 @@ impl Tui { } fn stop() -> Result<()> { - info!(target:"Tui", "Stopping the TUI editor"); + info!(target:Self::id(), "Stopping the TUI editor"); disable_raw_mode()?; ratatui::crossterm::execute!( stdout(), diff --git a/src/tui/app.rs b/src/tui/app.rs index 51250c5..661e6ac 100644 --- a/src/tui/app.rs +++ b/src/tui/app.rs @@ -6,7 +6,7 @@ use crate::tui::logger::Logger; use crate::tui::menu_bar::MenuBar; use AppComponent::AppMenuBar; use anyhow::{Context, Result}; -use log::error; +use log::{error, info, trace, warn}; use ratatui::DefaultTerminal; use ratatui::buffer::Buffer; use ratatui::crossterm::event; @@ -22,7 +22,7 @@ use std::time::Duration; // TODO: Need a way to dynamically run Widget::render on all widgets. // TODO: + Need a way to map Rect to Component::id() to position each widget? // TODO: Need a good way to dynamically run Component methods on all widgets. -#[derive(PartialEq)] +#[derive(Debug, Clone, Copy, PartialEq)] pub enum AppComponent { AppEditor, AppExplorer, @@ -44,6 +44,7 @@ impl<'a> App<'a> { } pub fn new(root_path: PathBuf) -> Result { + trace!(target:Self::id(), "Building {}", Self::id()); let app = Self { editor_tabs: EditorTab::new(&root_path), explorer: Explorer::new(&root_path)?, @@ -56,6 +57,7 @@ impl<'a> App<'a> { /// Logic that should be executed once on application startup. pub fn start(&mut self) -> Result<()> { + trace!(target:Self::id(), "Starting App"); let root_path = self.explorer.root_path.clone(); let editor = self .editor_tabs @@ -72,10 +74,8 @@ impl<'a> App<'a> { pub fn run(mut self, mut terminal: DefaultTerminal) -> Result<()> { self.start()?; + trace!(target:Self::id(), "Entering App run loop"); loop { - self.refresh_editor_contents() - .context("Failed to refresh editor contents.")?; - terminal.draw(|f| { f.render_widget(&mut self, f.area()); })?; @@ -122,6 +122,7 @@ impl<'a> App<'a> { } fn change_focus(&mut self, focus: AppComponent) { + info!(target:Self::id(), "Changing widget focus to {:?}", focus); match focus { AppEditor => match self.editor_tabs.current_editor_mut() { None => { @@ -138,6 +139,7 @@ impl<'a> App<'a> { /// Refresh the contents of the editor to match the selected TreeItem in the file Explorer. /// If the selected item is not a file, this does nothing. + #[allow(unused)] fn refresh_editor_contents(&mut self) -> Result<()> { // TODO: This may be useful for a preview mode of the selected file prior to opening a tab. // Use the currently selected TreeItem or get an absolute path to this source file. @@ -242,8 +244,8 @@ impl<'a> Component for App<'a> { Action::Quit | Action::Handled => Ok(action), Action::Save => match editor.save() { Ok(_) => Ok(Action::Handled), - Err(_) => { - error!(target:Self::id(), "Failed to save editor contents"); + Err(e) => { + error!(target:Self::id(), "Failed to save editor contents: {e}"); Ok(Action::Noop) } }, diff --git a/src/tui/component.rs b/src/tui/component.rs index 3e07f9d..8eca3ed 100644 --- a/src/tui/component.rs +++ b/src/tui/component.rs @@ -1,6 +1,7 @@ #![allow(dead_code, unused_variables)] use anyhow::Result; +use log::trace; use ratatui::crossterm::event::{Event, KeyEvent, MouseEvent}; pub enum Action { @@ -55,7 +56,12 @@ pub struct ComponentState { } impl ComponentState { + pub fn id() -> &'static str { + "ComponentState" + } + fn new() -> Self { + trace!(target:Self::id(), "Building {}", Self::id()); Self { focus: Focus::Active, help_text: String::new(), diff --git a/src/tui/editor.rs b/src/tui/editor.rs index b500de8..f927f75 100644 --- a/src/tui/editor.rs +++ b/src/tui/editor.rs @@ -3,6 +3,7 @@ use anyhow::{Context, Result, bail}; use edtui::{ EditorEventHandler, EditorState, EditorTheme, EditorView, LineNumbers, Lines, SyntaxHighlighter, }; +use log::{error, trace}; use ratatui::buffer::Buffer; use ratatui::crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers}; use ratatui::layout::{Alignment, Rect}; @@ -25,6 +26,7 @@ impl Editor { // TODO: You shouldnt be able to construct the editor without a path? pub fn new() -> Self { + trace!(target:Self::id(), "Building {}", Self::id()); Editor { state: EditorState::default(), event_handler: EditorEventHandler::default(), @@ -38,6 +40,7 @@ impl Editor { } pub fn set_contents(&mut self, path: &std::path::PathBuf) -> Result<()> { + trace!(target:Self::id(), "Setting Editor contents from path {:?}", path); if let Ok(contents) = std::fs::read_to_string(path) { let lines: Vec<_> = contents .lines() @@ -53,8 +56,10 @@ impl Editor { pub fn save(&self) -> Result<()> { if let Some(path) = &self.file_path { + trace!(target:Self::id(), "Saving Editor contents {:?}", path); return std::fs::write(path, self.state.lines.to_string()).map_err(|e| e.into()); }; + error!(target:Self::id(), "Failed saving Editor contents; file_path was None"); bail!("File not saved. No file path set.") } } diff --git a/src/tui/editor_tab.rs b/src/tui/editor_tab.rs index d06d2bc..8cc309a 100644 --- a/src/tui/editor_tab.rs +++ b/src/tui/editor_tab.rs @@ -1,7 +1,7 @@ use crate::tui::component::{Action, Component}; use crate::tui::editor::Editor; use anyhow::{Context, Result}; -use log::trace; +use log::{trace, warn}; use ratatui::buffer::Buffer; use ratatui::crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers}; use ratatui::layout::Rect; @@ -24,7 +24,7 @@ impl EditorTab { } pub fn new(path: &std::path::PathBuf) -> Self { - trace!(target:Self::id(), "Building EditorTab with path '{path:?}'"); + trace!(target:Self::id(), "Building EditorTab with path {path:?}"); let tab_order = vec![path.to_string_lossy().to_string()]; Self { editors: HashMap::from([(tab_order.first().unwrap().to_owned(), Editor::new())]), @@ -34,14 +34,18 @@ impl EditorTab { } pub fn next_editor(&mut self) { - self.current_editor = (self.current_editor + 1) % self.tab_order.len(); + let next = (self.current_editor + 1) % self.tab_order.len(); + trace!(target:Self::id(), "Moving from {} to next editor tab at {}", self.current_editor, next); + self.current_editor = next; } pub fn prev_editor(&mut self) { - self.current_editor = self + let prev = self .current_editor .checked_sub(1) .unwrap_or(self.tab_order.len() - 1); + trace!(target:Self::id(), "Moving from {} to previous editor tab at {}", self.current_editor, prev); + self.current_editor = prev; } pub fn current_editor(&self) -> Option<&Editor> { @@ -53,10 +57,12 @@ impl EditorTab { } pub fn open_tab(&mut self, path: &std::path::PathBuf) -> Result<()> { + trace!(target:Self::id(), "Opening new EditorTab with path {:?}", path); if self .editors .contains_key(&path.to_string_lossy().to_string()) { + warn!(target:Self::id(), "EditorTab already opened with this file"); return Ok(()); } diff --git a/src/tui/explorer.rs b/src/tui/explorer.rs index a44b9b0..a1e10c0 100644 --- a/src/tui/explorer.rs +++ b/src/tui/explorer.rs @@ -1,5 +1,6 @@ use crate::tui::component::{Action, Component, ComponentState, Focus}; use anyhow::{Context, Result, bail}; +use log::trace; use ratatui::buffer::Buffer; use ratatui::crossterm::event::{Event, KeyCode, KeyEvent, MouseEvent, MouseEventKind}; use ratatui::layout::{Alignment, Position, Rect}; @@ -24,6 +25,7 @@ impl<'a> Explorer<'a> { } pub fn new(path: &PathBuf) -> Result { + trace!(target:Self::id(), "Building {}", Self::id()); let explorer = Explorer { root_path: path.to_owned(), tree_items: Self::build_tree_from_path(path.to_owned())?, diff --git a/src/tui/logger.rs b/src/tui/logger.rs index 809f072..44ad0f3 100644 --- a/src/tui/logger.rs +++ b/src/tui/logger.rs @@ -1,4 +1,5 @@ use crate::tui::component::{Action, Component, ComponentState, Focus}; +use log::trace; use ratatui::buffer::Buffer; use ratatui::crossterm::event::{Event, KeyCode, KeyEvent}; use ratatui::layout::Rect; @@ -19,6 +20,7 @@ impl Logger { } pub fn new() -> Self { + trace!(target:Self::id(), "Building {}", Self::id()); let state = TuiWidgetState::new(); state.transition(TuiWidgetEvent::HideKey); Self { diff --git a/src/tui/menu_bar.rs b/src/tui/menu_bar.rs index aa96009..b3ee35d 100644 --- a/src/tui/menu_bar.rs +++ b/src/tui/menu_bar.rs @@ -2,6 +2,7 @@ use crate::tui::component::{Action, Component, ComponentState}; use crate::tui::menu_bar::MenuBarItemOption::{ About, Exit, Reload, Save, ShowHideExplorer, ShowHideLogger, }; +use log::trace; use ratatui::buffer::Buffer; use ratatui::crossterm::event::{KeyCode, KeyEvent}; use ratatui::layout::Rect; @@ -80,8 +81,13 @@ pub struct MenuBar { } impl MenuBar { + pub fn id() -> &'static str { + "MenuBar" + } + const DEFAULT_HELP: &str = "(←/h)/(→/l): Select option | Enter: Choose selection"; pub fn new() -> Self { + trace!(target:Self::id(), "Building {}", Self::id()); Self { selected: MenuBarItem::File, opened: None, @@ -136,12 +142,14 @@ impl MenuBar { fn rect_under_option(anchor: Rect, area: Rect, width: u16, height: u16) -> Rect { // TODO: X offset for item option? It's fine as-is, but it might look nicer. - Rect { + let rect = Rect { x: anchor.x, y: anchor.y + anchor.height, width: width.min(area.width), height, - } + }; + trace!(target:Self::id(), "Building Rect under MenuBar popup {}", rect); + rect } } -- 2.47.2 From a3d850acd971937f8c55eafdc225a8084dd359ee Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Sun, 25 Jan 2026 09:44:37 -0500 Subject: [PATCH 67/73] [tui] Clean up logger and editor. Filter some noisy system logs in the Logger by default. Use LLD for linking to silence warning for deprecated gold linker. --- .cargo/config.toml | 3 +++ src/tui/app.rs | 2 +- src/tui/editor.rs | 5 ++--- src/tui/editor_tab.rs | 4 ++-- src/tui/explorer.rs | 7 +++++-- src/tui/logger.rs | 6 ++++-- 6 files changed, 17 insertions(+), 10 deletions(-) create mode 100644 .cargo/config.toml diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..a12f1c0 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,3 @@ + +[build] +rustflags = [ "-C", "link-arg=-fuse-ld=lld", ] diff --git a/src/tui/app.rs b/src/tui/app.rs index 661e6ac..0371a32 100644 --- a/src/tui/app.rs +++ b/src/tui/app.rs @@ -6,7 +6,7 @@ use crate::tui::logger::Logger; use crate::tui::menu_bar::MenuBar; use AppComponent::AppMenuBar; use anyhow::{Context, Result}; -use log::{error, info, trace, warn}; +use log::{error, info, trace}; use ratatui::DefaultTerminal; use ratatui::buffer::Buffer; use ratatui::crossterm::event; diff --git a/src/tui/editor.rs b/src/tui/editor.rs index f927f75..9d1d0d3 100644 --- a/src/tui/editor.rs +++ b/src/tui/editor.rs @@ -24,13 +24,12 @@ impl Editor { "Editor" } - // TODO: You shouldnt be able to construct the editor without a path? - pub fn new() -> Self { + pub fn new(path: &std::path::PathBuf) -> Self { trace!(target:Self::id(), "Building {}", Self::id()); Editor { state: EditorState::default(), event_handler: EditorEventHandler::default(), - file_path: None, + file_path: Some(path.to_owned()), syntax_set: SyntaxSet::load_defaults_nonewlines(), component_state: ComponentState::default().with_help_text(concat!( "CTRL+S: Save file | ALT+(←/h): Previous tab | ALT+(l/→): Next tab |", diff --git a/src/tui/editor_tab.rs b/src/tui/editor_tab.rs index 8cc309a..1864487 100644 --- a/src/tui/editor_tab.rs +++ b/src/tui/editor_tab.rs @@ -27,7 +27,7 @@ impl EditorTab { trace!(target:Self::id(), "Building EditorTab with path {path:?}"); let tab_order = vec![path.to_string_lossy().to_string()]; Self { - editors: HashMap::from([(tab_order.first().unwrap().to_owned(), Editor::new())]), + editors: HashMap::from([(tab_order.first().unwrap().to_owned(), Editor::new(path))]), tab_order, current_editor: 0, } @@ -68,7 +68,7 @@ impl EditorTab { let path_str = path.to_string_lossy().to_string(); self.tab_order.push(path_str.clone()); - let mut editor = Editor::new(); + let mut editor = Editor::new(path); editor.set_contents(path).context("Failed to open tab")?; self.editors.insert(path_str, editor); self.current_editor = self.tab_order.len() - 1; diff --git a/src/tui/explorer.rs b/src/tui/explorer.rs index a1e10c0..63f4e5a 100644 --- a/src/tui/explorer.rs +++ b/src/tui/explorer.rs @@ -32,7 +32,7 @@ impl<'a> Explorer<'a> { tree_state: TreeState::default(), component_state: ComponentState::default().with_help_text(concat!( "(↑/k)/(↓/j): Select item | ←/h: Close folder | →/l: Open folder |", - " Enter: Open editor tab" + " Space: Open / close folder | Enter: Open file in new editor tab" )), }; Ok(explorer) @@ -149,7 +149,7 @@ impl<'a> Component for Explorer<'a> { return Ok(Action::OpenTab); } } - return Ok(Action::Noop); + // Otherwise fall through and handle Enter in the next match case. } let changed = match key.code { @@ -160,6 +160,9 @@ impl<'a> Component for Explorer<'a> { let key = self.tree_state.selected().to_owned(); self.tree_state.close(key.as_ref()) } + KeyCode::Char(' ') | KeyCode::Enter => self + .tree_state + .toggle(self.tree_state.selected().to_owned()), KeyCode::Right | KeyCode::Char('l') => self.tree_state.key_right(), _ => false, }; diff --git a/src/tui/logger.rs b/src/tui/logger.rs index 44ad0f3..f82f9f4 100644 --- a/src/tui/logger.rs +++ b/src/tui/logger.rs @@ -1,5 +1,5 @@ use crate::tui::component::{Action, Component, ComponentState, Focus}; -use log::trace; +use log::{LevelFilter, trace}; use ratatui::buffer::Buffer; use ratatui::crossterm::event::{Event, KeyCode, KeyEvent}; use ratatui::layout::Rect; @@ -24,7 +24,9 @@ impl Logger { let state = TuiWidgetState::new(); state.transition(TuiWidgetEvent::HideKey); Self { - state, + state: state + .set_level_for_target("arboard::platform::linux::x11", LevelFilter::Off) + .set_level_for_target("mio::poll", LevelFilter::Off), component_state: ComponentState::default().with_help_text(concat!( "Space: Hide/show logging target selector panel | (↑/k)/(↓/j): Select target |", " (←/h)/(→/l): Display level | f: Focus target | +/-: Filter level |", -- 2.47.2 From 6c2f3f9005cd85eac6e484ba66971be5eaf3c4ea Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Sun, 25 Jan 2026 10:13:25 -0500 Subject: [PATCH 68/73] [tui] Highlight border of active widget. --- src/tui/app.rs | 15 +++++++++++++++ src/tui/component.rs | 15 +++++++++++++++ src/tui/editor.rs | 5 +++-- src/tui/editor_tab.rs | 1 + src/tui/explorer.rs | 30 +++++++++++++++--------------- src/tui/logger.rs | 3 ++- src/tui/menu_bar.rs | 8 ++++++-- 7 files changed, 57 insertions(+), 20 deletions(-) diff --git a/src/tui/app.rs b/src/tui/app.rs index 0371a32..9d0d220 100644 --- a/src/tui/app.rs +++ b/src/tui/app.rs @@ -121,8 +121,23 @@ impl<'a> App<'a> { .render(area, buf); } + fn clear_focus(&mut self) { + info!(target:Self::id(), "Clearing all widget focus"); + self.explorer.component_state.set_focus(Focus::Inactive); + self.explorer.component_state.set_focus(Focus::Inactive); + self.logger.component_state.set_focus(Focus::Inactive); + self.menu_bar.component_state.set_focus(Focus::Inactive); + match self.editor_tabs.current_editor_mut() { + None => { + error!(target:Self::id(), "Failed to get current Editor while clearing focus") + } + Some(editor) => editor.component_state.set_focus(Focus::Inactive), + } + } + fn change_focus(&mut self, focus: AppComponent) { info!(target:Self::id(), "Changing widget focus to {:?}", focus); + self.clear_focus(); match focus { AppEditor => match self.editor_tabs.current_editor_mut() { None => { diff --git a/src/tui/component.rs b/src/tui/component.rs index 8eca3ed..eb8bd14 100644 --- a/src/tui/component.rs +++ b/src/tui/component.rs @@ -3,6 +3,7 @@ use anyhow::Result; use log::trace; use ratatui::crossterm::event::{Event, KeyEvent, MouseEvent}; +use ratatui::style::Color; pub enum Action { /// Exit the application. @@ -81,10 +82,20 @@ pub enum Focus { Inactive, } +impl Focus { + pub(crate) fn get_active_color(&self) -> Color { + match self { + Focus::Active => Color::LightYellow, + Focus::Inactive => Color::White, + } + } +} + pub trait FocusState { fn with_focus(self, focus: Focus) -> Self; fn set_focus(&mut self, focus: Focus); fn toggle_focus(&mut self); + fn get_active_color(&self) -> Color; } impl FocusState for ComponentState { @@ -105,4 +116,8 @@ impl FocusState for ComponentState { Focus::Inactive => self.set_focus(Focus::Active), } } + + fn get_active_color(&self) -> Color { + self.focus.get_active_color() + } } diff --git a/src/tui/editor.rs b/src/tui/editor.rs index 9d1d0d3..76f772c 100644 --- a/src/tui/editor.rs +++ b/src/tui/editor.rs @@ -1,4 +1,4 @@ -use crate::tui::component::{Action, Component, ComponentState, Focus}; +use crate::tui::component::{Action, Component, ComponentState, Focus, FocusState}; use anyhow::{Context, Result, bail}; use edtui::{ EditorEventHandler, EditorState, EditorTheme, EditorView, LineNumbers, Lines, SyntaxHighlighter, @@ -86,7 +86,8 @@ impl Widget for &mut Editor { .title_style(Style::default().fg(Color::Yellow)) .title_alignment(Alignment::Right) .borders(Borders::ALL) - .padding(Padding::new(0, 0, 0, 1)), + .padding(Padding::new(0, 0, 0, 1)) + .style(Style::default().fg(self.component_state.get_active_color())), ), ) .syntax_highlighter(SyntaxHighlighter::new("dracula", lang).ok()) diff --git a/src/tui/editor_tab.rs b/src/tui/editor_tab.rs index 1864487..24d7ae5 100644 --- a/src/tui/editor_tab.rs +++ b/src/tui/editor_tab.rs @@ -84,6 +84,7 @@ impl EditorTab { .map(|f| f.to_string_lossy().to_string()) .unwrap_or("Unknown".to_string()) }); + // Don't set border color based on ComponentState::focus, the Editor renders the border. Tabs::new(tab_titles) .select(self.current_editor) .divider("|") diff --git a/src/tui/explorer.rs b/src/tui/explorer.rs index 63f4e5a..14a5585 100644 --- a/src/tui/explorer.rs +++ b/src/tui/explorer.rs @@ -1,4 +1,4 @@ -use crate::tui::component::{Action, Component, ComponentState, Focus}; +use crate::tui::component::{Action, Component, ComponentState, Focus, FocusState}; use anyhow::{Context, Result, bail}; use log::trace; use ratatui::buffer::Buffer; @@ -101,20 +101,20 @@ impl<'a> Widget for &mut Explorer<'a> { if let Ok(tree) = Tree::new(&self.tree_items.children()) { let file_name = self.root_path.file_name().unwrap_or("Unknown".as_ref()); StatefulWidget::render( - tree.style(Style::default()) - .block( - Block::default() - .borders(Borders::ALL) - .title(file_name.to_string_lossy()) - .title_style(Style::default().fg(Color::Green)) - .title_alignment(Alignment::Center), - ) - .highlight_style( - Style::new() - .fg(Color::Black) - .bg(Color::Rgb(57, 59, 64)) - .add_modifier(Modifier::BOLD), - ), + tree.block( + Block::default() + .borders(Borders::ALL) + .title(file_name.to_string_lossy()) + .border_style(Style::default().fg(self.component_state.get_active_color())) + .title_style(Style::default().fg(Color::Green)) + .title_alignment(Alignment::Center), + ) + .highlight_style( + Style::new() + .fg(Color::Black) + .bg(Color::Rgb(57, 59, 64)) + .add_modifier(Modifier::BOLD), + ), area, buf, &mut self.tree_state, diff --git a/src/tui/logger.rs b/src/tui/logger.rs index f82f9f4..a0d2403 100644 --- a/src/tui/logger.rs +++ b/src/tui/logger.rs @@ -1,4 +1,4 @@ -use crate::tui::component::{Action, Component, ComponentState, Focus}; +use crate::tui::component::{Action, Component, ComponentState, Focus, FocusState}; use log::{LevelFilter, trace}; use ratatui::buffer::Buffer; use ratatui::crossterm::event::{Event, KeyCode, KeyEvent}; @@ -42,6 +42,7 @@ impl Widget for &Logger { Self: Sized, { TuiLoggerSmartWidget::default() + .border_style(Style::default().fg(self.component_state.get_active_color())) .style_error(Style::default().fg(Color::Red)) .style_debug(Style::default().fg(Color::Green)) .style_warn(Style::default().fg(Color::Yellow)) diff --git a/src/tui/menu_bar.rs b/src/tui/menu_bar.rs index b3ee35d..d99fe5c 100644 --- a/src/tui/menu_bar.rs +++ b/src/tui/menu_bar.rs @@ -1,4 +1,4 @@ -use crate::tui::component::{Action, Component, ComponentState}; +use crate::tui::component::{Action, Component, ComponentState, FocusState}; use crate::tui::menu_bar::MenuBarItemOption::{ About, Exit, Reload, Save, ShowHideExplorer, ShowHideLogger, }; @@ -108,7 +108,11 @@ impl MenuBar { }; Tabs::new(titles) .style(tabs_style) - .block(Block::default().borders(Borders::ALL)) + .block( + Block::default() + .borders(Borders::ALL) + .border_style(Style::default().fg(self.component_state.get_active_color())), + ) .highlight_style(highlight_style) .select(self.selected as usize) .render(area, buf); -- 2.47.2 From fa36a633ee04c2f62b7393111d9f961292ce3bb8 Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Sun, 25 Jan 2026 12:04:31 -0500 Subject: [PATCH 69/73] [tui] Add File MenuBar options Reload and Close. --- src/tui/app.rs | 43 ++++++++++++++----- src/tui/component.rs | 5 +++ src/tui/editor.rs | 11 +++++ src/tui/editor_tab.rs | 96 +++++++++++++++++++++++++++++++++++++++---- src/tui/menu_bar.rs | 23 ++++++----- 5 files changed, 150 insertions(+), 28 deletions(-) diff --git a/src/tui/app.rs b/src/tui/app.rs index 9d0d220..970f9db 100644 --- a/src/tui/app.rs +++ b/src/tui/app.rs @@ -99,7 +99,9 @@ impl<'a> App<'a> { AppEditor => match self.editor_tabs.current_editor() { Some(editor) => editor.component_state.help_text.clone(), None => { - error!(target:Self::id(), "Failed to get Editor while drawing bottom status bar"); + if !self.editor_tabs.is_empty() { + error!(target:Self::id(), "Failed to get Editor while drawing bottom status bar"); + } "Failed to get current Editor while getting widget help text".to_string() } }, @@ -242,14 +244,12 @@ impl<'a> Component for App<'a> { AppMenuBar => self.menu_bar.handle_event(event.clone())?, }; - let editor = self - .editor_tabs - .current_editor_mut() - .context("Failed to get current editor while handling App events")?; // Components should always handle mouse events for click interaction. if let Some(mouse) = event.as_mouse_event() { if mouse.kind == MouseEventKind::Down(MouseButton::Left) { - editor.handle_mouse_events(mouse)?; + if let Some(editor) = self.editor_tabs.current_editor_mut() { + editor.handle_mouse_events(mouse)?; + } self.explorer.handle_mouse_events(mouse)?; self.logger.handle_mouse_events(mouse)?; } @@ -257,13 +257,20 @@ impl<'a> Component for App<'a> { match action { Action::Quit | Action::Handled => Ok(action), - Action::Save => match editor.save() { - Ok(_) => Ok(Action::Handled), - Err(e) => { - error!(target:Self::id(), "Failed to save editor contents: {e}"); + Action::Save => match self.editor_tabs.current_editor_mut() { + None => { + error!(target:Self::id(), "Failed to get current editor while handling App Action::Save"); Ok(Action::Noop) } + Some(editor) => match editor.save() { + Ok(_) => Ok(Action::Handled), + Err(e) => { + error!(target:Self::id(), "Failed to save editor contents: {e}"); + Ok(Action::Noop) + } + }, }, + Action::OpenTab => { if let Ok(path) = self.explorer.selected() { let path_buf = PathBuf::from(path); @@ -273,6 +280,22 @@ impl<'a> Component for App<'a> { Ok(Action::Noop) } } + Action::CloseTab => match self.editor_tabs.close_current_tab() { + Ok(_) => Ok(Action::Handled), + Err(_) => Ok(Action::Noop), + }, + Action::ReloadFile => { + trace!(target:Self::id(), "Reloading file for current editor"); + if let Some(editor) = self.editor_tabs.current_editor_mut() { + editor + .reload_contents() + .map(|_| Action::Handled) + .context("Failed to handle Action::ReloadFile") + } else { + error!(target:Self::id(), "Failed to get current editor while handling App Action::ReloadFile"); + Ok(Action::Noop) + } + } _ => Ok(Action::Noop), } } diff --git a/src/tui/component.rs b/src/tui/component.rs index eb8bd14..4487ea1 100644 --- a/src/tui/component.rs +++ b/src/tui/component.rs @@ -22,6 +22,11 @@ pub enum Action { /// The input was handled by a Component and should not be passed to the next component. Handled, OpenTab, + ReloadFile, + ShowHideExplorer, + ShowHideLogger, + About, + CloseTab, } pub trait Component { diff --git a/src/tui/editor.rs b/src/tui/editor.rs index 76f772c..5070d90 100644 --- a/src/tui/editor.rs +++ b/src/tui/editor.rs @@ -38,6 +38,17 @@ impl Editor { } } + pub fn reload_contents(&mut self) -> Result<()> { + trace!(target:Self::id(), "Reloading editor file contents {:?}", self.file_path); + match self.file_path.clone() { + None => { + error!(target:Self::id(), "Failed to reload editor contents with None file_path"); + bail!("Failed to reload editor contents with None file_path") + } + Some(path) => self.set_contents(&path), + } + } + pub fn set_contents(&mut self, path: &std::path::PathBuf) -> Result<()> { trace!(target:Self::id(), "Setting Editor contents from path {:?}", path); if let Ok(contents) = std::fs::read_to_string(path) { diff --git a/src/tui/editor_tab.rs b/src/tui/editor_tab.rs index 24d7ae5..9965b16 100644 --- a/src/tui/editor_tab.rs +++ b/src/tui/editor_tab.rs @@ -1,7 +1,7 @@ -use crate::tui::component::{Action, Component}; +use crate::tui::component::{Action, Component, Focus, FocusState}; use crate::tui::editor::Editor; -use anyhow::{Context, Result}; -use log::{trace, warn}; +use anyhow::{Context, Result, anyhow}; +use log::{error, info, trace, warn}; use ratatui::buffer::Buffer; use ratatui::crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers}; use ratatui::layout::Rect; @@ -36,6 +36,7 @@ impl EditorTab { pub fn next_editor(&mut self) { let next = (self.current_editor + 1) % self.tab_order.len(); trace!(target:Self::id(), "Moving from {} to next editor tab at {}", self.current_editor, next); + self.set_tab_focus(Focus::Active, next); self.current_editor = next; } @@ -45,15 +46,64 @@ impl EditorTab { .checked_sub(1) .unwrap_or(self.tab_order.len() - 1); trace!(target:Self::id(), "Moving from {} to previous editor tab at {}", self.current_editor, prev); + self.set_tab_focus(Focus::Active, prev); self.current_editor = prev; } + pub fn get_editor_key(&self, index: usize) -> Option { + match self.tab_order.get(index) { + None => { + if !self.tab_order.is_empty() { + error!(target:Self::id(), "Failed to get editor tab key with invalid index {index}"); + } + None + } + Some(key) => Some(key.to_owned()), + } + } + pub fn current_editor(&self) -> Option<&Editor> { - self.editors.get(&self.tab_order[self.current_editor]) + self.editors.get(&self.get_editor_key(self.current_editor)?) } pub fn current_editor_mut(&mut self) -> Option<&mut Editor> { - self.editors.get_mut(&self.tab_order[self.current_editor]) + self.editors + .get_mut(&self.get_editor_key(self.current_editor)?) + } + + pub fn set_current_tab_focus(&mut self, focus: Focus) { + trace!(target:Self::id(), "Setting current tab {} focus to {:?}", self.current_editor, focus); + self.set_tab_focus(focus, self.current_editor) + } + + pub fn set_tab_focus(&mut self, focus: Focus, index: usize) { + trace!(target:Self::id(), "Setting tab {} focus to {:?}", index, focus); + if focus == Focus::Active && index != self.current_editor { + // If we are setting another tab to active, disable the current one. + trace!( + target:Self::id(), + "New tab {} focus set to Active; Setting current tab {} to Inactive", + index, + self.current_editor + ); + self.set_current_tab_focus(Focus::Inactive); + } + match self.get_editor_key(index) { + None => { + error!(target:Self::id(), "Failed setting tab focus for invalid key {index}"); + } + Some(key) => match self.editors.get_mut(&key) { + None => { + error!( + target:Self::id(), + "Failed to update tab focus at index {} with invalid key: {}", + self.current_editor, + self.tab_order[self.current_editor] + ) + } + Some(editor) => editor.component_state.set_focus(focus), + }, + } } pub fn open_tab(&mut self, path: &std::path::PathBuf) -> Result<()> { @@ -75,6 +125,35 @@ impl EditorTab { Ok(()) } + pub fn close_current_tab(&mut self) -> Result<()> { + self.close_tab(self.current_editor) + } + + pub fn close_tab(&mut self, index: usize) -> Result<()> { + let key = self + .tab_order + .get(index) + .ok_or(anyhow!( + "Failed to get tab order with invalid index {index}" + ))? + .to_owned(); + match self.editors.remove(&key) { + None => { + error!(target:Self::id(), "Failed to remove editor tab {key} with invalid index {index}") + } + Some(_) => { + self.prev_editor(); + self.tab_order.remove(index); + info!(target:Self::id(), "Closed editor tab {key} at index {index}") + } + } + Ok(()) + } + + pub fn is_empty(&self) -> bool { + self.editors.is_empty() + } + pub fn render(&mut self, tabs_area: Rect, editor_area: Rect, buf: &mut Buffer) { // TODO: Only file name is displayed in tab title, so files with the same name in different // directories will appear confusing. @@ -119,9 +198,10 @@ impl Component for EditorTab { _ => {} } } - self.current_editor_mut() - .context("Failed to get current editor")? - .handle_event(event) + if let Some(editor) = self.current_editor_mut() { + return editor.handle_event(event); + } + Ok(Action::Noop) } fn handle_key_events(&mut self, key: KeyEvent) -> Result { diff --git a/src/tui/menu_bar.rs b/src/tui/menu_bar.rs index d99fe5c..ac4d3b2 100644 --- a/src/tui/menu_bar.rs +++ b/src/tui/menu_bar.rs @@ -1,6 +1,6 @@ use crate::tui::component::{Action, Component, ComponentState, FocusState}; use crate::tui::menu_bar::MenuBarItemOption::{ - About, Exit, Reload, Save, ShowHideExplorer, ShowHideLogger, + About, CloseTab, Exit, Reload, Save, ShowHideExplorer, ShowHideLogger, }; use log::trace; use ratatui::buffer::Buffer; @@ -23,6 +23,7 @@ enum MenuBarItem { #[derive(Debug, Clone, Copy, PartialEq, Eq, FromRepr, EnumIter)] enum MenuBarItemOption { Save, + CloseTab, Reload, Exit, ShowHideExplorer, @@ -39,6 +40,7 @@ impl MenuBarItemOption { ShowHideExplorer => "Show / hide explorer", ShowHideLogger => "Show / hide logger", About => "About", + CloseTab => "Close tab", } } } @@ -66,7 +68,7 @@ impl MenuBarItem { pub fn options(&self) -> &[MenuBarItemOption] { match self { - MenuBarItem::File => &[Save, Reload, Exit], + MenuBarItem::File => &[Save, CloseTab, Reload, Exit], MenuBarItem::View => &[ShowHideExplorer, ShowHideLogger], MenuBarItem::Help => &[About], } @@ -145,14 +147,14 @@ impl MenuBar { } fn rect_under_option(anchor: Rect, area: Rect, width: u16, height: u16) -> Rect { - // TODO: X offset for item option? It's fine as-is, but it might look nicer. let rect = Rect { x: anchor.x, y: anchor.y + anchor.height, width: width.min(area.width), height, }; - trace!(target:Self::id(), "Building Rect under MenuBar popup {}", rect); + // TODO: X offset for item option? It's fine as-is, but it might look nicer. + // trace!(target:Self::id(), "Building Rect under MenuBar popup {}", rect); rect } } @@ -190,14 +192,15 @@ impl Component for MenuBar { } KeyCode::Enter => { if let Some(selected) = self.list_state.selected() { - let seletion = self.selected.options()[selected]; - return match seletion { + let selection = self.selected.options()[selected]; + return match selection { Save => Ok(Action::Save), Exit => Ok(Action::Quit), - Reload => Ok(Action::Noop), // TODO - ShowHideExplorer => Ok(Action::Noop), // TODO - ShowHideLogger => Ok(Action::Noop), // TODO - About => Ok(Action::Noop), // TODO + Reload => Ok(Action::ReloadFile), + ShowHideExplorer => Ok(Action::ShowHideExplorer), + ShowHideLogger => Ok(Action::ShowHideLogger), + About => Ok(Action::About), + CloseTab => Ok(Action::CloseTab), }; } Ok(Action::Noop) -- 2.47.2 From 76fe09f39bb722485f5cc2a803ae148aa54f6039 Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Sun, 25 Jan 2026 12:45:52 -0500 Subject: [PATCH 70/73] [tui] Implement View MenuBar actions. You can show / hide the Logger and the Explorer now. --- src/tui/app.rs | 111 ++++++++++++++++++++++++++++++++----------- src/tui/component.rs | 49 +++++++++++++++++-- 2 files changed, 127 insertions(+), 33 deletions(-) diff --git a/src/tui/app.rs b/src/tui/app.rs index 970f9db..7d5752e 100644 --- a/src/tui/app.rs +++ b/src/tui/app.rs @@ -1,5 +1,5 @@ use crate::tui::app::AppComponent::{AppEditor, AppExplorer, AppLogger}; -use crate::tui::component::{Action, Component, Focus, FocusState}; +use crate::tui::component::{Action, Component, Focus, FocusState, Visible, VisibleState}; use crate::tui::editor_tab::EditorTab; use crate::tui::explorer::Explorer; use crate::tui::logger::Logger; @@ -186,40 +186,87 @@ impl<'a> Widget for &mut App<'a> { where Self: Sized, { + let vertical_constraints = match self.logger.component_state.vis { + Visible::Visible => { + vec![ + Constraint::Length(3), // top status bar + Constraint::Percentage(70), // horizontal layout + Constraint::Fill(1), // terminal + Constraint::Length(3), // bottom status bar + ] + } + Visible::Hidden => { + vec![ + Constraint::Length(3), // top status bar + Constraint::Fill(1), // horizontal layout + Constraint::Length(3), // bottom status bar + ] + } + }; let vertical = Layout::default() .direction(Direction::Vertical) - .constraints([ - Constraint::Length(3), // top status bar - Constraint::Percentage(70), // horizontal layout - Constraint::Percentage(30), // terminal - Constraint::Length(3), // bottom status bar - ]) + .constraints(vertical_constraints) .split(area); + let horizontal_constraints = match self.explorer.component_state.vis { + Visible::Visible => { + vec![ + Constraint::Max(30), // File explorer with a max width of 30 characters. + Constraint::Fill(1), // Editor fills the remaining space. + ] + } + Visible::Hidden => { + vec![ + Constraint::Fill(1), // Editor fills the remaining space. + ] + } + }; + + // The index used for vertical here does not care if the Logger is Visible or not. let horizontal = Layout::default() .direction(Direction::Horizontal) - .constraints([ - Constraint::Max(30), // File explorer with a max width of 30 characters. - Constraint::Fill(1), // Editor fills the remaining space. - ]) + .constraints(horizontal_constraints) .split(vertical[1]); + match self.explorer.component_state.vis { + Visible::Visible => { + let editor_layout = Layout::default() + .direction(Direction::Vertical) + .constraints([ + Constraint::Length(1), // Editor tabs. + Constraint::Fill(1), // Editor contents. + ]) + .split(horizontal[1]); + self.editor_tabs + .render(editor_layout[0], editor_layout[1], buf); + self.explorer.render(horizontal[0], buf); + } + Visible::Hidden => { + let editor_layout = Layout::default() + .direction(Direction::Vertical) + .constraints([ + Constraint::Length(1), // Editor tabs. + Constraint::Fill(1), // Editor contents. + ]) + .split(horizontal[0]); + self.editor_tabs + .render(editor_layout[0], editor_layout[1], buf); + } + } - let editor_layout = Layout::default() - .direction(Direction::Vertical) - .constraints([ - Constraint::Length(1), // Editor tabs. - Constraint::Fill(1), // Editor contents. - ]) - .split(horizontal[1]); - - self.draw_bottom_status(vertical[3], buf); - self.editor_tabs - .render(editor_layout[0], editor_layout[1], buf); - self.explorer.render(horizontal[0], buf); - self.logger.render(vertical[2], buf); - - // The title bar is rendered last to overlay any popups created for drop-down menus. - self.menu_bar.render(vertical[0], buf); + match self.logger.component_state.vis { + // Index 1 of vertical is rendered with the horizontal layout above. + Visible::Visible => { + self.logger.render(vertical[2], buf); + self.draw_bottom_status(vertical[3], buf); + // The title bar is rendered last to overlay any popups created for drop-down menus. + self.menu_bar.render(vertical[0], buf); + } + Visible::Hidden => { + self.draw_bottom_status(vertical[2], buf); + // The title bar is rendered last to overlay any popups created for drop-down menus. + self.menu_bar.render(vertical[0], buf); + } + } } } @@ -255,6 +302,7 @@ impl<'a> Component for App<'a> { } } + // Handle actions returned from widgets that may need context on other widgets or app state. match action { Action::Quit | Action::Handled => Ok(action), Action::Save => match self.editor_tabs.current_editor_mut() { @@ -270,7 +318,6 @@ impl<'a> Component for App<'a> { } }, }, - Action::OpenTab => { if let Ok(path) = self.explorer.selected() { let path_buf = PathBuf::from(path); @@ -296,6 +343,14 @@ impl<'a> Component for App<'a> { Ok(Action::Noop) } } + Action::ShowHideLogger => { + self.logger.component_state.togget_visible(); + Ok(Action::Handled) + } + Action::ShowHideExplorer => { + self.explorer.component_state.togget_visible(); + Ok(Action::Handled) + } _ => Ok(Action::Noop), } } diff --git a/src/tui/component.rs b/src/tui/component.rs index 4487ea1..fac8a97 100644 --- a/src/tui/component.rs +++ b/src/tui/component.rs @@ -1,5 +1,7 @@ #![allow(dead_code, unused_variables)] +use crate::tui::component::Focus::Inactive; +use Focus::Active; use anyhow::Result; use log::trace; use ratatui::crossterm::event::{Event, KeyEvent, MouseEvent}; @@ -58,6 +60,7 @@ pub trait Component { #[derive(Debug, Clone, Default)] pub struct ComponentState { pub(crate) focus: Focus, + pub(crate) vis: Visible, pub(crate) help_text: String, } @@ -69,7 +72,8 @@ impl ComponentState { fn new() -> Self { trace!(target:Self::id(), "Building {}", Self::id()); Self { - focus: Focus::Active, + focus: Active, + vis: Visible::Visible, help_text: String::new(), } } @@ -90,8 +94,8 @@ pub enum Focus { impl Focus { pub(crate) fn get_active_color(&self) -> Color { match self { - Focus::Active => Color::LightYellow, - Focus::Inactive => Color::White, + Active => Color::LightYellow, + Inactive => Color::White, } } } @@ -107,6 +111,7 @@ impl FocusState for ComponentState { fn with_focus(self, focus: Focus) -> Self { Self { focus, + vis: Visible::Visible, help_text: self.help_text, } } @@ -117,8 +122,8 @@ impl FocusState for ComponentState { fn toggle_focus(&mut self) { match self.focus { - Focus::Active => self.set_focus(Focus::Inactive), - Focus::Inactive => self.set_focus(Focus::Active), + Active => self.set_focus(Inactive), + Inactive => self.set_focus(Active), } } @@ -126,3 +131,37 @@ impl FocusState for ComponentState { self.focus.get_active_color() } } + +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub enum Visible { + #[default] + Visible, + Hidden, +} + +pub trait VisibleState { + fn with_visible(self, vis: Visible) -> Self; + fn set_visible(&mut self, vis: Visible); + fn togget_visible(&mut self); +} + +impl VisibleState for ComponentState { + fn with_visible(self, vis: Visible) -> Self { + Self { + focus: self.focus, + vis, + help_text: self.help_text, + } + } + + fn set_visible(&mut self, vis: Visible) { + self.vis = vis; + } + + fn togget_visible(&mut self) { + match self.vis { + Visible::Visible => self.set_visible(Visible::Hidden), + Visible::Hidden => self.set_visible(Visible::Visible), + } + } +} -- 2.47.2 From ae9f787c81510eb4347f3d738c39e092aba87091 Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Sun, 25 Jan 2026 14:57:34 -0500 Subject: [PATCH 71/73] [tui] Add About page within Help MenuBar. --- README.md | 10 +--- src/tui.rs | 1 + src/tui/about.rs | 139 +++++++++++++++++++++++++++++++++++++++++++ src/tui/app.rs | 43 +++++++++---- src/tui/component.rs | 22 +++---- src/tui/menu_bar.rs | 2 +- 6 files changed, 188 insertions(+), 29 deletions(-) create mode 100644 src/tui/about.rs diff --git a/README.md b/README.md index 43c1f54..1335086 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,11 @@ # CLIDE -CLIDE is a barebones but extendable IDE written in Rust using the Qt UI framework that supports both full and headless Linux environments. -The core application will provide you with a text editor that can be extended with plugins written in Rust. - -The UI is written in QML and compiled to C++ using `cxx`, which is then linked into the Rust application. +CLIDE is an extendable command-line driven development environment written in Rust using the Qt UI framework that supports both full and headless Linux environments. +The GUI is written in QML compiled through Rust using the cxx-qt crate, while the TUI was implemented using the ratatui crate. It's up to you to build your own development environment for your tools. -This project is intended to be a light-weight core application with no language-specific tools or features. To add tools for your purposes, create a plugin that implements the `ClidePlugin` trait. (This is currently under development and not yet available.) -Once you've created your plugin, you can submit a pull request to add your plugin to the final section in this README if you'd like to contribute. -If this section becomes too large, we may explore other options to distribute plugins. +Once you've created your plugin, you can submit a pull request to add a link to the git repository for your plugin to the final section in this README if you'd like to contribute. The following packages must be installed before the application will build. In the future, we may provide a minimal installation option that only includes dependencies for the headless TUI. diff --git a/src/tui.rs b/src/tui.rs index 3199e70..3a0127d 100644 --- a/src/tui.rs +++ b/src/tui.rs @@ -1,3 +1,4 @@ +mod about; mod app; mod component; mod editor; diff --git a/src/tui/about.rs b/src/tui/about.rs new file mode 100644 index 0000000..e7a7acc --- /dev/null +++ b/src/tui/about.rs @@ -0,0 +1,139 @@ +use log::trace; +use ratatui::buffer::Buffer; +use ratatui::layout::{Constraint, Direction, Layout, Rect}; +use ratatui::style::{Modifier, Style}; +use ratatui::text::{Line, Span}; +use ratatui::widgets::{Block, Borders, Clear, Padding, Paragraph, Widget, Wrap}; + +pub struct About {} + +impl About { + #[allow(unused)] + pub fn id() -> &'static str { + "About" + } + + pub fn new() -> Self { + // trace!(target:Self::id(), "Building {}", Self::id()); + Self {} + } +} + +impl Widget for About { + fn render(self, area: Rect, buf: &mut Buffer) + where + Self: Sized, + { + Clear::default().render(area, buf); + // Split main area + let chunks = Layout::default() + .direction(Direction::Vertical) + .constraints([ + Constraint::Fill(2), // image column + Constraint::Fill(1), // image column + Constraint::Fill(2), // text column + ]) + .split(area); + + let top_chunks = Layout::default() + .direction(Direction::Horizontal) + .constraints([ + Constraint::Fill(1), + Constraint::Fill(3), + Constraint::Fill(1), + ]) + .split(chunks[1]); + + let bottom_chunks = Layout::default() + .direction(Direction::Horizontal) + .constraints([ + Constraint::Fill(1), + Constraint::Fill(3), + Constraint::Fill(1), + ]) + .split(chunks[2]); + + // ---------- IMAGE ---------- + let kilroy_art = [ + " * ", + " |.===. ", + " {}o o{} ", + "-----------------------ooO--(_)--Ooo---------------------------", + "# #", + "# CLIDE WAS HERE #", + "# #", + "# https://git.shaunreed.com/shaunred/clide #", + "# https://shaunreed.com/shaunred/clide #", + "# #", + ]; + + let kilroy_lines: Vec = kilroy_art + .iter() + .map(|l| Line::from(Span::raw(*l))) + .collect(); + + Paragraph::new(kilroy_lines) + .block( + Block::default() + .borders(Borders::NONE) + .padding(Padding::bottom(0)), + ) + .wrap(Wrap { trim: false }) + .centered() + .render(top_chunks[1], buf); + + // ---------- TEXT ---------- + let about_text = vec![ + Line::from(vec![Span::styled( + "clide\n", + Style::default().add_modifier(Modifier::BOLD), + )]) + .centered(), + Line::from(""), + Line::from(vec![ + Span::styled("Author: ", Style::default().add_modifier(Modifier::BOLD)), + Span::raw("Shaun Reed"), + ]) + .left_aligned(), + Line::from(vec![ + Span::styled("Email: ", Style::default().add_modifier(Modifier::BOLD)), + Span::raw("shaunrd0@gmail.com"), + ]) + .left_aligned(), + Line::from(vec![ + Span::styled("URL: ", Style::default().add_modifier(Modifier::BOLD)), + Span::raw("https://git.shaunreed.com/shaunrd0/clide"), + ]) + .left_aligned(), + Line::from(vec![ + Span::styled("Blog: ", Style::default().add_modifier(Modifier::BOLD)), + Span::raw("https://shaunreed.com"), + ]) + .left_aligned(), + Line::from(""), + Line::from(vec![Span::styled( + "Description\n", + Style::default().add_modifier(Modifier::BOLD), + )]) + .left_aligned(), + Line::from(concat!( + "CLIDE is an extendable command-line driven development environment written in Rust using the Qt UI framework that supports both full and headless Linux environments. ", + "The GUI is written in QML compiled through Rust using the cxx-qt crate, while the TUI was implemented using the ratatui crate. ", + )) + .style(Style::default()) + .left_aligned(), + ]; + Block::bordered().render(area, buf); + + let paragraph = Paragraph::new(about_text) + .block( + Block::default() + .title("About") + .borders(Borders::ALL) + .padding(Padding::top(0)), + ) + .wrap(Wrap { trim: true }); + + paragraph.render(bottom_chunks[1], buf); + } +} diff --git a/src/tui/app.rs b/src/tui/app.rs index 7d5752e..04932c5 100644 --- a/src/tui/app.rs +++ b/src/tui/app.rs @@ -1,5 +1,6 @@ +use crate::tui::about::About; use crate::tui::app::AppComponent::{AppEditor, AppExplorer, AppLogger}; -use crate::tui::component::{Action, Component, Focus, FocusState, Visible, VisibleState}; +use crate::tui::component::{Action, Component, Focus, FocusState, Visibility, VisibleState}; use crate::tui::editor_tab::EditorTab; use crate::tui::explorer::Explorer; use crate::tui::logger::Logger; @@ -36,6 +37,7 @@ pub struct App<'a> { logger: Logger, menu_bar: MenuBar, last_active: AppComponent, + about: bool, } impl<'a> App<'a> { @@ -46,11 +48,12 @@ impl<'a> App<'a> { pub fn new(root_path: PathBuf) -> Result { trace!(target:Self::id(), "Building {}", Self::id()); let app = Self { - editor_tabs: EditorTab::new(&root_path), + editor_tabs: EditorTab::new(&root_path.join("src/tui/app.rs")), explorer: Explorer::new(&root_path)?, logger: Logger::new(), menu_bar: MenuBar::new(), last_active: AppEditor, + about: false, }; Ok(app) } @@ -187,7 +190,7 @@ impl<'a> Widget for &mut App<'a> { Self: Sized, { let vertical_constraints = match self.logger.component_state.vis { - Visible::Visible => { + Visibility::Visible => { vec![ Constraint::Length(3), // top status bar Constraint::Percentage(70), // horizontal layout @@ -195,7 +198,7 @@ impl<'a> Widget for &mut App<'a> { Constraint::Length(3), // bottom status bar ] } - Visible::Hidden => { + Visibility::Hidden => { vec![ Constraint::Length(3), // top status bar Constraint::Fill(1), // horizontal layout @@ -209,13 +212,13 @@ impl<'a> Widget for &mut App<'a> { .split(area); let horizontal_constraints = match self.explorer.component_state.vis { - Visible::Visible => { + Visibility::Visible => { vec![ Constraint::Max(30), // File explorer with a max width of 30 characters. Constraint::Fill(1), // Editor fills the remaining space. ] } - Visible::Hidden => { + Visibility::Hidden => { vec![ Constraint::Fill(1), // Editor fills the remaining space. ] @@ -228,7 +231,7 @@ impl<'a> Widget for &mut App<'a> { .constraints(horizontal_constraints) .split(vertical[1]); match self.explorer.component_state.vis { - Visible::Visible => { + Visibility::Visible => { let editor_layout = Layout::default() .direction(Direction::Vertical) .constraints([ @@ -240,7 +243,7 @@ impl<'a> Widget for &mut App<'a> { .render(editor_layout[0], editor_layout[1], buf); self.explorer.render(horizontal[0], buf); } - Visible::Hidden => { + Visibility::Hidden => { let editor_layout = Layout::default() .direction(Direction::Vertical) .constraints([ @@ -255,18 +258,23 @@ impl<'a> Widget for &mut App<'a> { match self.logger.component_state.vis { // Index 1 of vertical is rendered with the horizontal layout above. - Visible::Visible => { + Visibility::Visible => { self.logger.render(vertical[2], buf); self.draw_bottom_status(vertical[3], buf); // The title bar is rendered last to overlay any popups created for drop-down menus. self.menu_bar.render(vertical[0], buf); } - Visible::Hidden => { + Visibility::Hidden => { self.draw_bottom_status(vertical[2], buf); // The title bar is rendered last to overlay any popups created for drop-down menus. self.menu_bar.render(vertical[0], buf); } } + + if self.about { + let about_area = area.centered(Constraint::Percentage(50), Constraint::Percentage(45)); + About::new().render(about_area, buf); + } } } @@ -351,12 +359,27 @@ impl<'a> Component for App<'a> { self.explorer.component_state.togget_visible(); Ok(Action::Handled) } + Action::ShowHideAbout => { + self.about = !self.about; + Ok(Action::Handled) + } _ => Ok(Action::Noop), } } /// Handles key events for the App Component only. fn handle_key_events(&mut self, key: KeyEvent) -> Result { + match key.code { + // If the ESC key is pressed with the About page open, hide it. + KeyCode::Esc | KeyCode::Char('q') => { + if self.about { + self.about = false; + return Ok(Action::Handled); + } + } + _ => {} + } + match key { KeyEvent { code: KeyCode::Char('q'), diff --git a/src/tui/component.rs b/src/tui/component.rs index fac8a97..5a119fe 100644 --- a/src/tui/component.rs +++ b/src/tui/component.rs @@ -27,7 +27,7 @@ pub enum Action { ReloadFile, ShowHideExplorer, ShowHideLogger, - About, + ShowHideAbout, CloseTab, } @@ -60,7 +60,7 @@ pub trait Component { #[derive(Debug, Clone, Default)] pub struct ComponentState { pub(crate) focus: Focus, - pub(crate) vis: Visible, + pub(crate) vis: Visibility, pub(crate) help_text: String, } @@ -73,7 +73,7 @@ impl ComponentState { trace!(target:Self::id(), "Building {}", Self::id()); Self { focus: Active, - vis: Visible::Visible, + vis: Visibility::Visible, help_text: String::new(), } } @@ -111,7 +111,7 @@ impl FocusState for ComponentState { fn with_focus(self, focus: Focus) -> Self { Self { focus, - vis: Visible::Visible, + vis: Visibility::Visible, help_text: self.help_text, } } @@ -133,20 +133,20 @@ impl FocusState for ComponentState { } #[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] -pub enum Visible { +pub enum Visibility { #[default] Visible, Hidden, } pub trait VisibleState { - fn with_visible(self, vis: Visible) -> Self; - fn set_visible(&mut self, vis: Visible); + fn with_visible(self, vis: Visibility) -> Self; + fn set_visible(&mut self, vis: Visibility); fn togget_visible(&mut self); } impl VisibleState for ComponentState { - fn with_visible(self, vis: Visible) -> Self { + fn with_visible(self, vis: Visibility) -> Self { Self { focus: self.focus, vis, @@ -154,14 +154,14 @@ impl VisibleState for ComponentState { } } - fn set_visible(&mut self, vis: Visible) { + fn set_visible(&mut self, vis: Visibility) { self.vis = vis; } fn togget_visible(&mut self) { match self.vis { - Visible::Visible => self.set_visible(Visible::Hidden), - Visible::Hidden => self.set_visible(Visible::Visible), + Visibility::Visible => self.set_visible(Visibility::Hidden), + Visibility::Hidden => self.set_visible(Visibility::Visible), } } } diff --git a/src/tui/menu_bar.rs b/src/tui/menu_bar.rs index ac4d3b2..bc11bb0 100644 --- a/src/tui/menu_bar.rs +++ b/src/tui/menu_bar.rs @@ -199,7 +199,7 @@ impl Component for MenuBar { Reload => Ok(Action::ReloadFile), ShowHideExplorer => Ok(Action::ShowHideExplorer), ShowHideLogger => Ok(Action::ShowHideLogger), - About => Ok(Action::About), + About => Ok(Action::ShowHideAbout), CloseTab => Ok(Action::CloseTab), }; } -- 2.47.2 From 439d3af7d357e80e6e4bb690660f3c4f11f43090 Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Sun, 25 Jan 2026 15:20:06 -0500 Subject: [PATCH 72/73] [tui] Open application with no editor tab. The explorer can be used to open the first tab. --- src/tui/about.rs | 1 - src/tui/app.rs | 16 +--------------- src/tui/editor_tab.rs | 24 ++++++++++++++++++------ 3 files changed, 19 insertions(+), 22 deletions(-) diff --git a/src/tui/about.rs b/src/tui/about.rs index e7a7acc..c5f222a 100644 --- a/src/tui/about.rs +++ b/src/tui/about.rs @@ -1,4 +1,3 @@ -use log::trace; use ratatui::buffer::Buffer; use ratatui::layout::{Constraint, Direction, Layout, Rect}; use ratatui::style::{Modifier, Style}; diff --git a/src/tui/app.rs b/src/tui/app.rs index 04932c5..6537437 100644 --- a/src/tui/app.rs +++ b/src/tui/app.rs @@ -20,9 +20,6 @@ use ratatui::widgets::{Paragraph, Wrap}; use std::path::PathBuf; use std::time::Duration; -// TODO: Need a way to dynamically run Widget::render on all widgets. -// TODO: + Need a way to map Rect to Component::id() to position each widget? -// TODO: Need a good way to dynamically run Component methods on all widgets. #[derive(Debug, Clone, Copy, PartialEq)] pub enum AppComponent { AppEditor, @@ -48,7 +45,7 @@ impl<'a> App<'a> { pub fn new(root_path: PathBuf) -> Result { trace!(target:Self::id(), "Building {}", Self::id()); let app = Self { - editor_tabs: EditorTab::new(&root_path.join("src/tui/app.rs")), + editor_tabs: EditorTab::new(None), explorer: Explorer::new(&root_path)?, logger: Logger::new(), menu_bar: MenuBar::new(), @@ -61,17 +58,6 @@ impl<'a> App<'a> { /// Logic that should be executed once on application startup. pub fn start(&mut self) -> Result<()> { trace!(target:Self::id(), "Starting App"); - let root_path = self.explorer.root_path.clone(); - let editor = self - .editor_tabs - .current_editor_mut() - .context("Failed to get current editor in App::start")?; - editor - .set_contents(&root_path.join("src/tui/app.rs")) - .context(format!( - "Failed to initialize editor contents to path: {root_path:?}" - ))?; - editor.component_state.set_focus(Focus::Active); Ok(()) } diff --git a/src/tui/editor_tab.rs b/src/tui/editor_tab.rs index 9965b16..1a5fb01 100644 --- a/src/tui/editor_tab.rs +++ b/src/tui/editor_tab.rs @@ -23,13 +23,25 @@ impl EditorTab { "EditorTab" } - pub fn new(path: &std::path::PathBuf) -> Self { + pub fn new(path: Option<&std::path::PathBuf>) -> Self { trace!(target:Self::id(), "Building EditorTab with path {path:?}"); - let tab_order = vec![path.to_string_lossy().to_string()]; - Self { - editors: HashMap::from([(tab_order.first().unwrap().to_owned(), Editor::new(path))]), - tab_order, - current_editor: 0, + match path { + None => Self { + editors: HashMap::new(), + tab_order: Vec::new(), + current_editor: 0, + }, + Some(path) => { + let tab_order = vec![path.to_string_lossy().to_string()]; + Self { + editors: HashMap::from([( + tab_order.first().unwrap().to_owned(), + Editor::new(path), + )]), + tab_order, + current_editor: 0, + } + } } } -- 2.47.2 From 9fc7864f31362a6fe84e8c866ac83f6a2086bb7a Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Sun, 25 Jan 2026 15:46:17 -0500 Subject: [PATCH 73/73] Update README. --- README.md | 65 ++++++++++++++++++++++++++++++++++++++++++++-- resources/tui.png | Bin 0 -> 545393 bytes src/main.rs | 2 +- 3 files changed, 64 insertions(+), 3 deletions(-) create mode 100644 resources/tui.png diff --git a/README.md b/README.md index 1335086..dc4dbd7 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,7 @@ CLIDE is an extendable command-line driven development environment written in Rust using the Qt UI framework that supports both full and headless Linux environments. The GUI is written in QML compiled through Rust using the cxx-qt crate, while the TUI was implemented using the ratatui crate. -It's up to you to build your own development environment for your tools. -To add tools for your purposes, create a plugin that implements the `ClidePlugin` trait. (This is currently under development and not yet available.) +It's up to you to build your own development environment for your tools. Plugins are planned to be supported in the future for bringing your own language-specific tools or features. Once you've created your plugin, you can submit a pull request to add a link to the git repository for your plugin to the final section in this README if you'd like to contribute. The following packages must be installed before the application will build. @@ -20,6 +19,68 @@ And of course, [Rust](https://www.rust-lang.org/tools/install). curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh ``` +## Usage + +To install and run clide + +```bash +git clone https://git.shaunreed.com/shaunrd0/clide +cd clide +cargo install --path . +``` + +After installation `clide` can be used directly + +```bash +clide --help + +Extendable command-line driven development environment written in Rust using the Qt UI framework. +If no flags are provided, the GUI editor is launched in a separate process. +If no path is provided, the current directory is used. + +Usage: clide [OPTIONS] [PATH] + +Arguments: + [PATH] The root directory for the project to open with the clide editor + +Options: + -t, --tui Run clide in headless mode + -g, --gui Run the clide GUI in the current process, blocking the terminal and showing all output streams + -h, --help Print help +``` + +### TUI + +The TUI is implemented using the ratatui crate and has the typical features you would expect from a text editor. +You can browse your project tree, open / close new editor tabs, and save / reload files. +Controls for the TUI are listed at the bottom of the window, and update depending on which widget you have focused. +For now, there are no language-specific features or plugins available for the TUI – it is only a text editor. + +To run the TUI, pass the `-t` or `--tui` flags. + +```bash +# With cargo from the project root +cargo run -- -t +# Or via clide directly after installation +clide -t +``` + +![image](./resources/tui.png) + +### GUI + +The GUI is still in development. It is at this point a text viewer, instead of a text editor. +There are many placeholder buttons and features in the GUI that do nothing when used. + +The GUI is run by default when executing the `clide` application. + +```bash +# With cargo from the project root +cargo run +# Or via clide directly after installation +clide +``` + ## Development It's recommended to use RustRover or Qt Creator for development. diff --git a/resources/tui.png b/resources/tui.png new file mode 100644 index 0000000000000000000000000000000000000000..639cf3dd2a9fc4dbfc60aa7247285ed2f0ac12f5 GIT binary patch literal 545393 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 z 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

C>`E#33aEIxJG;@p4@S; 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%0AHp4s@?}($d+fl&lmIV^K(tShY;g^hukC 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%0DOr2#PTR&=P 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 VzT_2Tc9}m)H1F!DmfW#;_78hh_v`=w literal 0 HcmV?d00001 diff --git a/src/main.rs b/src/main.rs index b672903..f5cf2eb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,7 +6,7 @@ use std::process::{Command, Stdio}; pub mod gui; pub mod tui; -/// Command line interface IDE with full GUI and headless modes. +/// Extendable command-line driven development environment written in Rust using the Qt UI framework. /// If no flags are provided, the GUI editor is launched in a separate process. /// If no path is provided, the current directory is used. #[derive(Parser, Debug)] -- 2.47.2