add gui perhaps

This commit is contained in:
senstella
2025-10-08 20:26:04 +09:00
parent e6c6f94616
commit 02ef2098b3
5 changed files with 318 additions and 18 deletions

View File

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

View File

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

View File

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