chore: Refactor code for Input. (#1046)

This commit is contained in:
Jason Lee 2025-07-05 14:37:12 +08:00 committed by GitHub
parent 89ee8c0630
commit 4156c831c6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 97 additions and 92 deletions

View file

@ -202,11 +202,11 @@ impl SyntaxHighlighter {
pub fn update(
&mut self,
selected_range: &Range<usize>,
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);

View file

@ -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<usize> {
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();

View file

@ -99,7 +99,7 @@ impl Render for DiagnosticPopover {
deferred(
div()
.id("code-editor-diagnostic-popover")
.id("diagnostic-popover")
.absolute()
.left(x)
.top(y)

View file

@ -43,6 +43,11 @@ pub enum InputMode {
rows: usize,
height: Option<DefiniteLength>,
},
AutoGrow {
rows: usize,
min_rows: usize,
max_rows: usize,
},
CodeEditor {
tab: TabSize,
rows: usize,
@ -53,18 +58,33 @@ pub enum InputMode {
highlighter: Rc<RefCell<Option<SyntaxHighlighter>>>,
markers: Rc<Vec<Marker>>,
},
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<usize>,
full_text: SharedString,
full_text: &SharedString,
new_text: &str,
cx: &mut App,
) {

View file

@ -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<InputState>,
placeholder: SharedString,
size: Size,
@ -36,7 +35,6 @@ impl NumberInput {
/// Create a new [`NumberInput`] element bind to the [`InputState`].
pub fn new(state: &Entity<InputState>) -> 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))

View file

@ -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()

View file

@ -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<Self>) {
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<Self>) {
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<Self>) {
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<Self>) {
if self.is_single_line() {
if self.mode.is_single_line() {
return;
}
@ -910,7 +892,7 @@ impl InputState {
window: &mut Window,
cx: &mut Context<Self>,
) {
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<Self>) {
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<Self>,
) {
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<Self>) -> 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<Self>) -> 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<Self>) -> 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<Self>,
) -> 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<Self>) {
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<Self>,
) {
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<Self>) {
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<Self>) -> 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()))

View file

@ -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()

View file

@ -47,20 +47,20 @@ impl TextWrapper {
pub(super) fn set_wrap_width(&mut self, wrap_width: Option<Pixels>, 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;
}

View file

@ -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);
};