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
22 changes: 10 additions & 12 deletions examples/editor/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
use floem::{
prelude::*,
views::editor::{
command::{Command, CommandExecuted},
core::{
command::EditCommand, cursor::CursorAffinity, editor::EditType, selection::Selection,
},
core::{cursor::CursorAffinity, editor::EditType, selection::Selection},
text::{default_dark_color, SimpleStyling},
},
};
Expand All @@ -23,36 +20,37 @@ fn app_view() -> impl IntoView {
.style(|s| s.size_full())
.editor_style(default_dark_color)
.editor_style(move |s| s.hide_gutter(hide_gutter_a.get()));
let focus_editor_a = editor_a.editor().clone();
let editor_b = editor_a
.shared_editor()
.editor_style(default_dark_color)
.editor_style(move |s| s.hide_gutter(hide_gutter_b.get()))
.style(|s| s.size_full())
.pre_command(|ev| {
if matches!(ev.cmd, Command::Edit(EditCommand::Undo)) {
println!("Undo command executed on editor B, ignoring!");
return CommandExecuted::Yes;
}
CommandExecuted::No
})
.update(|_| {
// This hooks up to both editors!
println!("Editor changed");
})
.placeholder("Some placeholder text");
let doc = editor_a.doc();
let clear_editor_a = editor_a.editor().clone();

Stack::new((
editor_a,
editor_b,
Stack::new((
Button::new("Clear").action(move || {
doc.edit_single(
doc.edit_single_from(
&clear_editor_a,
Selection::region(0, doc.text().len(), CursorAffinity::Backward),
"",
EditType::DeleteSelection,
);
}),
Button::new("Focus A").action(move || {
if let Some(id) = focus_editor_a.editor_view_id.get_untracked() {
id.request_focus();
}
}),
Button::new("Flip Gutter").action(move || {
hide_gutter_a.update(|hide| *hide = !*hide);
hide_gutter_b.update(|hide| *hide = !*hide);
Expand Down
4 changes: 3 additions & 1 deletion examples/syntax-editor/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -215,12 +215,14 @@ mod tests {
.style(|s| s.size_full());

let doc = editor.doc();
let clear_editor = editor.editor().clone();

Stack::new((
editor,
Stack::new((
Button::new("Clear").action(move || {
doc.edit_single(
doc.edit_single_from(
&clear_editor,
Selection::region(0, doc.text().len(), CursorAffinity::Backward),
"",
EditType::DeleteSelection,
Expand Down
16 changes: 4 additions & 12 deletions examples/widget-gallery/src/texteditor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,7 @@ use floem::{
action::inspect,
prelude::*,
views::editor::{
command::{Command, CommandExecuted},
core::{
command::EditCommand, cursor::CursorAffinity, editor::EditType, selection::Selection,
},
core::{cursor::CursorAffinity, editor::EditType, selection::Selection},
text::{SimpleStyling, default_dark_color},
},
};
Expand All @@ -28,26 +25,21 @@ pub fn editor_view() -> impl IntoView {
.editor_style(default_dark_color)
.editor_style(move |s| s.hide_gutter(!hide_gutter_a.get()))
.style(|s| s.size_full())
.pre_command(|ev| {
if matches!(ev.cmd, Command::Edit(EditCommand::Undo)) {
println!("Undo command executed on editor B, ignoring!");
return CommandExecuted::Yes;
}
CommandExecuted::No
})
.update(|_| {
// This hooks up to both editors!
println!("Editor changed");
})
.placeholder("Some placeholder text");
let doc = editor_a.doc();
let clear_editor_a = editor_a.editor().clone();

Stack::new((
editor_a,
editor_b,
Stack::new((
Button::new("Clear").action(move || {
doc.edit_single(
doc.edit_single_from(
&clear_editor_a,
Selection::region(0, doc.text().len(), CursorAffinity::Backward),
"",
EditType::DeleteSelection,
Expand Down
68 changes: 54 additions & 14 deletions src/views/editor/text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -188,24 +188,44 @@ pub trait Document: DocumentPhantom + ::std::any::Any {
self.edit(&mut iter, edit_type);
}

/// Perform the edit(s) on this document.
/// Perform a single edit while preserving editor-specific semantics for the initiating editor.
///
/// This intentionally does not require an `Editor` as this is primarily intended for use by
/// code that wants to modify the document from 'outside' the usual keybinding/command logic.
/// Use this when a UI action edits the document on behalf of a live editor and should record
/// that editor's cursor state for undo.
fn edit_single_from(
&self,
editor: &Editor,
selection: Selection,
content: &str,
edit_type: EditType,
) {
let mut iter = std::iter::once((selection, content));
self.edit_from(editor, &mut iter, edit_type);
}

/// Perform the edit(s) on this document.
///
/// ```rust,ignore
/// let editor: TextEditor = text_editor();
/// let doc: Rc<dyn Document> = editor.doc();
/// This intentionally does not require an `Editor`. It is the raw document mutation path for
/// code that wants to modify text without an initiating editor context.
///
/// stack((
/// editor,
/// button(|| "Append 'Hello'").on_click_stop(move |_| {
/// let text = doc.text();
/// doc.edit_single(Selection::caret(text.len()), "Hello", EditType::InsertChars);
/// })
/// ))
/// ```
/// Because it has no initiating editor context, it can't record that editor's cursor history
/// for undo. If a UI action is editing on behalf of a live editor, prefer
/// [`Document::edit_from`] or [`Document::edit_single_from`] instead.
fn edit(&self, iter: &mut dyn Iterator<Item = (Selection, &str)>, edit_type: EditType);

/// Perform the edit(s) on this document using the provided editor as the initiating context.
///
/// The default implementation falls back to [`Document::edit`], which keeps this additive for
/// custom `Document` implementations that do not yet preserve initiating-editor cursor history
/// for undo.
fn edit_from(
&self,
_editor: &Editor,
iter: &mut dyn Iterator<Item = (Selection, &str)>,
edit_type: EditType,
) {
self.edit(iter, edit_type);
}
}

pub trait DocumentPhantom {
Expand Down Expand Up @@ -516,9 +536,29 @@ where
self.doc.edit_single(selection, content, edit_type)
}

fn edit_single_from(
&self,
editor: &Editor,
selection: Selection,
content: &str,
edit_type: EditType,
) {
self.doc
.edit_single_from(editor, selection, content, edit_type)
}

fn edit(&self, iter: &mut dyn Iterator<Item = (Selection, &str)>, edit_type: EditType) {
self.doc.edit(iter, edit_type)
}

fn edit_from(
&self,
editor: &Editor,
iter: &mut dyn Iterator<Item = (Selection, &str)>,
edit_type: EditType,
) {
self.doc.edit_from(editor, iter, edit_type)
}
}
impl<D, F> DocumentPhantom for ExtCmdDocument<D, F>
where
Expand Down
56 changes: 49 additions & 7 deletions src/views/editor/text_document.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,46 @@ impl TextDocument {
});
}

fn apply_programmatic_edit(
&self,
editor: Option<&Editor>,
iter: &mut dyn Iterator<Item = (Selection, &str)>,
edit_type: EditType,
) {
let mut cursor = editor.map(|editor| editor.cursor.get_untracked());
let cursor_before = cursor.as_ref().map(|cursor| cursor.mode.clone());

let deltas = self
.buffer
.try_update(|buffer| buffer.edit(iter, edit_type));
let deltas = deltas.map(|x| [x]);
let deltas = deltas.as_ref().map(|x| x as &[_]).unwrap_or(&[]);

if deltas.is_empty() {
return;
}

if let Some(cursor) = cursor.as_mut() {
for delta in deltas.iter().map(|(_, delta, _)| delta) {
cursor.apply_delta(delta);
}
}

if let (Some(cursor_before), Some(cursor)) = (cursor_before, cursor.as_ref()) {
self.buffer.update(|buffer| {
buffer.set_cursor_before(cursor_before);
buffer.set_cursor_after(cursor.mode.clone());
});
}

self.update_cache_rev();
self.on_update(editor, deltas);

if let (Some(editor), Some(cursor)) = (editor, cursor) {
editor.cursor.set(cursor);
}
}

fn placeholder(&self, editor_id: EditorId) -> Option<String> {
self.placeholders
.with_untracked(|placeholders| placeholders.get(&editor_id).cloned())
Expand Down Expand Up @@ -231,14 +271,16 @@ impl Document for TextDocument {
}

fn edit(&self, iter: &mut dyn Iterator<Item = (Selection, &str)>, edit_type: EditType) {
let deltas = self
.buffer
.try_update(|buffer| buffer.edit(iter, edit_type));
let deltas = deltas.map(|x| [x]);
let deltas = deltas.as_ref().map(|x| x as &[_]).unwrap_or(&[]);
self.apply_programmatic_edit(None, iter, edit_type);
}

self.update_cache_rev();
self.on_update(None, deltas);
fn edit_from(
&self,
editor: &Editor,
iter: &mut dyn Iterator<Item = (Selection, &str)>,
edit_type: EditType,
) {
self.apply_programmatic_edit(Some(editor), iter, edit_type);
}
}
impl DocumentPhantom for TextDocument {
Expand Down
Loading