Rust 和 GTK 文本编辑器:使用共享数据排除撤消和重做操作故障

问题描述 投票:0回答:1

我一直在努力开发自己的文本编辑器,作为学习 Rust 的一种方式。但即使有了 ChatGPT 的帮助以及整个 Stack Overflow 已经提出的问题,我还是遇到了障碍。 我无法进行撤消和重做操作。

目前只有 Rc 和 Arc 可以编译,但是当要使用带有 Rc 的撤消和重做系统时,它会出现恐慌,而使用 Arc 时,它会冻结直到我杀死它。

我尝试过对数据的引用,但由于堆栈需要改变它不起作用,而且由于 gtk 的工作方式而需要通过上下文传递它,这增加了问题的巨大复杂性。

我正在尝试做一个正常状态堆栈和撤消堆栈系统,它需要作为共享数据在整个应用程序中工作。

我在 GitLab 上有完整的代码这里

但我知道它一定在这里的某个地方: 主要.rs

pub mod structs;
// ...
pub mod unre_handler_impl;
// ...
pub mod undo_redo;

mod user_interface;

use gtk::prelude::*;
use gtk::{Application, TextBuffer};
use user_interface::build_ui;
use std::sync::{Arc, Mutex};
use crate::structs::{HeaderBarItems, WindowContentItems, UndoRedoHandler};
use crate::save_load_backend::{save_buffer_to_file, load_file_into_buffer};
use crate::undo_redo::{character_change_handler, undo_handler, redo_handler};

fn main() {
    gtk::init().expect("Failed to start GTK");

    let app = Application::builder()
        .application_id("com.savagedevs.rust_writer")
        .build();

    let cloned_app = app.clone();
    app.connect_activate(move |_| {
        let undo_redo_handler = Arc::new(Mutex::new(UndoRedoHandler::new()));

        // ...

        let undo_redo_text = text_buffer.clone();
        let undo_redo_data: Arc<Mutex<UndoRedoHandler>> = Arc::clone(&undo_redo_handler);
        text_buffer.connect_changed(move |_| {
            let mut undo_redo_data = undo_redo_data.lock().unwrap();
            character_change_handler(&undo_redo_text, &mut undo_redo_data);
            drop(undo_redo_data);
        });

        let undo_text = text_buffer.clone();
        let undo_data: Arc<Mutex<UndoRedoHandler>> = Arc::clone(&undo_redo_handler);
        edit_menu.edit_undo.connect_activate(move |_| {
            let mut undo_data = undo_data.lock().unwrap();
            undo_handler(&undo_text, &mut undo_data);
            drop(undo_data);
        });

        let redo_text = text_buffer.clone();
        let redo_data: Arc<Mutex<UndoRedoHandler>> = Arc::clone(&undo_redo_handler);
        edit_menu.edit_redo.connect_activate(move |_| {
            let mut redo_data = redo_data.lock().unwrap();
            redo_handler(&redo_text, &mut redo_data);
            drop(redo_data);
        });
    });
    app.run();
}

undo_redo.rs

use gtk::prelude::*;
use gtk::TextBuffer;
use crate::structs::UndoRedoHandler;

pub fn character_change_handler(text_buffer: &TextBuffer, undo_redo: &mut UndoRedoHandler) {
    println!("Text Changed");
    
    let start_iter = text_buffer.start_iter();
    let end_iter = text_buffer.end_iter();

    let raw_text = text_buffer.text(&start_iter, &end_iter, true).unwrap();
    let text_slice = raw_text.to_string();

    if text_slice.len() <= 2 {
        return;
    }
    let last_two_chars = &text_slice[text_slice.len() - 2..];

    if !last_two_chars.starts_with(' ') && last_two_chars.chars().nth(1) == Some(' ') {
        undo_redo.push(text_slice);
        println!("Word detected");
    }
}

pub fn undo_handler(text_buffer: &TextBuffer, undo_redo: &mut UndoRedoHandler) {
    if let Some(text) = undo_redo.undo() {
        text_buffer.set_text(text.as_str());
    } else {
        println!("Nothing to undo.");
    }
}

pub fn redo_handler(text_buffer: &TextBuffer, undo_redo: &mut UndoRedoHandler) {
    if let Some(text) = undo_redo.redo() {
        text_buffer.set_text(text.as_str());
    } else {
        println!("Nothing to redo.");
    }
}

结构.rs

// ...
#[derive(Debug, Clone, PartialEq)]
pub struct UndoRedoHandler {
    pub history: Vec<String>,
    pub undo_stack: Vec<String>,
}

unre_handler_impl.rs

use crate::structs::UndoRedoHandler;

impl UndoRedoHandler {
    pub fn new() -> Self {
        UndoRedoHandler {
            history: Vec::new(),
            undo_stack: Vec::new(),
        }
    }

    pub fn push(&mut self, item: String) {
        self.history.push(item);
        self.undo_stack.clear();
    }

    pub fn undo(&mut self) -> Option<String> {
        if let Some(item) = self.history.pop() {
            self.undo_stack.push(item.clone());
            self.history.last().cloned()
        } else {
            None
        }
    }

    pub fn redo(&mut self) -> Option<String> {
        if let Some(item) = self.undo_stack.pop() {
            self.history.push(item.clone());
            self.history.last().cloned()
        } else {
            None
        }
    }
}

impl Default for UndoRedoHandler {
    fn default() -> Self {
        Self::new()
    }
}

任何建议将不胜感激,我正处于想要拔掉头发的地步。仅仅共享一个可在应用程序的所有部分使用的通用撤消重做缓冲区不应该那么复杂。

rust gtk3 undo-redo gtk-rs
1个回答
0
投票

我发现问题了!每当我触发撤消事件时,它都会更改文本,从而触发来自 GTK 的

connect_changed()
事件,该事件会尝试双重锁定互斥锁,从而导致死锁。为了解决这个问题,我必须改变:

let undo_redo_text = text_buffer.clone();
let undo_redo_data: Arc<Mutex<UndoRedoHandler>> = Arc::clone(&undo_redo_handler);
text_buffer.connect_changed(move |_| {
    let mut undo_redo_data = undo_redo_data.lock().unwrap();
    character_change_handler(&undo_redo_text, &mut undo_redo_data);
    drop(undo_redo_data);
});

至:

let undo_redo_text = text_buffer.clone();
let undo_redo_data: Arc<Mutex<UndoRedoHandler>> = Arc::clone(&undo_redo_handler);
text_buffer.connect_changed(move |_| {
    let mut undo_redo_data = match undo_redo_data.try_lock() {
        Ok(data) => data,
        Err(_) => return, // Return early if lock cannot be acquired
    };
    character_change_handler(&undo_redo_text, &mut undo_redo_data);
    drop(undo_redo_data);
});

这样,每当互斥体繁忙时,它都不会尝试重新锁定。现在撤消和重做的行为与应有的完全一样。如果你的速度超人性的快,可能会出现一种边缘情况,这可能会导致它放弃一个状态,但我不认为任何人都可以在锁持续的 200 纳秒内为一个状态编写足够的内容。

© www.soinside.com 2019 - 2024. All rights reserved.