Skip to content

Commit

Permalink
Use our own text buffer implementation
Browse files Browse the repository at this point in the history
... and then splice changes into the automerge doc, and only insert/delete
the parts of the text buffer that actually changed (instead of replacing
the whole text all the time).
  • Loading branch information
jonas2515 committed Dec 9, 2024
1 parent 1cadac5 commit 9f78995
Show file tree
Hide file tree
Showing 4 changed files with 175 additions and 36 deletions.
85 changes: 56 additions & 29 deletions src/application.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::*;
Expand All @@ -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
Expand All @@ -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();
Expand Down Expand Up @@ -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);
Expand All @@ -160,19 +159,47 @@ 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);
}
});

window
})
.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::<gtk::Window>().present();
}
Expand Down
2 changes: 2 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down
89 changes: 89 additions & 0 deletions src/textbuffer.rs
Original file line number Diff line number Diff line change
@@ -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 <https://www.gnu.org/licenses/>.
*
* 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<bool>,
}

#[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<Vec<Signal>> = 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<imp::AardvarkTextBuffer>)
@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);
}
}
35 changes: 28 additions & 7 deletions src/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::*;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -90,7 +94,8 @@ mod imp {

glib::wrapper! {
pub struct AardvarkWindow(ObjectSubclass<imp::AardvarkWindow>)
@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 {
Expand All @@ -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()
}
}

0 comments on commit 9f78995

Please sign in to comment.