add gui perhaps
This commit is contained in:
113
Cargo.lock
generated
113
Cargo.lock
generated
@@ -1050,7 +1050,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1136,6 +1136,12 @@ dependencies = [
|
||||
"miniz_oxide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "float_next_after"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8bf7cc16383c4b8d58b9905a8509f02926ce3058053c056376248d958c9df1e8"
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
version = "1.0.7"
|
||||
@@ -1222,6 +1228,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"iced",
|
||||
"iced_aw",
|
||||
"imara-diff",
|
||||
"nucleo-matcher",
|
||||
"rmcp",
|
||||
@@ -1675,6 +1682,21 @@ dependencies = [
|
||||
"thiserror 1.0.69",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iced_aw"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42e05df3019f20c6decea93d035b32a2afc7b329d89cc5a68cca097d0e0a1889"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"chrono",
|
||||
"iced",
|
||||
"iced_fonts",
|
||||
"itertools",
|
||||
"num-format",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iced_core"
|
||||
version = "0.13.2"
|
||||
@@ -1695,6 +1717,15 @@ dependencies = [
|
||||
"web-time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iced_fonts"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df7deb0800a850ee25c8a42559f72c0f249e577feb3aad37b9b65dc1e517e52a"
|
||||
dependencies = [
|
||||
"iced_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iced_futures"
|
||||
version = "0.13.2"
|
||||
@@ -1736,6 +1767,7 @@ dependencies = [
|
||||
"iced_core",
|
||||
"iced_futures",
|
||||
"log",
|
||||
"lyon_path",
|
||||
"once_cell",
|
||||
"raw-window-handle",
|
||||
"rustc-hash 2.1.1",
|
||||
@@ -1799,6 +1831,7 @@ dependencies = [
|
||||
"iced_glyphon",
|
||||
"iced_graphics",
|
||||
"log",
|
||||
"lyon",
|
||||
"once_cell",
|
||||
"rustc-hash 2.1.1",
|
||||
"thiserror 1.0.69",
|
||||
@@ -1886,6 +1919,15 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.15"
|
||||
@@ -2049,6 +2091,58 @@ version = "0.12.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38"
|
||||
|
||||
[[package]]
|
||||
name = "lyon"
|
||||
version = "1.0.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dbcb7d54d54c8937364c9d41902d066656817dce1e03a44e5533afebd1ef4352"
|
||||
dependencies = [
|
||||
"lyon_algorithms",
|
||||
"lyon_tessellation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lyon_algorithms"
|
||||
version = "1.0.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f4c0829e28c4f336396f250d850c3987e16ce6db057ffe047ce0dd54aab6b647"
|
||||
dependencies = [
|
||||
"lyon_path",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lyon_geom"
|
||||
version = "1.0.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e16770d760c7848b0c1c2d209101e408207a65168109509f8483837a36cf2e7"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"euclid",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lyon_path"
|
||||
version = "1.0.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1aeca86bcfd632a15984ba029b539ffb811e0a70bf55e814ef8b0f54f506fdeb"
|
||||
dependencies = [
|
||||
"lyon_geom",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lyon_tessellation"
|
||||
version = "1.0.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f3f586142e1280335b1bc89539f7c97dd80f08fc43e9ab1b74ef0a42b04aa353"
|
||||
dependencies = [
|
||||
"float_next_after",
|
||||
"lyon_path",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "malloc_buf"
|
||||
version = "0.0.6"
|
||||
@@ -2239,6 +2333,16 @@ dependencies = [
|
||||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-format"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a652d9771a63711fd3c3deb670acfbe5c30a4072e664d7a3bf5a9e1056ac72c3"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"itoa",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
@@ -2246,6 +2350,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"libm",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3115,7 +3220,7 @@ dependencies = [
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys 0.11.0",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3582,7 +3687,7 @@ dependencies = [
|
||||
"getrandom 0.3.3",
|
||||
"once_cell",
|
||||
"rustix 1.1.2",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4373,7 +4478,7 @@ version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
||||
dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -6,6 +6,7 @@ edition = "2024"
|
||||
[dependencies]
|
||||
anyhow = "1.0.100"
|
||||
iced = { version = "0.13.1", features = ["tokio"] }
|
||||
iced_aw = { version = "0.11.0", features = ["tab_bar"] }
|
||||
imara-diff = "0.2.0"
|
||||
nucleo-matcher = "0.3.1"
|
||||
rmcp = { version = "0.8.0", features = ["server", "macros", "transport-sse-server", "transport-io", "transport-streamable-http-server", "elicitation", "schemars"] }
|
||||
|
||||
196
src/gui.rs
196
src/gui.rs
@@ -1,24 +1,200 @@
|
||||
use iced::{Element, widget::text_editor};
|
||||
use crate::fossil::FossilManager;
|
||||
use iced::{
|
||||
Element,
|
||||
widget::{button, column, row, text, text_editor},
|
||||
};
|
||||
use iced_aw::{TabLabel, Tabs};
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct State {
|
||||
content: text_editor::Content,
|
||||
fossil_manager: FossilManager,
|
||||
tab_editors: HashMap<String, text_editor::Content>,
|
||||
active_tab: Option<String>,
|
||||
tab_order: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Message {
|
||||
Edit(text_editor::Action),
|
||||
Edit(String, text_editor::Action),
|
||||
TabSelected(String),
|
||||
CreateFossil,
|
||||
SyncFossil(String),
|
||||
DeleteFossil(String),
|
||||
RefreshFromManager,
|
||||
}
|
||||
|
||||
impl State {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn with_shared_manager(fossil_manager: FossilManager) -> Self {
|
||||
Self {
|
||||
fossil_manager,
|
||||
tab_editors: HashMap::new(),
|
||||
active_tab: None,
|
||||
tab_order: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_fossil(&mut self, id: String, content: String) {
|
||||
// Add to fossil manager
|
||||
{
|
||||
let mut fossils = self.fossil_manager.fossils.lock().unwrap();
|
||||
fossils.insert(id.clone(), crate::fossil::Fossil::new(content.clone()));
|
||||
}
|
||||
|
||||
// Add editor tab
|
||||
self.tab_editors
|
||||
.insert(id.clone(), text_editor::Content::with_text(&content));
|
||||
self.tab_order.push(id.clone());
|
||||
self.active_tab = Some(id);
|
||||
}
|
||||
|
||||
pub fn sync_fossil(&mut self, id: &str) {
|
||||
if let Some(editor_content) = self.tab_editors.get(id) {
|
||||
let content = editor_content.text();
|
||||
let mut fossils = self.fossil_manager.fossils.lock().unwrap();
|
||||
if let Some(fossil) = fossils.get_mut(id) {
|
||||
fossil.commit(content);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_fossil_to_editor(&mut self, id: &str) {
|
||||
let fossils = self.fossil_manager.fossils.lock().unwrap();
|
||||
if let Some(fossil) = fossils.get(id) {
|
||||
if let Some(latest_content) = fossil.latest() {
|
||||
self.tab_editors.insert(
|
||||
id.to_string(),
|
||||
text_editor::Content::with_text(latest_content),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn refresh_from_manager(&mut self) {
|
||||
let fossils = self.fossil_manager.fossils.lock().unwrap();
|
||||
let fossil_ids: Vec<String> = fossils.keys().cloned().collect();
|
||||
drop(fossils);
|
||||
|
||||
// Add new fossils that don't exist in GUI
|
||||
for id in &fossil_ids {
|
||||
if !self.tab_editors.contains_key(id) {
|
||||
self.load_fossil_to_editor(id);
|
||||
self.tab_order.push(id.clone());
|
||||
}
|
||||
}
|
||||
|
||||
// Remove fossils that no longer exist in manager
|
||||
let gui_fossil_ids: Vec<String> = self.tab_editors.keys().cloned().collect();
|
||||
for id in gui_fossil_ids {
|
||||
if !fossil_ids.contains(&id) {
|
||||
self.tab_editors.remove(&id);
|
||||
self.tab_order.retain(|x| x != &id);
|
||||
if self.active_tab.as_ref() == Some(&id) {
|
||||
self.active_tab = self.tab_order.first().cloned();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update active tab if none is selected but tabs exist
|
||||
if self.active_tab.is_none() && !self.tab_order.is_empty() {
|
||||
self.active_tab = Some(self.tab_order[0].clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn view(state: &State) -> Element<'_, Message> {
|
||||
text_editor(&state.content)
|
||||
.placeholder("Type something here...")
|
||||
.on_action(Message::Edit)
|
||||
.into()
|
||||
let mut tabs = Tabs::new(Message::TabSelected);
|
||||
|
||||
for id in &state.tab_order {
|
||||
tabs = tabs.push(
|
||||
id.clone(),
|
||||
TabLabel::Text(id.clone()),
|
||||
tab_content(state, id),
|
||||
);
|
||||
}
|
||||
|
||||
let tabs = if let Some(active_id) = &state.active_tab {
|
||||
tabs.set_active_tab(active_id)
|
||||
} else {
|
||||
tabs
|
||||
};
|
||||
|
||||
let controls = row![
|
||||
button("New Fossil").on_press(Message::CreateFossil),
|
||||
button("Sync Active").on_press_maybe(
|
||||
state
|
||||
.active_tab
|
||||
.as_ref()
|
||||
.map(|id| Message::SyncFossil(id.clone()))
|
||||
),
|
||||
button("Delete Active").on_press_maybe(
|
||||
state
|
||||
.active_tab
|
||||
.as_ref()
|
||||
.map(|id| Message::DeleteFossil(id.clone()))
|
||||
),
|
||||
button("Refresh").on_press(Message::RefreshFromManager),
|
||||
]
|
||||
.spacing(10)
|
||||
.padding(10);
|
||||
|
||||
column![controls, tabs].spacing(10).padding(10).into()
|
||||
}
|
||||
|
||||
pub fn update(state: &mut State, message: Message) {
|
||||
match message {
|
||||
Message::Edit(action) => {}
|
||||
fn tab_content<'a>(state: &'a State, fossil_id: &str) -> Element<'a, Message> {
|
||||
if let Some(content) = state.tab_editors.get(fossil_id) {
|
||||
text_editor(content)
|
||||
.placeholder("Edit your fossil content here...")
|
||||
.on_action({
|
||||
let fossil_id = fossil_id.to_string();
|
||||
move |action| Message::Edit(fossil_id.clone(), action)
|
||||
})
|
||||
.into()
|
||||
} else {
|
||||
text("No content available").into()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(state: &mut State, message: Message) -> iced::Task<Message> {
|
||||
match message {
|
||||
Message::Edit(fossil_id, action) => {
|
||||
if let Some(content) = state.tab_editors.get_mut(&fossil_id) {
|
||||
content.perform(action);
|
||||
}
|
||||
}
|
||||
Message::TabSelected(tab_id) => {
|
||||
state.active_tab = Some(tab_id);
|
||||
}
|
||||
Message::CreateFossil => {
|
||||
let new_id = format!("fossil_{}", state.tab_order.len() + 1);
|
||||
state.add_fossil(new_id, String::new());
|
||||
}
|
||||
Message::SyncFossil(fossil_id) => {
|
||||
state.sync_fossil(&fossil_id);
|
||||
}
|
||||
Message::DeleteFossil(fossil_id) => {
|
||||
// Remove from fossil manager
|
||||
{
|
||||
let mut fossils = state.fossil_manager.fossils.lock().unwrap();
|
||||
fossils.remove(&fossil_id);
|
||||
}
|
||||
|
||||
// Remove from GUI state
|
||||
state.tab_editors.remove(&fossil_id);
|
||||
state.tab_order.retain(|id| id != &fossil_id);
|
||||
|
||||
// Update active tab
|
||||
if state.active_tab.as_ref() == Some(&fossil_id) {
|
||||
state.active_tab = state.tab_order.first().cloned();
|
||||
}
|
||||
}
|
||||
Message::RefreshFromManager => {
|
||||
state.refresh_from_manager();
|
||||
}
|
||||
}
|
||||
iced::Task::none()
|
||||
}
|
||||
|
||||
19
src/main.rs
19
src/main.rs
@@ -1,8 +1,9 @@
|
||||
use crate::{
|
||||
gui::{update, view},
|
||||
gui::{update, view, State},
|
||||
mcp::FossilEditor,
|
||||
fossil::FossilManager,
|
||||
};
|
||||
use iced::Executor;
|
||||
use iced::Task;
|
||||
use rmcp::{ServiceExt, transport::stdio};
|
||||
use tokio::runtime::Runtime;
|
||||
use tracing_subscriber::{self, EnvFilter};
|
||||
@@ -21,7 +22,10 @@ fn main() -> iced::Result {
|
||||
|
||||
let rt = Runtime::new().unwrap();
|
||||
|
||||
let editor = FossilEditor::new();
|
||||
// Create shared fossil manager
|
||||
let shared_fossil_manager = FossilManager::new();
|
||||
|
||||
let editor = FossilEditor::with_shared_state(shared_fossil_manager.clone());
|
||||
rt.spawn(async {
|
||||
let service = editor
|
||||
.serve(stdio())
|
||||
@@ -34,5 +38,12 @@ fn main() -> iced::Result {
|
||||
service.waiting().await.unwrap();
|
||||
});
|
||||
|
||||
iced::run("A cool counter", update, view)
|
||||
iced::application("Fossil Editor", update, view)
|
||||
.default_font(iced::Font::MONOSPACE)
|
||||
.run_with(move || {
|
||||
let mut state = State::with_shared_manager(shared_fossil_manager);
|
||||
// Add a sample fossil to start with
|
||||
state.add_fossil("sample.py".to_string(), "def hello():\n print('Hello, World!')".to_string());
|
||||
(state, Task::none())
|
||||
})
|
||||
}
|
||||
|
||||
@@ -80,6 +80,13 @@ impl FossilEditor {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_shared_state(state: FossilManager) -> Self {
|
||||
Self {
|
||||
tool_router: Self::tool_router(),
|
||||
state,
|
||||
}
|
||||
}
|
||||
|
||||
#[tool(description = "Creates a new code fossil in memory with initial content.")]
|
||||
fn create_fossil(
|
||||
&self,
|
||||
|
||||
Reference in New Issue
Block a user