Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 24 additions & 1 deletion src/views/editor/text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::{borrow::Cow, fmt::Debug, ops::Range, rc::Rc};
use crate::{
peniko::Color,
peniko::color::palette,
reactive::{RwSignal, Scope},
reactive::{Memo, RwSignal, Scope},
text::{Attrs, AttrsList, FamilyOwned, FontWeight, FontWidth},
views::EditorCustomStyle,
};
Expand Down Expand Up @@ -206,6 +206,17 @@ pub trait Document: DocumentPhantom + ::std::any::Any {
/// ))
/// ```
fn edit(&self, iter: &mut dyn Iterator<Item = (Selection, &str)>, edit_type: EditType);

/// Reactive dirty state for the document.
fn dirty(&self) -> Memo<bool>;

/// Whether the document has been modified since it was last marked pristine.
fn is_dirty(&self) -> bool {
self.dirty().get_untracked()
}

/// Mark the current state as the pristine (saved) baseline.
fn mark_pristine(&self) {}
}

pub trait DocumentPhantom {
Expand Down Expand Up @@ -519,6 +530,18 @@ where
fn edit(&self, iter: &mut dyn Iterator<Item = (Selection, &str)>, edit_type: EditType) {
self.doc.edit(iter, edit_type)
}

fn dirty(&self) -> Memo<bool> {
self.doc.dirty()
}

fn is_dirty(&self) -> bool {
self.doc.is_dirty()
}

fn mark_pristine(&self) {
self.doc.mark_pristine()
}
}
impl<D, F> DocumentPhantom for ExtCmdDocument<D, F>
where
Expand Down
60 changes: 57 additions & 3 deletions src/views/editor/text_document.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ use floem_editor_core::{
selection::Selection,
word::WordCursor,
};
use floem_reactive::{Effect, RwSignal, Scope, SignalGet, SignalTrack, SignalUpdate, SignalWith};
use floem_reactive::{
Effect, Memo, RwSignal, Scope, SignalGet, SignalTrack, SignalUpdate, SignalWith,
};
use lapce_xi_rope::{Rope, RopeDelta};
use smallvec::{SmallVec, smallvec};
use ui_events::keyboard::Modifiers;
Expand Down Expand Up @@ -58,6 +60,7 @@ impl<'a> OnUpdate<'a> {
pub struct TextDocument {
buffer: RwSignal<Buffer>,
cache_rev: RwSignal<u64>,
dirty: Memo<bool>,
preedit: PreeditData,

/// Whether to keep the indent of the previous line when inserting a new line
Expand All @@ -77,7 +80,8 @@ pub struct TextDocument {
impl TextDocument {
pub fn new(cx: Scope, text: impl Into<Rope>) -> TextDocument {
let text = text.into();
let buffer = Buffer::new(text);
let buffer = cx.create_rw_signal(Buffer::new(text));
let dirty = cx.create_memo(move |_| !buffer.with(Buffer::is_pristine));
let preedit = PreeditData {
preedit: cx.create_rw_signal(None),
};
Expand All @@ -94,8 +98,9 @@ impl TextDocument {
});

TextDocument {
buffer: cx.create_rw_signal(buffer),
buffer,
cache_rev,
dirty,
preedit,
keep_indent: Cell::new(true),
auto_indent: Cell::new(false),
Expand Down Expand Up @@ -240,6 +245,14 @@ impl Document for TextDocument {
self.update_cache_rev();
self.on_update(None, deltas);
}

fn dirty(&self) -> Memo<bool> {
self.dirty
}

fn mark_pristine(&self) {
self.buffer.update(Buffer::set_pristine);
}
}
impl DocumentPhantom for TextDocument {
fn phantom_text(&self, edid: EditorId, styling: &EditorStyle, line: usize) -> PhantomTextLine {
Expand Down Expand Up @@ -366,3 +379,44 @@ impl std::fmt::Debug for TextDocument {
s.finish()
}
}

#[cfg(test)]
mod tests {
use floem_editor_core::{cursor::CursorAffinity, editor::EditType, selection::Selection};
use floem_reactive::{Scope, SignalGet};

use super::TextDocument;
use crate::views::editor::text::Document;

#[test]
fn dirty_tracking_respects_pristine_baseline() {
let cx = Scope::new();
let doc = TextDocument::new(cx, "abc");
let dirty = doc.dirty();

assert!(!doc.is_dirty());
assert!(!dirty.get_untracked());

doc.edit_single(
Selection::caret(3, CursorAffinity::Backward),
"d",
EditType::InsertChars,
);
assert!(doc.is_dirty());
assert!(dirty.get_untracked());

let marked_rev = doc.cache_rev().get_untracked();
doc.mark_pristine();
assert!(!doc.is_dirty());
assert!(!dirty.get_untracked());
assert_eq!(doc.cache_rev().get_untracked(), marked_rev);

doc.edit_single(
Selection::caret(4, CursorAffinity::Backward),
"e",
EditType::InsertChars,
);
assert!(doc.is_dirty());
assert!(dirty.get_untracked());
}
}
Loading