diff --git a/crates/ui/src/highlighter/highlighter.rs b/crates/ui/src/highlighter/highlighter.rs index c966bb62..e68c7de4 100644 --- a/crates/ui/src/highlighter/highlighter.rs +++ b/crates/ui/src/highlighter/highlighter.rs @@ -202,11 +202,11 @@ impl SyntaxHighlighter { pub fn update( &mut self, selected_range: &Range, - full_text: SharedString, + full_text: &SharedString, new_text: &str, cx: &mut App, ) { - if self.text == full_text { + if &self.text == full_text { return; } @@ -244,7 +244,7 @@ impl SyntaxHighlighter { // Update state self.old_tree = Some(new_tree); - self.text = full_text; + self.text = full_text.clone(); // let measure = Measure::new("build_styles"); self.build_styles(changed_ranges, changed_len, cx); diff --git a/crates/ui/src/input/element.rs b/crates/ui/src/input/element.rs index 0615a141..788fd4d7 100644 --- a/crates/ui/src/input/element.rs +++ b/crates/ui/src/input/element.rs @@ -79,7 +79,7 @@ impl TextElement { let mut cursor_bounds = None; // If the input has a fixed height (Otherwise is auto-grow), we need to add a bottom margin to the input. - let bottom_margin = if state.is_auto_grow() { + let bottom_margin = if state.mode.is_auto_grow() { px(0.) + line_height } else { BOTTOM_MARGIN_ROWS * line_height + line_height @@ -336,7 +336,7 @@ impl TextElement { line_height: Pixels, input_height: Pixels, ) -> Range { - if state.is_single_line() { + if state.mode.is_single_line() { return 0..1; } @@ -510,7 +510,7 @@ impl Element for TextElement { let mut style = Style::default(); style.size.width = relative(1.).into(); - if state.is_multi_line() { + if state.mode.is_multi_line() { style.flex_grow = 1.0; if let Some(h) = state.mode.height() { style.size.height = h.into(); @@ -543,7 +543,7 @@ impl Element for TextElement { let highlight_styles = self.highlight_lines(&visible_range, cx); let state = self.state.read(cx); - let multi_line = state.is_multi_line(); + let multi_line = state.mode.is_multi_line(); let text = state.text.clone(); let is_empty = text.is_empty(); let placeholder = self.placeholder.clone(); diff --git a/crates/ui/src/input/hover_popover.rs b/crates/ui/src/input/hover_popover.rs index f7daa82e..b9ffd094 100644 --- a/crates/ui/src/input/hover_popover.rs +++ b/crates/ui/src/input/hover_popover.rs @@ -99,7 +99,7 @@ impl Render for DiagnosticPopover { deferred( div() - .id("code-editor-diagnostic-popover") + .id("diagnostic-popover") .absolute() .left(x) .top(y) diff --git a/crates/ui/src/input/mode.rs b/crates/ui/src/input/mode.rs index 3b0b951b..4e87218c 100644 --- a/crates/ui/src/input/mode.rs +++ b/crates/ui/src/input/mode.rs @@ -43,6 +43,11 @@ pub enum InputMode { rows: usize, height: Option, }, + AutoGrow { + rows: usize, + min_rows: usize, + max_rows: usize, + }, CodeEditor { tab: TabSize, rows: usize, @@ -53,18 +58,33 @@ pub enum InputMode { highlighter: Rc>>, markers: Rc>, }, - AutoGrow { - rows: usize, - min_rows: usize, - max_rows: usize, - }, } +#[allow(unused)] impl InputMode { + #[inline] + pub(super) fn is_single_line(&self) -> bool { + matches!(self, InputMode::SingleLine) + } + + #[inline] pub(super) fn is_code_editor(&self) -> bool { matches!(self, InputMode::CodeEditor { .. }) } + #[inline] + pub(super) fn is_auto_grow(&self) -> bool { + matches!(self, InputMode::AutoGrow { .. }) + } + + #[inline] + pub(super) fn is_multi_line(&self) -> bool { + matches!( + self, + InputMode::MultiLine { .. } | InputMode::AutoGrow { .. } | InputMode::CodeEditor { .. } + ) + } + pub(super) fn set_rows(&mut self, new_rows: usize) { match self { InputMode::MultiLine { rows, .. } => { @@ -162,7 +182,7 @@ impl InputMode { pub(super) fn update_highlighter( &mut self, selected_range: &Range, - full_text: SharedString, + full_text: &SharedString, new_text: &str, cx: &mut App, ) { diff --git a/crates/ui/src/input/number_input.rs b/crates/ui/src/input/number_input.rs index 47169fb6..46cacf3c 100644 --- a/crates/ui/src/input/number_input.rs +++ b/crates/ui/src/input/number_input.rs @@ -1,7 +1,7 @@ use gpui::{ - actions, prelude::FluentBuilder as _, px, AnyElement, App, Context, ElementId, Entity, - EventEmitter, FocusHandle, Focusable, InteractiveElement, IntoElement, KeyBinding, - ParentElement, RenderOnce, SharedString, Styled, Window, + actions, prelude::FluentBuilder as _, px, AnyElement, App, Context, Entity, EventEmitter, + FocusHandle, Focusable, InteractiveElement, IntoElement, KeyBinding, ParentElement, RenderOnce, + SharedString, Styled, Window, }; use crate::{ @@ -24,7 +24,6 @@ pub fn init(cx: &mut App) { #[derive(IntoElement)] pub struct NumberInput { - id: ElementId, state: Entity, placeholder: SharedString, size: Size, @@ -36,7 +35,6 @@ impl NumberInput { /// Create a new [`NumberInput`] element bind to the [`InputState`]. pub fn new(state: &Entity) -> Self { Self { - id: ("number-input", state.entity_id()).into(), state: state.clone(), size: Size::default(), placeholder: SharedString::default(), @@ -123,7 +121,7 @@ impl RenderOnce for NumberInput { let focused = self.state.focus_handle(cx).is_focused(window); h_flex() - .id(self.id) + .id(("number-input", self.state.entity_id())) .key_context(KEY_CONTENT) .on_action(window.listener_for(&self.state, InputState::on_action_increment)) .on_action(window.listener_for(&self.state, InputState::on_action_decrement)) diff --git a/crates/ui/src/input/otp_input.rs b/crates/ui/src/input/otp_input.rs index 393b5d7f..d326e73f 100644 --- a/crates/ui/src/input/otp_input.rs +++ b/crates/ui/src/input/otp_input.rs @@ -244,7 +244,7 @@ impl RenderOnce for OtpInput { groups[group_ix].push( h_flex() - .id(("input-otp", ix)) + .id(ix) .border_1() .border_color(cx.theme().input) .bg(cx.theme().background) @@ -292,6 +292,7 @@ impl RenderOnce for OtpInput { } v_flex() + .id(("otp-input", self.state.entity_id())) .track_focus(&self.state.read(cx).focus_handle) .on_key_down(window.listener_for(&self.state, OtpState::on_key_down)) .items_center() diff --git a/crates/ui/src/input/state.rs b/crates/ui/src/input/state.rs index e9b7bd35..35390536 100644 --- a/crates/ui/src/input/state.rs +++ b/crates/ui/src/input/state.rs @@ -563,7 +563,7 @@ impl InputState { /// /// move_lines: Number of lines to move vertically (positive for down, negative for up). fn move_vertical(&mut self, move_lines: isize, window: &mut Window, cx: &mut Context) { - if self.is_single_line() { + if self.mode.is_single_line() { return; } @@ -654,24 +654,6 @@ impl InputState { cx.notify(); } - #[inline] - pub(super) fn is_multi_line(&self) -> bool { - matches!( - self.mode, - InputMode::MultiLine { .. } | InputMode::AutoGrow { .. } | InputMode::CodeEditor { .. } - ) - } - - #[inline] - pub(super) fn is_single_line(&self) -> bool { - matches!(self.mode, InputMode::SingleLine) - } - - #[inline] - pub(super) fn is_auto_grow(&self) -> bool { - matches!(self.mode, InputMode::AutoGrow { .. }) - } - /// Set the text of the input field. /// /// And the selection_range will be reset to 0..0. @@ -685,7 +667,7 @@ impl InputState { self.replace_text(value, window, cx); self.history.ignore = false; // Ensure cursor to start when set text - if self.is_single_line() { + if self.mode.is_single_line() { self.selected_range = (Cursor::new(self.text.len())..Cursor::new(self.text.len())).into(); } else { @@ -859,7 +841,7 @@ impl InputState { } pub(super) fn up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context) { - if self.is_single_line() { + if self.mode.is_single_line() { return; } @@ -875,7 +857,7 @@ impl InputState { } pub(super) fn down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context) { - if self.is_single_line() { + if self.mode.is_single_line() { return; } @@ -892,7 +874,7 @@ impl InputState { } pub(super) fn page_up(&mut self, _: &MovePageUp, window: &mut Window, cx: &mut Context) { - if self.is_single_line() { + if self.mode.is_single_line() { return; } @@ -910,7 +892,7 @@ impl InputState { window: &mut Window, cx: &mut Context, ) { - if self.is_single_line() { + if self.mode.is_single_line() { return; } @@ -949,7 +931,7 @@ impl InputState { } pub(super) fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context) { - if self.is_single_line() { + if self.mode.is_single_line() { return; } let offset = self.start_of_line(window, cx).saturating_sub(1); @@ -962,7 +944,7 @@ impl InputState { window: &mut Window, cx: &mut Context, ) { - if self.is_single_line() { + if self.mode.is_single_line() { return; } let offset = (self.end_of_line(window, cx) + 1).min(self.text.len()); @@ -1112,7 +1094,7 @@ impl InputState { /// Get start of line fn start_of_line(&mut self, window: &mut Window, cx: &mut Context) -> usize { - if self.is_single_line() { + if self.mode.is_single_line() { return 0; } @@ -1130,7 +1112,7 @@ impl InputState { /// /// This is means is always get the first line of selection. fn start_of_line_of_selection(&mut self, window: &mut Window, cx: &mut Context) -> usize { - if self.is_single_line() { + if self.mode.is_single_line() { return 0; } @@ -1154,7 +1136,7 @@ impl InputState { /// Get end of line fn end_of_line(&mut self, window: &mut Window, cx: &mut Context) -> usize { - if self.is_single_line() { + if self.mode.is_single_line() { return self.text.len(); } @@ -1195,7 +1177,7 @@ impl InputState { window: &mut Window, cx: &mut Context, ) -> String { - if self.is_single_line() { + if self.mode.is_single_line() { return "".into(); } @@ -1326,7 +1308,7 @@ impl InputState { } pub(super) fn enter(&mut self, action: &Enter, window: &mut Window, cx: &mut Context) { - if self.is_multi_line() { + if self.mode.is_multi_line() { // Get current line indent let indent = if self.mode.is_code_editor() { self.indent_of_next_line(window, cx) @@ -1568,25 +1550,28 @@ impl InputState { window: &mut Window, cx: &mut Context, ) { - let offset = self.index_for_mouse_position(event.position, window, cx); - if let Some(marker) = self.mode.marker_for_offset(offset) { - if let Some(diagnostic_popover) = self.diagnostic_popover.as_ref() { - if diagnostic_popover.read(cx).marker.range == marker.range { - diagnostic_popover.update(cx, |this, cx| { - this.show(cx); - }); + if self.mode.is_code_editor() { + // Show diagnostic popover on mouse move + let offset = self.index_for_mouse_position(event.position, window, cx); + if let Some(marker) = self.mode.marker_for_offset(offset) { + if let Some(diagnostic_popover) = self.diagnostic_popover.as_ref() { + if diagnostic_popover.read(cx).marker.range == marker.range { + diagnostic_popover.update(cx, |this, cx| { + this.show(cx); + }); - return; + return; + } } - } - self.diagnostic_popover = Some(DiagnosticPopover::new(marker, cx.entity(), cx)); - cx.notify(); - } else { - if let Some(diagnostic_popover) = self.diagnostic_popover.as_mut() { - diagnostic_popover.update(cx, |this, cx| { - this.check_to_hide(event.position, cx); - }) + self.diagnostic_popover = Some(DiagnosticPopover::new(marker, cx.entity(), cx)); + cx.notify(); + } else { + if let Some(diagnostic_popover) = self.diagnostic_popover.as_mut() { + diagnostic_popover.update(cx, |this, cx| { + this.check_to_hide(event.position, cx); + }) + } } } } @@ -1652,7 +1637,7 @@ impl InputState { pub(super) fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context) { if let Some(clipboard) = cx.read_from_clipboard() { let mut new_text = clipboard.text().unwrap_or_default(); - if !self.is_multi_line() { + if !self.mode.is_multi_line() { new_text = new_text.replace('\n', ""); } @@ -1770,7 +1755,7 @@ impl InputState { let pos = inner_position - line_origin; // Return offset by use closest_index_for_x if is single line mode. - if self.is_single_line() { + if self.mode.is_single_line() { return line.unwrapped_layout.closest_index_for_x(pos.x); } @@ -1820,7 +1805,7 @@ impl InputState { // // If only 1 line, the value is 0 // If have 2 line, the value is 1 - if self.is_multi_line() { + if self.mode.is_multi_line() { let p = point(px(0.), *y_offset); let height = line_height + line.wrap_boundaries.len() as f32 * line_height; *y_offset = *y_offset + height; @@ -2181,9 +2166,9 @@ impl EntityInputHandler for InputState { self.push_history(&range, &new_text, window, cx); self.text = mask_text.clone(); self.mode - .update_highlighter(&range, self.text.clone(), &new_text, cx); + .update_highlighter(&range, &self.text, &new_text, cx); self.mode.clear_markers(); - self.text_wrapper.update(self.text.clone(), false, cx); + self.text_wrapper.update(&self.text, false, cx); self.selected_range = (new_offset..new_offset).into(); self.marked_range.take(); self.update_preferred_x_offset(cx); @@ -2222,9 +2207,9 @@ impl EntityInputHandler for InputState { self.push_history(&range, new_text, window, cx); self.text = pending_text; self.mode - .update_highlighter(&range, self.text.clone(), &new_text, cx); + .update_highlighter(&range, &self.text, &new_text, cx); self.mode.clear_markers(); - self.text_wrapper.update(self.text.clone(), false, cx); + self.text_wrapper.update(&self.text, false, cx); if new_text.is_empty() { // Cancel selection, when cancel IME input. self.selected_range = (range.start..range.start).into(); @@ -2327,14 +2312,13 @@ impl Focusable for InputState { impl Render for InputState { fn render(&mut self, _: &mut Window, cx: &mut Context) -> impl IntoElement { - self.text_wrapper.update(self.text.clone(), false, cx); - self.mode - .update_highlighter(&(0..0), self.text.clone(), "", cx); + self.text_wrapper.update(&self.text, false, cx); + self.mode.update_highlighter(&(0..0), &self.text, "", cx); div() - .id("text-element") + .id("input-state") .flex_1() - .when(self.is_multi_line(), |this| this.h_full()) + .when(self.mode.is_multi_line(), |this| this.h_full()) .flex_grow() .overflow_x_hidden() .child(TextElement::new(cx.entity().clone()).placeholder(self.placeholder.clone())) diff --git a/crates/ui/src/input/text_input.rs b/crates/ui/src/input/text_input.rs index 22e92236..fadb7874 100644 --- a/crates/ui/src/input/text_input.rs +++ b/crates/ui/src/input/text_input.rs @@ -174,8 +174,10 @@ impl RenderOnce for TextInput { let prefix = self.prefix; let suffix = self.suffix; - let show_clear_button = - self.cleanable && !state.loading && !state.text.is_empty() && state.is_single_line(); + let show_clear_button = self.cleanable + && !state.loading + && !state.text.is_empty() + && state.mode.is_single_line(); let has_suffix = suffix.is_some() || state.loading || self.mask_toggle || show_clear_button; div() @@ -198,7 +200,7 @@ impl RenderOnce for TextInput { .on_action(window.listener_for(&self.state, InputState::cut)) .on_action(window.listener_for(&self.state, InputState::undo)) .on_action(window.listener_for(&self.state, InputState::redo)) - .when(state.is_multi_line(), |this| { + .when(state.mode.is_multi_line(), |this| { this.on_action(window.listener_for(&self.state, InputState::indent_inline)) .on_action(window.listener_for(&self.state, InputState::outdent_inline)) .on_action(window.listener_for(&self.state, InputState::indent_block)) @@ -209,7 +211,7 @@ impl RenderOnce for TextInput { .on_action(window.listener_for(&self.state, InputState::right)) .on_action(window.listener_for(&self.state, InputState::select_left)) .on_action(window.listener_for(&self.state, InputState::select_right)) - .when(state.is_multi_line(), |this| { + .when(state.mode.is_multi_line(), |this| { this.on_action(window.listener_for(&self.state, InputState::up)) .on_action(window.listener_for(&self.state, InputState::down)) .on_action(window.listener_for(&self.state, InputState::select_up)) @@ -249,7 +251,7 @@ impl RenderOnce for TextInput { .input_h(self.size) .cursor_text() .text_size(font_size) - .when(state.is_multi_line(), |this| { + .when(state.mode.is_multi_line(), |this| { this.h_auto() .when_some(self.height, |this, height| this.h(height)) }) @@ -297,7 +299,7 @@ impl RenderOnce for TextInput { ) }) .refine_style(&self.style) - .when(state.is_multi_line(), |this| { + .when(state.mode.is_multi_line(), |this| { if state.last_layout.is_some() { this.relative().child( div() diff --git a/crates/ui/src/input/text_wrapper.rs b/crates/ui/src/input/text_wrapper.rs index 5c3a028a..405515e0 100644 --- a/crates/ui/src/input/text_wrapper.rs +++ b/crates/ui/src/input/text_wrapper.rs @@ -47,20 +47,20 @@ impl TextWrapper { pub(super) fn set_wrap_width(&mut self, wrap_width: Option, cx: &mut App) { self.wrap_width = wrap_width; - self.update(self.text.clone(), true, cx); + self.update(&self.text.clone(), true, cx); } pub(super) fn set_font(&mut self, font: Font, font_size: Pixels, cx: &mut App) { self.font = font; self.font_size = font_size; - self.update(self.text.clone(), true, cx); + self.update(&self.text.clone(), true, cx); } /// Update the text wrapper and recalculate the wrapped lines. /// /// If the `text` is the same as the current text, do nothing. - pub(super) fn update(&mut self, text: SharedString, force: bool, cx: &mut App) { - if self.text == text && !force { + pub(super) fn update(&mut self, text: &SharedString, force: bool, cx: &mut App) { + if &self.text == text && !force { return; } @@ -96,7 +96,7 @@ impl TextWrapper { prev_line_ix += line.len() + 1; } - self.text = text; + self.text = text.clone(); self.wrapped_lines = wrapped_lines; self.lines = lines; } diff --git a/crates/ui/src/text/element.rs b/crates/ui/src/text/element.rs index 82cefd9b..c555e31c 100644 --- a/crates/ui/src/text/element.rs +++ b/crates/ui/src/text/element.rs @@ -242,7 +242,7 @@ impl CodeBlock { let mut styles = vec![]; if let Some(lang) = &lang { let mut highlighter = SyntaxHighlighter::new(&lang, cx); - highlighter.update(&(0..0), code.clone(), "", cx); + highlighter.update(&(0..0), &code, "", cx); styles = highlighter.styles(&(0..code.len()), &theme); };