Skip to content

Commit

Permalink
text_editor: impl placeholder
Browse files Browse the repository at this point in the history
  • Loading branch information
MinusGix committed Mar 8, 2024
1 parent 6221c20 commit 30262b2
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 13 deletions.
3 changes: 2 additions & 1 deletion examples/editor/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ fn app_view() -> impl View {
.update(|_| {
// This hooks up to both editors!
println!("Editor changed");
});
})
.placeholder("Some placeholder text");
let doc = editor_a.doc();
let gutter_a = editor_a.editor().gutter;
let gutter_b = editor_b.editor().gutter;
Expand Down
1 change: 1 addition & 0 deletions src/views/editor/phantom_text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ pub struct PhantomText {
pub enum PhantomTextKind {
/// Input methods
Ime,
Placeholder,
/// Completion lens / Inline completion
Completion,
/// Inlay hints supplied by an LSP/PSP (like type annotations)
Expand Down
66 changes: 59 additions & 7 deletions src/views/editor/text_document.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,17 @@ use floem_editor_core::{
selection::Selection,
word::WordCursor,
};
use floem_reactive::{RwSignal, Scope};
use floem_reactive::{create_effect, RwSignal, Scope};
use floem_winit::keyboard::ModifiersState;
use lapce_xi_rope::{Rope, RopeDelta};
use smallvec::{smallvec, SmallVec};

use super::{
actions::{handle_command_default, CommonAction},
color::EditorColor,
command::{Command, CommandExecuted},
id::EditorId,
phantom_text::PhantomTextLine,
phantom_text::{PhantomText, PhantomTextKind, PhantomTextLine},
text::{Document, DocumentPhantom, PreeditData, SystemClipboard},
Editor,
};
Expand Down Expand Up @@ -64,6 +65,8 @@ pub struct TextDocument {
/// Whether to automatically indent the new line via heuristics
pub auto_indent: Cell<bool>,

pub placeholders: RwSignal<HashMap<EditorId, String>>,

// (cmd: &Command, count: Option<usize>, modifiers: ModifierState)
/// Ran before a command is executed. If it says that it executed the command, then handlers
/// after it will not be called.
Expand All @@ -78,13 +81,25 @@ impl TextDocument {
let preedit = PreeditData {
preedit: cx.create_rw_signal(None),
};
let cache_rev = cx.create_rw_signal(0);

let placeholders = cx.create_rw_signal(HashMap::new());

// Whenever the placeholders change, update the cache rev
create_effect(move |_| {
placeholders.track();
cache_rev.try_update(|cache_rev| {
*cache_rev += 1;
});
});

TextDocument {
buffer: cx.create_rw_signal(buffer),
cache_rev: cx.create_rw_signal(0),
cache_rev,
preedit,
keep_indent: Cell::new(true),
auto_indent: Cell::new(false),
placeholders,
pre_command: Rc::new(RefCell::new(HashMap::new())),
on_updates: Rc::new(RefCell::new(SmallVec::new())),
}
Expand Down Expand Up @@ -126,6 +141,17 @@ impl TextDocument {
pub fn clear_on_updates(&self) {
self.on_updates.borrow_mut().clear();
}

pub fn add_placeholder(&self, editor_id: EditorId, placeholder: String) {
self.placeholders.update(|placeholders| {
placeholders.insert(editor_id, placeholder);
});
}

fn placeholder(&self, editor_id: EditorId) -> Option<String> {
self.placeholders
.with_untracked(|placeholders| placeholders.get(&editor_id).cloned())
}
}
impl Document for TextDocument {
fn text(&self) -> Rope {
Expand Down Expand Up @@ -216,12 +242,38 @@ impl Document for TextDocument {
}
}
impl DocumentPhantom for TextDocument {
fn phantom_text(&self, _editor: &Editor, _line: usize) -> PhantomTextLine {
PhantomTextLine::default()
fn phantom_text(&self, editor: &Editor, _line: usize) -> PhantomTextLine {
let mut text = SmallVec::new();

if self.buffer.with_untracked(Buffer::is_empty) {
if let Some(placeholder) = self.placeholder(editor.id()) {
text.push(PhantomText {
kind: PhantomTextKind::Placeholder,
col: 0,
text: placeholder,
font_size: None,
fg: Some(editor.color(EditorColor::Dim)),
bg: None,
under_line: None,
});
}
}

PhantomTextLine { text }
}

fn has_multiline_phantom(&self, _editor: &Editor) -> bool {
false
fn has_multiline_phantom(&self, editor: &Editor) -> bool {
if !self.buffer.with_untracked(Buffer::is_empty) {
return false;
}

self.placeholders.with_untracked(|placeholder| {
let Some(placeholder) = placeholder.get(&editor.id()) else {
return false;
};

placeholder.lines().count() > 1
})
}
}
impl CommonAction for TextDocument {
Expand Down
35 changes: 30 additions & 5 deletions src/views/text_editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,12 @@ impl TextEditor {
self.editor.doc()
}

/// Try downcasting the document to a [`TextDocument`].
/// Returns `None` if the document is not a [`TextDocument`].
fn text_doc(&self) -> Option<Rc<TextDocument>> {
self.doc().downcast_rc().ok()
}

// TODO(minor): should this be named `text`? Ideally most users should use the rope text version
pub fn rope_text(&self) -> RopeTextVal {
self.editor.rope_text()
Expand Down Expand Up @@ -204,6 +210,21 @@ impl TextEditor {
self
}

/// Set the placeholder text that is displayed when the document is empty.
/// Can span multiple lines.
/// This is per-editor, not per-document.
/// Equivalent to calling [`TextDocument::add_placeholder`]
/// Default: `None`
///
/// Note: only works for the default backing [`TextDocument`] doc
pub fn placeholder(self, text: impl Into<String>) -> Self {
if let Some(doc) = self.text_doc() {
doc.add_placeholder(self.editor_id(), text.into());
}

self
}

/// When commands are run on the document, this function is called.
/// If it returns [`CommandExecuted::Yes`] then further handlers after it, including the
/// default handler, are not executed.
Expand All @@ -229,18 +250,22 @@ impl TextEditor {
/// CommandExecuted::No
/// });
/// ```
/// Note that these are specific to each text editor view.
/// Note that these are specific to each text editor view.
///
/// Note: only works for the default backing [`TextDocument`] doc
pub fn pre_command(self, f: impl Fn(PreCommand) -> CommandExecuted + 'static) -> Self {
let doc: Result<Rc<TextDocument>, _> = self.editor.doc().downcast_rc();
if let Ok(doc) = doc {
if let Some(doc) = self.text_doc() {
doc.add_pre_command(self.editor.id(), f);
}
self
}

/// Listen for deltas applied to the editor.
/// Useful for anything that has positions based in the editor that can be updated after
/// typing, such as syntax highlighting.
/// Note: only works for the default backing [`TextDocument`] doc
pub fn update(self, f: impl Fn(OnUpdate) + 'static) -> Self {
let doc: Result<Rc<TextDocument>, _> = self.editor.doc().downcast_rc();
if let Ok(doc) = doc {
if let Some(doc) = self.text_doc() {
doc.add_on_update(f);
}
self
Expand Down

0 comments on commit 30262b2

Please sign in to comment.