diff --git a/src/application.rs b/src/application.rs index 9f8a08a..cc74062 100644 --- a/src/application.rs +++ b/src/application.rs @@ -28,11 +28,13 @@ use automerge::{AutoCommit, ObjType}; use gettextrs::gettext; use gtk::{gio, glib}; use tokio::sync::{mpsc, oneshot}; +use automerge::PatchAction; use crate::config::VERSION; use crate::glib::closure_local; use crate::network; use crate::AardvarkWindow; +use crate::AardvarkTextBuffer; mod imp { use super::*; @@ -48,24 +50,9 @@ mod imp { } impl AardvarkApplication { - fn update_text(&self, text: &str) { + fn update_text(&self, position: i32, del: i32, text: &str) { let mut doc = self.automerge.borrow_mut(); - let current_text = match doc.get(automerge::ROOT, "root").expect("root exists") { - Some((_, root)) => doc.text(&root).unwrap(), - None => "".to_owned(), - }; - - println!("CMP '{}' == '{}'", current_text, text); - - if text == "" { - return; - } - - if text == current_text { - return; - } - let root = match doc.get(automerge::ROOT, "root").expect("root exists") { Some(root) => root.1, None => doc @@ -74,7 +61,27 @@ mod imp { }; println!("root = {}", root); - doc.update_text(&root, text).unwrap(); + doc.splice_text(&root,position as usize, del as isize, text).unwrap(); + + // move the diff pointer forward to current position + doc.update_diff_cursor(); +/* + let patches = doc.diff_incremental(); + for patch in patches.iter() { + println!("{}", patch.action); + match &patch.action { + PatchAction::SpliceText { index: _, value: _, marks: _ } => {}, + PatchAction::DeleteSeq { index: _, length: _ } => {}, + PatchAction::PutMap { key: _, value: _, conflict: _ } => {}, + PatchAction::PutSeq { index: _, value: _, conflict: _ } => {}, + PatchAction::Insert { index: _, values: _ } => {}, + PatchAction::Increment { prop: _, value: _ } => {}, + PatchAction::Conflict { prop: _ } => {}, + PatchAction::DeleteMap { key: _ } => {}, + PatchAction::Mark { marks: _ } => {}, + } + } +*/ { let bytes = doc.save_incremental(); @@ -130,17 +137,9 @@ mod imp { .get_or_init(|| { let window = AardvarkWindow::new(&*application); let app = application.clone(); - window.connect_closure( - "text-changed", - false, - closure_local!(|_window: AardvarkWindow, text: &str| { - app.imp().update_text(text); - }), - ); - let mut rx = application.imp().rx.take().unwrap(); let w = window.clone(); - let app = application.clone(); + glib::spawn_future_local(async move { while let Some(bytes) = rx.recv().await { println!("got {:?}", bytes); @@ -160,12 +159,31 @@ mod imp { .expect("inserting map at root"), }; println!("root = {}", root); + + // get the latest changes + let patches = doc_local.diff_incremental(); + for patch in patches.iter() { + println!("PATCH RECEIVED: {}", patch.action); + match &patch.action { + PatchAction::SpliceText { index, value, marks: _ } => { + w.splice_text_view(*index as i32, 0, value.make_string().as_str()); + }, + PatchAction::DeleteSeq { index, length } => { + w.splice_text_view(*index as i32, *length as i32, ""); + }, + PatchAction::PutMap { key: _, value: _, conflict: _ } => {}, + PatchAction::PutSeq { index: _, value: _, conflict: _ } => {}, + PatchAction::Insert { index: _, values: _ } => {}, + PatchAction::Increment { prop: _, value: _ } => {}, + PatchAction::Conflict { prop: _ } => {}, + PatchAction::DeleteMap { key: _ } => {}, + PatchAction::Mark { marks: _ } => {}, + } + } + doc_local.text(&root).unwrap() }; dbg!(&text); - - println!("SET_TEXT = '{}'", text); - w.set_text(&text); } }); @@ -173,6 +191,15 @@ mod imp { }) .clone(); + let app = application.clone(); + window.clone().get_text_buffer().connect_closure( + "text-change", + false, + closure_local!(|_buffer: AardvarkTextBuffer, position: i32, del: i32, text: &str| { + app.imp().update_text(position, del, text); + }), + ); + // Ask the window manager/compositor to present the window window.upcast::().present(); } diff --git a/src/main.rs b/src/main.rs index c875800..c64e448 100644 --- a/src/main.rs +++ b/src/main.rs @@ -23,9 +23,11 @@ mod config; mod network; mod operation; mod window; +mod textbuffer; use self::application::AardvarkApplication; use self::window::AardvarkWindow; +use self::textbuffer::AardvarkTextBuffer; use config::{GETTEXT_PACKAGE, LOCALEDIR, PKGDATADIR}; use gettextrs::{bind_textdomain_codeset, bindtextdomain, textdomain}; diff --git a/src/textbuffer.rs b/src/textbuffer.rs new file mode 100644 index 0000000..6c821ad --- /dev/null +++ b/src/textbuffer.rs @@ -0,0 +1,89 @@ +/* textbuffer.rs + * + * Copyright 2024 Tobias + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +use gtk::prelude::*; +use adw::subclass::prelude::*; +use gtk::glib; +use glib::subclass::Signal; +use std::sync::OnceLock; +use std::cell::Cell; + +mod imp { + use super::*; + + #[derive(Debug, Default)] + pub struct AardvarkTextBuffer { + pub inhibit_emit_text_change: Cell, + } + + #[glib::object_subclass] + impl ObjectSubclass for AardvarkTextBuffer { + const NAME: &'static str = "AardvarkTextBuffer"; + type Type = super::AardvarkTextBuffer; + type ParentType = gtk::TextBuffer; + } + + impl ObjectImpl for AardvarkTextBuffer { + fn signals() -> &'static [Signal] { + static SIGNALS: OnceLock> = OnceLock::new(); + SIGNALS.get_or_init(|| { + vec![Signal::builder("text-change") + .param_types([i32::static_type(), i32::static_type(), str::static_type()]) + .build()] + }) + } + } + + impl TextBufferImpl for AardvarkTextBuffer { + fn insert_text(&self, iter: &mut gtk::TextIter, new_text: &str) { + let offset = iter.offset(); + println!("inserting new text {} at pos {}", new_text, offset); + if !self.inhibit_emit_text_change.get() { + self.obj().emit_by_name::<()>("text-change", &[&offset, &0, &new_text]); + } + self.parent_insert_text(iter, new_text); + } + + fn delete_range(&self, start: &mut gtk::TextIter, end: &mut gtk::TextIter) { + let offset_start = start.offset(); + let offset_end = end.offset(); + println!("deleting range at start {} end {}", offset_start, offset_end); + if !self.inhibit_emit_text_change.get() { + self.obj().emit_by_name::<()>("text-change", &[&offset_start, &(offset_end - offset_start), &""]); + } + self.parent_delete_range(start, end); + } + } +} + +glib::wrapper! { + pub struct AardvarkTextBuffer(ObjectSubclass) + @extends gtk::TextBuffer; +} + +impl AardvarkTextBuffer { + pub fn new() -> Self { + glib::Object::builder().build() + } + + pub fn set_inhibit_emit_text_change(&self, inhibit_emit_text_change: bool) { + self.imp().inhibit_emit_text_change.set(inhibit_emit_text_change); + } +} diff --git a/src/window.rs b/src/window.rs index 2280d75..fed2eee 100644 --- a/src/window.rs +++ b/src/window.rs @@ -24,6 +24,7 @@ use gtk::{gio, glib}; use glib::subclass::Signal; use std::sync::OnceLock; use adw::prelude::AdwDialogExt; +use crate::AardvarkTextBuffer; mod imp { use super::*; @@ -58,7 +59,10 @@ mod imp { impl ObjectImpl for AardvarkWindow { fn constructed(&self) { self.parent_constructed(); - let buffer = self.text_view.buffer(); + + let buffer = AardvarkTextBuffer::new(); + self.text_view.set_buffer(Some(&buffer)); + let obj = self.obj().clone(); buffer.connect_changed(move |buffer| { let s = buffer.text(&buffer.start_iter(), &buffer.end_iter(), false); @@ -90,7 +94,8 @@ mod imp { glib::wrapper! { pub struct AardvarkWindow(ObjectSubclass) - @extends gtk::Widget, gtk::Window, gtk::ApplicationWindow, adw::ApplicationWindow, @implements gio::ActionGroup, gio::ActionMap; + @extends gtk::Widget, gtk::Window, gtk::ApplicationWindow, adw::ApplicationWindow, + @implements gio::ActionGroup, gio::ActionMap; } impl AardvarkWindow { @@ -100,11 +105,27 @@ impl AardvarkWindow { .build() } - pub fn set_text(&self, text: &str) { + pub fn splice_text_view(&self, pos: i32, del: i32, text: &str) { + let window = self.imp(); + let buffer: AardvarkTextBuffer = window.text_view.buffer().downcast().unwrap(); + + if del != 0 { + let mut begin = buffer.iter_at_offset(pos); + let mut end = buffer.iter_at_offset(pos + del); + buffer.set_inhibit_emit_text_change(true); + buffer.delete(&mut begin, &mut end); + buffer.set_inhibit_emit_text_change(false); + return; + } + + let mut pos_iter = buffer.iter_at_offset(pos); + buffer.set_inhibit_emit_text_change(true); + buffer.insert(&mut pos_iter, text); + buffer.set_inhibit_emit_text_change(false); + } + + pub fn get_text_buffer(&self) -> AardvarkTextBuffer { let window = self.imp(); - let buffer = window.text_view.buffer(); - let s = buffer.text(&buffer.start_iter(), &buffer.end_iter(), false); - if text == s { println!("bailing out on superfluous set_text"); return; } - buffer.set_text(text); + window.text_view.buffer().downcast().unwrap() } }