Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bunch of changes #7

Merged
merged 13 commits into from
Dec 14, 2024
Merged
186 changes: 103 additions & 83 deletions aardvark-app/src/application.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,41 +26,27 @@ use adw::subclass::prelude::*;
use gettextrs::gettext;
use gtk::{gio, glib};
use tokio::sync::{mpsc, oneshot};
use automerge::PatchAction;

use crate::config::VERSION;
use crate::document::Document;
use crate::glib::closure_local;
use crate::{AardvarkTextBuffer, AardvarkWindow};

mod imp {
use automerge::PatchAction;

use super::*;

#[derive(Debug)]
pub struct AardvarkApplication {
window: OnceCell<AardvarkWindow>,
document: Document,
tx: mpsc::Sender<Vec<u8>>,
rx: RefCell<Option<mpsc::Receiver<Vec<u8>>>>,
pub window: OnceCell<AardvarkWindow>,
pub document: Document,
pub tx: mpsc::Sender<Vec<u8>>,
pub rx: RefCell<Option<mpsc::Receiver<Vec<u8>>>>,
#[allow(dead_code)]
backend_shutdown: oneshot::Sender<()>,
}

impl AardvarkApplication {
fn update_text(&self, position: i32, del: i32, text: &str) {
self.document
.update(position, del, text)
.expect("update automerge document after text update");

let bytes = self.document.save_incremental();
let tx = self.tx.clone();
glib::spawn_future_local(async move {
tx.send(bytes)
.await
.expect("sending message to networking backend");
});
}
}

#[glib::object_subclass]
Expand Down Expand Up @@ -99,70 +85,7 @@ mod imp {
// existing window.
fn activate(&self) {
let application = self.obj();

// Get the current window or create one if necessary
let window = self.window.get_or_init(|| {
let window = AardvarkWindow::new(&*application);
let mut rx = application
.imp()
.rx
.take()
.expect("rx should be given at this point");

{
let window = window.clone();
let application = application.clone();

glib::spawn_future_local(async move {
while let Some(bytes) = rx.recv().await {
let document = &application.imp().document;

// Apply remote changes to our local text CRDT
if let Err(err) = document.load_incremental(&bytes) {
eprintln!(
"failed applying text change from remote peer to automerge document: {err}"
);
continue;
}

// Get latest changes and apply them to our local text buffer
for patch in document.diff_incremental() {
match &patch.action {
PatchAction::SpliceText { index, value, .. } => {
window.splice_text_view(
*index as i32,
0,
value.make_string().as_str(),
);
}
PatchAction::DeleteSeq { index, length } => {
window.splice_text_view(*index as i32, *length as i32, "");
}
_ => (),
}
}

dbg!(document.text());
}
});
}

window
});

{
let application = application.clone();
window.get_text_buffer().connect_closure(
"text-change",
false,
closure_local!(|_buffer: AardvarkTextBuffer,
position: i32,
del: i32,
text: &str| {
application.imp().update_text(position, del, text);
}),
);
}
let window = application.get_window();

// Ask the window manager/compositor to present the window
window.clone().upcast::<gtk::Window>().present();
Expand Down Expand Up @@ -212,4 +135,101 @@ impl AardvarkApplication {

about.present(Some(&window));
}

pub fn get_window(&self) -> &AardvarkWindow {
// Get the current window or create one if necessary
self.imp().window.get_or_init(|| {
let window = AardvarkWindow::new(self);

{
let application = self.clone();
let mut rx = self
.imp()
.rx
.take()
.expect("rx should be given at this point");

glib::spawn_future_local(async move {
while let Some(bytes) = rx.recv().await {
application.ingest_message(bytes);
}
});
}

{
let application = self.clone();

window.get_text_buffer().connect_closure(
"text-change",
false,
closure_local!(|_buffer: AardvarkTextBuffer,
position: i32,
del: i32,
text: &str| {
application.update_text(position, del, text);
}),
);
}

window
})
}

fn ingest_message(&self, message: Vec<u8>) {
let document = &self.imp().document;
let window = self.imp().window.get().unwrap();
let buffer = window.get_text_buffer();

// Apply remote changes to our local text CRDT
if let Err(err) = document.load_incremental(&message) {
eprintln!(
"failed applying text change from remote peer to automerge document: {err}"
);
window.add_toast(adw::Toast::new(
"The network provided bad data!"
));
return;
}

// Get latest changes and apply them to our local text buffer
for patch in document.diff_incremental() {
match &patch.action {
PatchAction::SpliceText { index, value, .. } => {
buffer.splice(
*index as i32,
0,
value.make_string().as_str(),
);
}
PatchAction::DeleteSeq { index, length } => {
buffer.splice(*index as i32, *length as i32, "");
}
_ => (),
}
}

// Sanity check that the text buffer and CRDT are in the same state
if buffer.full_text() != document.text() {
window.add_toast(adw::Toast::new("The CRDT and the text view have different states!"));
// if the state diverged, use the CRDT as the source of truth
buffer.set_text(&document.text());
}

dbg!(document.text());
}

fn update_text(&self, position: i32, del: i32, text: &str) {
self.imp()
.document
.update(position, del, text)
.expect("update automerge document after text update");

let bytes = self.imp().document.save_incremental();
let tx = self.imp().tx.clone();
glib::spawn_future_local(async move {
tx.send(bytes)
.await
.expect("sending message to networking backend");
});
}
}
7 changes: 3 additions & 4 deletions aardvark-app/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,12 @@ mod textbuffer;
mod window;

use std::path::PathBuf;
use std::env;
use self::application::AardvarkApplication;
use self::textbuffer::AardvarkTextBuffer;
use self::window::AardvarkWindow;


use config::{GETTEXT_PACKAGE, LOCALEDIR};
use config::{GETTEXT_PACKAGE, LOCALEDIR, PKGDATADIR};
use gettextrs::{bind_textdomain_codeset, bindtextdomain, textdomain};
use gtk::prelude::*;
use gtk::{gio, glib};
Expand Down Expand Up @@ -63,7 +62,7 @@ fn main() -> glib::ExitCode {
fn get_pkgdatadir() -> PathBuf {
#[cfg(target_os = "macos")]
{
let exe_path = env::current_exe().expect("Failed to get current executable path");
let exe_path = std::env::current_exe().expect("Failed to get current executable path");
// Navigate to the 'Resources/share/aardvark' directory relative to the executable
exe_path
.parent() // Goes up to 'Contents/MacOS'
Expand All @@ -74,6 +73,6 @@ fn get_pkgdatadir() -> PathBuf {

#[cfg(not(target_os = "macos"))]
{
PathBuf::from("/app/share/aardvark")
PathBuf::from(PKGDATADIR)
}
}
22 changes: 21 additions & 1 deletion aardvark-app/src/textbuffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,29 @@ impl AardvarkTextBuffer {
glib::Object::builder().build()
}

pub fn set_inhibit_emit_text_change(&self, inhibit_emit_text_change: bool) {
fn set_inhibit_emit_text_change(&self, inhibit_emit_text_change: bool) {
self.imp()
.inhibit_emit_text_change
.set(inhibit_emit_text_change);
}

pub fn splice(&self, pos: i32, del: i32, text: &str) {
if del != 0 {
let mut begin = self.iter_at_offset(pos);
let mut end = self.iter_at_offset(pos + del);
self.set_inhibit_emit_text_change(true);
self.delete(&mut begin, &mut end);
self.set_inhibit_emit_text_change(false);
return;
}

let mut pos_iter = self.iter_at_offset(pos);
self.set_inhibit_emit_text_change(true);
self.insert(&mut pos_iter, text);
self.set_inhibit_emit_text_change(false);
}

pub fn full_text(&self) -> String {
self.text(&self.start_iter(), &self.end_iter(), true).into()
}
}
49 changes: 9 additions & 40 deletions aardvark-app/src/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,8 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/

use std::sync::OnceLock;

use adw::prelude::AdwDialogExt;
use adw::subclass::prelude::*;
use glib::subclass::Signal;
use gtk::prelude::*;
use gtk::{gio, glib};

Expand All @@ -41,6 +38,8 @@ mod imp {
pub open_document_button: TemplateChild<gtk::Button>,
#[template_child]
pub open_document_dialog: TemplateChild<adw::Dialog>,
#[template_child]
pub toast_overlay: TemplateChild<adw::ToastOverlay>,
}

#[glib::object_subclass]
Expand All @@ -65,27 +64,12 @@ mod imp {
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);
obj.emit_by_name::<()>("text-changed", &[&s.as_str()]);
});

let w = self.obj().clone().upcast::<gtk::Widget>();
let d = self.open_document_dialog.clone();
let window = self.obj().clone();
let dialog = self.open_document_dialog.clone();
self.open_document_button.connect_clicked(move |_| {
d.present(Some(&w));
dialog.present(Some(&window));
});
}

fn signals() -> &'static [Signal] {
static SIGNALS: OnceLock<Vec<Signal>> = OnceLock::new();
SIGNALS.get_or_init(|| {
vec![Signal::builder("text-changed")
.param_types([str::static_type()])
.build()]
})
}
}

impl WidgetImpl for AardvarkWindow {}
Expand All @@ -107,27 +91,12 @@ impl AardvarkWindow {
.build()
}

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();
window.text_view.buffer().downcast().unwrap()
}

pub fn add_toast(&self, toast: adw::Toast) {
self.imp().toast_overlay.add_toast(toast);
}
}
Loading
Loading