fix clippy

This commit is contained in:
2026-06-03 20:16:30 +07:00
parent b057cee65b
commit 87e0003543
11 changed files with 115 additions and 455 deletions

View File

@@ -173,3 +173,12 @@ where
} }
} }
} }
impl<I> Default for History<I>
where
I: HistoryItem,
{
fn default() -> Self {
Self::new()
}
}

View File

@@ -149,9 +149,7 @@ impl Element for EditorScrollbar {
cx: &mut App, cx: &mut App,
) -> Self::PrepaintState { ) -> Self::PrepaintState {
let state = self.state.read(cx); let state = self.state.read(cx);
let Some(snapshot) = state.editor_scrollbar_snapshot.get() else { let snapshot = state.editor_scrollbar_snapshot.get()?;
return None;
};
let scroll_handle = state.scroll_handle.clone(); let scroll_handle = state.scroll_handle.clone();
if scroll_handle.offset() != snapshot.cursor_scroll_offset { if scroll_handle.offset() != snapshot.cursor_scroll_offset {
@@ -492,7 +490,7 @@ impl TextElement {
bounds.size.height, bounds.size.height,
); );
bounds.origin = bounds.origin + scroll_offset; bounds.origin += scroll_offset;
(cursor_bounds, scroll_offset, current_row) (cursor_bounds, scroll_offset, current_row)
} }
@@ -622,17 +620,17 @@ impl TextElement {
let mut rev_line_corners = line_corners.iter().rev().peekable(); let mut rev_line_corners = line_corners.iter().rev().peekable();
while let Some(corners) = rev_line_corners.next() { while let Some(corners) = rev_line_corners.next() {
points.push(corners.top_left); points.push(corners.top_left);
if let Some(next) = rev_line_corners.peek() { if let Some(next) = rev_line_corners.peek()
if next.top_left.x > corners.top_left.x { && next.top_left.x > corners.top_left.x
points.push(point(next.top_left.x, corners.top_left.y)); {
} points.push(point(next.top_left.x, corners.top_left.y));
} }
} }
// print_points_as_svg_path(&line_corners, &points); // print_points_as_svg_path(&line_corners, &points);
let path_origin = bounds.origin + point(line_number_width, px(0.)); let path_origin = bounds.origin + point(line_number_width, px(0.));
let first_p = *points.get(0).unwrap(); let first_p = *points.first().unwrap();
let mut builder = gpui::PathBuilder::fill(); let mut builder = gpui::PathBuilder::fill();
builder.move_to(path_origin + first_p); builder.move_to(path_origin + first_p);
for p in points.iter().skip(1) { for p in points.iter().skip(1) {
@@ -690,10 +688,10 @@ impl TextElement {
} }
let mut selected_range = state.selected_range; let mut selected_range = state.selected_range;
if let Some(ime_marked_range) = &state.ime_marked_range { if let Some(ime_marked_range) = &state.ime_marked_range
if !ime_marked_range.is_empty() { && !ime_marked_range.is_empty()
selected_range = (ime_marked_range.end..ime_marked_range.end).into(); {
} selected_range = (ime_marked_range.end..ime_marked_range.end).into();
} }
if selected_range.is_empty() { if selected_range.is_empty() {
return None; return None;
@@ -713,7 +711,7 @@ impl TextElement {
let range = start_ix.max(last_layout.visible_range_offset.start) let range = start_ix.max(last_layout.visible_range_offset.start)
..end_ix.min(last_layout.visible_range_offset.end); ..end_ix.min(last_layout.visible_range_offset.end);
Self::layout_match_range(range, &last_layout, bounds) Self::layout_match_range(range, last_layout, bounds)
} }
/// Calculate the visible range of lines in the viewport. /// Calculate the visible range of lines in the viewport.
@@ -1065,7 +1063,7 @@ impl TextElement {
let shaped_line = window.text_system().shape_line( let shaped_line = window.text_system().shape_line(
display_text.to_string().into(), display_text.to_string().into(),
font_size, font_size,
&runs, runs,
None, None,
); );
@@ -1114,7 +1112,7 @@ impl TextElement {
let mut wrapped_lines = SmallVec::with_capacity(1); let mut wrapped_lines = SmallVec::with_capacity(1);
for range in &line_item.wrapped_lines { for range in &line_item.wrapped_lines {
let line_runs = runs_for_range(runs, run_offset, &range); let line_runs = runs_for_range(runs, run_offset, range);
let line_runs = if bg_segments.is_empty() { let line_runs = if bg_segments.is_empty() {
line_runs line_runs
} else { } else {
@@ -1260,10 +1258,7 @@ impl IntoElement for TextElement {
/// A debug function to print points as SVG path. /// A debug function to print points as SVG path.
#[allow(unused)] #[allow(unused)]
fn print_points_as_svg_path( fn print_points_as_svg_path(line_corners: &Vec<gpui::Corners<Pixels>>, points: Vec<Point<Pixels>>) {
line_corners: &Vec<gpui::Corners<Pixels>>,
points: &Vec<Point<Pixels>>,
) {
for corners in line_corners { for corners in line_corners {
println!( println!(
"tl: ({}, {}), tr: ({}, {}), bl: ({}, {}), br: ({}, {})", "tl: ({}, {}), tr: ({}, {}), bl: ({}, {}), br: ({}, {})",
@@ -1278,7 +1273,7 @@ fn print_points_as_svg_path(
); );
} }
if points.len() > 0 { if !points.is_empty() {
println!( println!(
"M{},{}", "M{},{}",
points[0].x.as_f32() as i32, points[0].x.as_f32() as i32,
@@ -1353,7 +1348,7 @@ impl Element for TextElement {
let line_height = window.line_height(); let line_height = window.line_height();
let (visible_range, visible_buffer_lines, visible_top) = let (visible_range, visible_buffer_lines, visible_top) =
self.calculate_visible_range(&state, line_height, bounds.size.height); self.calculate_visible_range(state, line_height, bounds.size.height);
let visible_start_offset = state.text.line_start_offset(visible_range.start); let visible_start_offset = state.text.line_start_offset(visible_range.start);
let visible_end_offset = state let visible_end_offset = state
.text .text
@@ -1387,7 +1382,7 @@ impl Element for TextElement {
// Calculate the width of the line numbers // Calculate the width of the line numbers
let (line_number_width, line_number_len) = let (line_number_width, line_number_len) =
Self::layout_line_numbers(&state, &text, text_size, &text_style, window); Self::layout_line_numbers(state, &text, text_size, &text_style, window);
let mut bounds = bounds; let mut bounds = bounds;
let wrap_width = if multi_line && state.soft_wrap { let wrap_width = if multi_line && state.soft_wrap {
@@ -1460,14 +1455,14 @@ impl Element for TextElement {
runs.extend(highlight_styles.iter().map(|(range, style)| { runs.extend(highlight_styles.iter().map(|(range, style)| {
let mut run = text_style.clone().highlight(*style).to_run(range.len()); let mut run = text_style.clone().highlight(*style).to_run(range.len());
if let Some(ime_marked_range) = &state.ime_marked_range {
if range.start >= ime_marked_range.start if let Some(ime_marked_range) = &state.ime_marked_range
&& range.end <= ime_marked_range.end && range.start >= ime_marked_range.start
{ && range.end <= ime_marked_range.end
run.color = marked_run.color; {
run.strikethrough = marked_run.strikethrough; run.color = marked_run.color;
run.underline = marked_run.underline; run.strikethrough = marked_run.strikethrough;
} run.underline = marked_run.underline;
} }
run run
@@ -1505,11 +1500,11 @@ impl Element for TextElement {
// Create shaped lines for whitespace indicators before layout // Create shaped lines for whitespace indicators before layout
let whitespace_indicators = let whitespace_indicators =
Self::layout_whitespace_indicators(&state, text_size, &text_style, window, cx); Self::layout_whitespace_indicators(state, text_size, &text_style, window, cx);
let lines = Self::layout_lines( let lines = Self::layout_lines(
&state, state,
&display_text, display_text,
&last_layout, &last_layout,
text_size, text_size,
&runs, &runs,
@@ -1621,9 +1616,9 @@ impl Element for TextElement {
self.layout_cursor(&last_layout, &mut bounds, scroll_size, window, cx); self.layout_cursor(&last_layout, &mut bounds, scroll_size, window, cx);
last_layout.cursor_bounds = cursor_bounds; last_layout.cursor_bounds = cursor_bounds;
let search_match_paths = self.layout_search_matches(&last_layout, &mut bounds, cx); let search_match_paths = self.layout_search_matches(&last_layout, &bounds, cx);
let selection_path = self.layout_selections(&last_layout, &mut bounds, window, cx); let selection_path = self.layout_selections(&last_layout, &mut bounds, window, cx);
let hover_highlight_path = self.layout_hover_highlight(&last_layout, &mut bounds, cx); let hover_highlight_path = self.layout_hover_highlight(&last_layout, &bounds, cx);
let document_color_paths = let document_color_paths =
self.layout_document_colors(&document_colors, &last_layout, &bounds, cx); self.layout_document_colors(&document_colors, &last_layout, &bounds, cx);
@@ -1666,7 +1661,7 @@ impl Element for TextElement {
sub_lines.push( sub_lines.push(
window window
.text_system() .text_system()
.shape_line(line_no, text_size, &runs, None), .shape_line(line_no, text_size, runs, None),
); );
for _ in 0..line.wrapped_lines.len().saturating_sub(1) { for _ in 0..line.wrapped_lines.len().saturating_sub(1) {
sub_lines.push(ShapedLine::default()); sub_lines.push(ShapedLine::default());
@@ -1847,7 +1842,7 @@ impl Element for TextElement {
); );
// Paint the actual line // Paint the actual line
_ = line.paint( line.paint(
p, p,
line_height, line_height,
text_align, text_align,
@@ -1893,10 +1888,11 @@ impl Element for TextElement {
} }
// Paint blinking cursor // Paint blinking cursor
if focused && show_cursor { if focused
if let Some(cursor_bounds) = prepaint.cursor_bounds_with_scroll() { && show_cursor
window.paint_quad(fill(cursor_bounds, cx.theme().cursor)); && let Some(cursor_bounds) = prepaint.cursor_bounds_with_scroll()
} {
window.paint_quad(fill(cursor_bounds, cx.theme().cursor));
} }
// Paint line numbers // Paint line numbers
@@ -1956,26 +1952,24 @@ impl Element for TextElement {
}); });
if let Some(hitbox) = prepaint.hover_definition_hitbox.as_ref() { if let Some(hitbox) = prepaint.hover_definition_hitbox.as_ref() {
window.set_cursor_style(gpui::CursorStyle::PointingHand, &hitbox); window.set_cursor_style(gpui::CursorStyle::PointingHand, hitbox);
} }
// Paint inline completion first line suffix (after cursor on same line) // Paint inline completion first line suffix (after cursor on same line)
if focused { if focused
if let Some(first_line) = &prepaint.ghost_first_line { && let Some(first_line) = &prepaint.ghost_first_line
if let (Some(cursor_bounds), Some(cursor_row_y)) = && let (Some(cursor_bounds), Some(cursor_row_y)) =
(prepaint.cursor_bounds_with_scroll(), cursor_row_y) (prepaint.cursor_bounds_with_scroll(), cursor_row_y)
{ {
let first_line_x = cursor_bounds.origin.x + cursor_bounds.size.width; let first_line_x = cursor_bounds.origin.x + cursor_bounds.size.width;
let p = point(first_line_x, cursor_row_y); let p = point(first_line_x, cursor_row_y);
// Paint background to cover any existing text // Paint background to cover any existing text
let bg_bounds = Bounds::new(p, size(first_line.width + px(4.), line_height)); let bg_bounds = Bounds::new(p, size(first_line.width + px(4.), line_height));
window.paint_quad(fill(bg_bounds, cx.theme().surface_background)); window.paint_quad(fill(bg_bounds, cx.theme().surface_background));
// Paint first line completion text // Paint first line completion text
_ = first_line.paint(p, line_height, text_align, None, window, cx); _ = first_line.paint(p, line_height, text_align, None, window, cx);
}
}
} }
self.paint_mouse_listeners(window, cx); self.paint_mouse_listeners(window, cx);

View File

@@ -26,7 +26,7 @@ impl Default for TabSize {
} }
impl TabSize { impl TabSize {
pub(super) fn to_string(&self) -> SharedString { pub(super) fn to_string(self) -> SharedString {
if self.hard_tabs { if self.hard_tabs {
"\t".into() "\t".into()
} else { } else {
@@ -146,7 +146,7 @@ impl TextElement {
builder.line_to(point(pos.x, pos.y + line_height)); builder.line_to(point(pos.x, pos.y + line_height));
current_indents.push(pos.x); current_indents.push(pos.x);
} }
} else if last_indents.len() > 0 { } else if !last_indents.is_empty() {
for x in &last_indents { for x in &last_indents {
let pos = point(*x, offset_y); let pos = point(*x, offset_y);
builder.move_to(pos); builder.move_to(pos);

View File

@@ -1,10 +1,8 @@
use std::rc::Rc;
use gpui::prelude::FluentBuilder as _; use gpui::prelude::FluentBuilder as _;
use gpui::{ use gpui::{
AnyElement, App, Context, DefiniteLength, Edges, EdgesRefinement, Entity, Hsla, AnyElement, App, DefiniteLength, Edges, EdgesRefinement, Entity, Hsla, InteractiveElement as _,
InteractiveElement as _, IntoElement, MouseButton, ParentElement as _, Rems, RenderOnce, IntoElement, MouseButton, ParentElement as _, Rems, RenderOnce, StyleRefinement, Styled,
StyleRefinement, Styled, TextAlign, Window, div, px, relative, TextAlign, Window, div, px, relative,
}; };
use theme::ActiveTheme; use theme::ActiveTheme;
@@ -13,7 +11,6 @@ use super::element::EditorScrollbar;
use crate::button::{Button, ButtonVariants as _}; use crate::button::{Button, ButtonVariants as _};
use crate::indicator::Indicator; use crate::indicator::Indicator;
use crate::input::clear_button; use crate::input::clear_button;
use crate::menu::PopupMenu;
use crate::{IconName, Selectable, Sizable, Size, StyleSized, StyledExt, h_flex, v_flex}; use crate::{IconName, Selectable, Sizable, Size, StyleSized, StyledExt, h_flex, v_flex};
/// Returns `(background, foreground)` colors for input-like components. /// Returns `(background, foreground)` colors for input-like components.
@@ -42,12 +39,6 @@ pub struct Input {
focus_bordered: bool, focus_bordered: bool,
tab_index: isize, tab_index: isize,
selected: bool, selected: bool,
/// An optional context menu builder to allow a custom context menu on the input.
///
/// If set, this will override the built-in context menu.
context_menu_builder:
Option<Rc<dyn Fn(PopupMenu, &mut Window, &mut Context<PopupMenu>) -> PopupMenu>>,
} }
impl Sizable for Input { impl Sizable for Input {
@@ -86,7 +77,6 @@ impl Input {
focus_bordered: true, focus_bordered: true,
tab_index: 0, tab_index: 0,
selected: false, selected: false,
context_menu_builder: None,
} }
} }
@@ -154,15 +144,6 @@ impl Input {
self self
} }
/// Sets the context menu for the input.
pub fn context_menu(
mut self,
f: impl Fn(PopupMenu, &mut Window, &mut Context<PopupMenu>) -> PopupMenu + 'static,
) -> Self {
self.context_menu_builder = Some(Rc::new(f));
self
}
fn render_toggle_mask_button(state: &Entity<InputState>, cx: &App) -> impl IntoElement { fn render_toggle_mask_button(state: &Entity<InputState>, cx: &App) -> impl IntoElement {
let _masked = state.read(cx).masked; let _masked = state.read(cx).masked;
Button::new("toggle-mask") Button::new("toggle-mask")
@@ -234,10 +215,8 @@ impl RenderOnce for Input {
let text_align = self.style.text.text_align.unwrap_or(TextAlign::Left); let text_align = self.style.text.text_align.unwrap_or(TextAlign::Left);
self.state.update(cx, |state, _| { self.state.update(cx, |state, _| {
state.context_menu_builder = self.context_menu_builder.clone();
state.disabled = self.disabled; state.disabled = self.disabled;
state.size = self.size; state.size = self.size;
// Only for single line mode // Only for single line mode
if state.mode.is_single_line() { if state.mode.is_single_line() {
state.text_align = text_align; state.text_align = text_align;
@@ -342,7 +321,6 @@ impl RenderOnce for Input {
MouseButton::Right, MouseButton::Right,
window.listener_for(&self.state, InputState::on_mouse_up), window.listener_for(&self.state, InputState::on_mouse_up),
) )
.on_mouse_move(window.listener_for(&self.state, InputState::on_mouse_move))
.on_scroll_wheel(window.listener_for(&self.state, InputState::on_scroll_wheel)) .on_scroll_wheel(window.listener_for(&self.state, InputState::on_scroll_wheel))
.size_full() .size_full()
.line_height(LINE_HEIGHT) .line_height(LINE_HEIGHT)
@@ -372,7 +350,7 @@ impl RenderOnce for Input {
.children(prefix) .children(prefix)
.when(state.mode.is_multi_line(), |mut this| { .when(state.mode.is_multi_line(), |mut this| {
let paddings = this.style().padding.clone(); let paddings = this.style().padding.clone();
this.child(Self::render_editor(paddings, &self.state, &state, window)) this.child(Self::render_editor(paddings, &self.state, state, window))
}) })
.when(!state.mode.is_multi_line(), |this| { .when(!state.mode.is_multi_line(), |this| {
this.child(self.state.clone()) this.child(self.state.clone())

View File

@@ -225,13 +225,12 @@ impl MaskPattern {
} }
// check if the fraction part is valid // check if the fraction part is valid
if let Some(frac) = frac_part { if let Some(frac) = frac_part
if !frac && !frac
.chars() .chars()
.all(|ch| ch.is_ascii_digit() || Some(ch) == *separator) .all(|ch| ch.is_ascii_digit() || Some(ch) == *separator)
{ {
return false; return false;
}
} }
true true
@@ -255,10 +254,10 @@ impl MaskPattern {
if token.is_sep() { if token.is_sep() {
// If next token is match, it's valid // If next token is match, it's valid
if let Some(next_token) = tokens.get(pos + 1) { if let Some(next_token) = tokens.get(pos + 1)
if next_token.is_match(ch) { && next_token.is_match(ch)
return true; {
} return true;
} }
} }
} }
@@ -305,11 +304,7 @@ impl MaskPattern {
let mut chars: Vec<char> = int_part.chars().rev().collect(); let mut chars: Vec<char> = int_part.chars().rev().collect();
// Removing the sign from formatting to avoid cases such as: -,123 // Removing the sign from formatting to avoid cases such as: -,123
let maybe_signed = if let Some(pos) = chars.iter().position(is_sign) { let maybe_signed = chars.iter().position(is_sign).map(|pos| chars.remove(pos));
Some(chars.remove(pos))
} else {
None
};
let mut result = String::new(); let mut result = String::new();
for (i, ch) in chars.iter().enumerate() { for (i, ch) in chars.iter().enumerate() {
@@ -386,7 +381,7 @@ impl MaskPattern {
return result; return result;
} }
return mask_text.to_owned(); mask_text.to_owned()
} }
Self::Pattern { tokens, .. } => { Self::Pattern { tokens, .. } => {
let mut result = String::new(); let mut result = String::new();
@@ -412,226 +407,3 @@ impl MaskPattern {
fn is_sign(ch: &char) -> bool { fn is_sign(ch: &char) -> bool {
matches!(ch, '+' | '-') matches!(ch, '+' | '-')
} }
#[cfg(test)]
mod tests {
use crate::input::mask_pattern::{MaskPattern, MaskToken};
#[test]
fn test_is_match() {
assert_eq!(MaskToken::Sep('(').is_match('('), true);
assert_eq!(MaskToken::Sep('-').is_match('('), false);
assert_eq!(MaskToken::Sep('-').is_match('3'), false);
assert_eq!(MaskToken::Digit.is_match('0'), true);
assert_eq!(MaskToken::Digit.is_match('9'), true);
assert_eq!(MaskToken::Digit.is_match('a'), false);
assert_eq!(MaskToken::Digit.is_match('C'), false);
assert_eq!(MaskToken::Letter.is_match('a'), true);
assert_eq!(MaskToken::Letter.is_match('Z'), true);
assert_eq!(MaskToken::Letter.is_match('3'), false);
assert_eq!(MaskToken::Letter.is_match('-'), false);
assert_eq!(MaskToken::LetterOrDigit.is_match('0'), true);
assert_eq!(MaskToken::LetterOrDigit.is_match('9'), true);
assert_eq!(MaskToken::LetterOrDigit.is_match('a'), true);
assert_eq!(MaskToken::LetterOrDigit.is_match('Z'), true);
assert_eq!(MaskToken::LetterOrDigit.is_match('3'), true);
assert_eq!(MaskToken::Any.is_match('a'), true);
assert_eq!(MaskToken::Any.is_match('3'), true);
assert_eq!(MaskToken::Any.is_match('-'), true);
assert_eq!(MaskToken::Any.is_match(' '), true);
}
#[test]
fn test_mask_none() {
let mask = MaskPattern::None;
assert_eq!(mask.is_none(), true);
assert_eq!(mask.is_valid("1124124ASLDJKljk"), true);
assert_eq!(mask.mask("hello-world"), "hello-world");
assert_eq!(mask.unmask("hello-world"), "hello-world");
}
#[test]
fn test_mask_pattern1() {
let mask = MaskPattern::new("(AA)999-999");
assert_eq!(
mask.tokens(),
Some(&vec![
MaskToken::Sep('('),
MaskToken::Letter,
MaskToken::Letter,
MaskToken::Sep(')'),
MaskToken::Digit,
MaskToken::Digit,
MaskToken::Digit,
MaskToken::Sep('-'),
MaskToken::Digit,
MaskToken::Digit,
MaskToken::Digit,
])
);
assert_eq!(mask.is_valid_at('(', 0), true);
assert_eq!(mask.is_valid_at('H', 0), true);
assert_eq!(mask.is_valid_at('3', 0), false);
assert_eq!(mask.is_valid_at('-', 0), false);
assert_eq!(mask.is_valid_at(')', 1), false);
assert_eq!(mask.is_valid_at('H', 1), true);
assert_eq!(mask.is_valid_at('1', 1), false);
assert_eq!(mask.is_valid_at('e', 2), true);
assert_eq!(mask.is_valid_at(')', 3), true);
assert_eq!(mask.is_valid_at('1', 3), true);
assert_eq!(mask.is_valid_at('2', 4), true);
assert_eq!(mask.is_valid("(AB)123-456"), true);
assert_eq!(mask.mask("AB123456"), "(AB)123-456");
assert_eq!(mask.mask("(AB)123-456"), "(AB)123-456");
assert_eq!(mask.mask("(AB123456"), "(AB)123-456");
assert_eq!(mask.mask("AB123-456"), "(AB)123-456");
assert_eq!(mask.mask("AB123-"), "(AB)123-");
assert_eq!(mask.mask("AB123--"), "(AB)123-");
assert_eq!(mask.mask("AB123-4"), "(AB)123-4");
let unmasked_text = mask.unmask("(AB)123-456");
assert_eq!(unmasked_text, "AB123456");
assert_eq!(mask.is_valid("12AB345"), false);
assert_eq!(mask.is_valid("(11)123-456"), false);
assert_eq!(mask.is_valid("##"), false);
assert_eq!(mask.is_valid("(AB)123456"), true);
}
#[test]
fn test_mask_pattern2() {
let mask = MaskPattern::new("999-999-******");
assert_eq!(
mask.tokens(),
Some(&vec![
MaskToken::Digit,
MaskToken::Digit,
MaskToken::Digit,
MaskToken::Sep('-'),
MaskToken::Digit,
MaskToken::Digit,
MaskToken::Digit,
MaskToken::Sep('-'),
MaskToken::Any,
MaskToken::Any,
MaskToken::Any,
MaskToken::Any,
MaskToken::Any,
MaskToken::Any,
])
);
let text = "123456A(111)";
let masked_text = mask.mask(text);
assert_eq!(masked_text, "123-456-A(111)");
let unmasked_text = mask.unmask(&masked_text);
assert_eq!(unmasked_text, "123456A(111)");
assert_eq!(mask.is_valid(&masked_text), true);
}
#[test]
fn test_number_with_group_separator() {
// Use comma as group separator
let mask = MaskPattern::number(Some(','));
assert_eq!(mask.mask("1234567"), "1,234,567");
assert_eq!(mask.mask("1,234,567"), "1,234,567");
assert_eq!(mask.unmask("1,234,567"), "1234567");
let mask = MaskPattern::number(Some(','));
assert_eq!(mask.mask("1234567.89"), "1,234,567.89");
assert_eq!(mask.unmask("1,234,567.89"), "1234567.89");
// Use space as group separator
let mask = MaskPattern::number(Some(' '));
assert_eq!(mask.mask("1234567"), "1 234 567");
assert_eq!(mask.unmask("1 234 567"), "1234567");
let mask = MaskPattern::number(Some(' '));
assert_eq!(mask.mask("1234567.89"), "1 234 567.89");
assert_eq!(mask.unmask("1 234 567.89"), "1234567.89");
// No group separator
let mask = MaskPattern::number(None);
assert_eq!(mask.mask("1234567"), "1234567");
assert_eq!(mask.unmask("1234567"), "1234567");
let mask = MaskPattern::number(None);
assert_eq!(mask.mask("1234567.89"), "1234567.89");
assert_eq!(mask.unmask("1234567.89"), "1234567.89");
}
#[test]
fn test_number_with_fraction_digits() {
let mask = MaskPattern::Number {
separator: Some(','),
fraction: Some(4),
};
assert_eq!(mask.mask("1234567"), "1,234,567");
assert_eq!(mask.unmask("1,234,567"), "1234567");
assert_eq!(mask.mask("1234567."), "1,234,567.");
assert_eq!(mask.mask("1234567.89"), "1,234,567.89");
assert_eq!(mask.unmask("1,234,567.890"), "1234567.89");
assert_eq!(mask.mask("1234567.891"), "1,234,567.891");
assert_eq!(mask.mask("1234567.891234"), "1,234,567.8912");
let mask = MaskPattern::Number {
separator: Some(','),
fraction: None,
};
assert_eq!(mask.mask("1234567.1234567"), "1,234,567.1234567");
let mask = MaskPattern::Number {
separator: Some(','),
fraction: Some(0),
};
assert_eq!(mask.mask("1234567.1234567"), "1,234,567");
}
#[test]
fn test_signed_number_numbers() {
let mask = MaskPattern::Number {
separator: Some(','),
fraction: Some(2),
};
assert_eq!(mask.is_valid("-"), true);
assert_eq!(mask.is_valid("-1234567"), true);
assert_eq!(mask.is_valid("-1,234,567"), true);
assert_eq!(mask.is_valid("-1234567."), true);
assert_eq!(mask.is_valid("-1234567.89"), true);
assert_eq!(mask.is_valid("+"), true);
assert_eq!(mask.is_valid("+1234567"), true);
assert_eq!(mask.is_valid("+1,234,567"), true);
assert_eq!(mask.is_valid("+1234567."), true);
assert_eq!(mask.is_valid("+1234567.89"), true);
// Only one sign is valid
assert_eq!(mask.is_valid("+-"), false);
assert_eq!(mask.is_valid("-+"), false);
assert_eq!(mask.is_valid("+-1234567"), false);
// No sign is valid in the middle of the number
assert_eq!(mask.is_valid("1,-234,567"), false);
assert_eq!(mask.is_valid("12-34567.89"), false);
// Signs in fractions are invalid
assert_eq!(mask.is_valid("+1234567.-"), false);
// The separator does not show up before the sign i.e. -,123
assert_eq!(mask.mask("-123"), "-123");
assert_eq!(mask.mask("-1234567"), "-1,234,567");
assert_eq!(mask.mask("+1234567"), "+1,234,567");
assert_eq!(mask.unmask("-1,234,567"), "-1234567");
assert_eq!(mask.mask("-1234567."), "-1,234,567.");
assert_eq!(mask.mask("-1234567.89"), "-1,234,567.89");
}
}

View File

@@ -7,6 +7,7 @@ mod cursor;
mod display_map; mod display_map;
mod element; mod element;
mod indent; mod indent;
#[allow(clippy::module_inception)]
mod input; mod input;
mod mask_pattern; mod mask_pattern;
mod mode; mod mode;

View File

@@ -152,7 +152,7 @@ impl InputState {
} }
} }
pub(super) fn up(&mut self, action: &MoveUp, window: &mut Window, cx: &mut Context<Self>) { pub(super) fn up(&mut self, _action: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
if self.mode.is_single_line() { if self.mode.is_single_line() {
return; return;
} }
@@ -168,7 +168,7 @@ impl InputState {
self.move_vertical(-1, window, cx); self.move_vertical(-1, window, cx);
} }
pub(super) fn down(&mut self, action: &MoveDown, window: &mut Window, cx: &mut Context<Self>) { pub(super) fn down(&mut self, _action: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
if self.mode.is_single_line() { if self.mode.is_single_line() {
return; return;
} }

View File

@@ -304,7 +304,7 @@ impl RopeExt for Rope {
} }
fn iter_lines(&self) -> RopeLines<'_> { fn iter_lines(&self) -> RopeLines<'_> {
RopeLines::new(&self) RopeLines::new(self)
} }
fn line_len(&self, row: usize) -> usize { fn line_len(&self, row: usize) -> usize {

View File

@@ -54,13 +54,11 @@ impl TextSelector {
/// Returns the start and end offsets of the selected word. /// Returns the start and end offsets of the selected word.
pub fn word_range(text: &Rope, offset: usize) -> Option<Range<usize>> { pub fn word_range(text: &Rope, offset: usize) -> Option<Range<usize>> {
let offset = text.clip_offset(offset, Bias::Left); let offset = text.clip_offset(offset, Bias::Left);
let Some(char) = text.char_at(offset) else { let char = text.char_at(offset)?;
return None;
};
let end = offset + char.len_utf8(); let end = offset + char.len_utf8();
let prev_chars = text.chars_at(offset).reversed().take(128); let prev_chars = text.chars_at(offset).reversed().take(128);
let next_chars = text.chars_at(end).take(128); let next_chars = text.chars_at(end).take(128);
Some(word_range_from_chars(offset, char, prev_chars, next_chars)) Some(word_range_from_chars(offset, char, prev_chars, next_chars))
} }
} }

View File

@@ -6,14 +6,13 @@ use std::cell::Cell;
use std::ops::Range; use std::ops::Range;
use std::rc::Rc; use std::rc::Rc;
use anyhow::Result;
use gpui::prelude::FluentBuilder as _; use gpui::prelude::FluentBuilder as _;
use gpui::{ use gpui::{
Action, App, AppContext, Bounds, ClipboardItem, Context, Edges, Entity, EntityInputHandler, Action, App, AppContext, Bounds, ClipboardItem, Context, Edges, Entity, EntityInputHandler,
EventEmitter, FocusHandle, Focusable, Half, InteractiveElement as _, IntoElement, KeyBinding, EventEmitter, FocusHandle, Focusable, Half, InteractiveElement as _, IntoElement, KeyBinding,
KeyDownEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement as _, KeyDownEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement as _,
Pixels, Point, Render, ScrollHandle, ScrollWheelEvent, ShapedLine, SharedString, Styled as _, Pixels, Point, Render, ScrollHandle, ScrollWheelEvent, ShapedLine, SharedString, Styled as _,
Subscription, Task, TextAlign, UTF16Selection, Window, actions, div, point, px, Subscription, TextAlign, UTF16Selection, Window, actions, div, point, px,
}; };
use ropey::{Rope, RopeSlice}; use ropey::{Rope, RopeSlice};
use serde::Deserialize; use serde::Deserialize;
@@ -34,7 +33,6 @@ use crate::input::element::RIGHT_MARGIN;
use crate::input::movement::MoveDirection; use crate::input::movement::MoveDirection;
use crate::input::rope_ext::Position; use crate::input::rope_ext::Position;
use crate::input::{RopeExt as _, Selection}; use crate::input::{RopeExt as _, Selection};
use crate::menu::PopupMenu;
use crate::{Root, Size}; use crate::{Root, Size};
#[derive(Action, Clone, PartialEq, Eq, Deserialize)] #[derive(Action, Clone, PartialEq, Eq, Deserialize)]
@@ -323,6 +321,7 @@ impl LastLayout {
} }
/// InputState to keep editing state of the [`super::Input`]. /// InputState to keep editing state of the [`super::Input`].
#[allow(clippy::type_complexity)]
pub struct InputState { pub struct InputState {
pub(super) focus_handle: FocusHandle, pub(super) focus_handle: FocusHandle,
pub(super) mode: InputMode, pub(super) mode: InputMode,
@@ -374,24 +373,6 @@ pub struct InputState {
pub(crate) mask_pattern: MaskPattern, pub(crate) mask_pattern: MaskPattern,
pub(super) placeholder: SharedString, pub(super) placeholder: SharedString,
/// An optional context menu builder to allow a custom context menu on the input.
///
/// If set, this will override the built-in context menu and ignore the value set in [`Self::enable_context_menu`].
pub(super) context_menu_builder:
Option<Rc<dyn Fn(PopupMenu, &mut Window, &mut Context<PopupMenu>) -> PopupMenu>>,
/// Whether the context menu that shows on right-click is enabled.
///
/// This value will be ignored if a context menu builder is defined in [`Self::context_menu_builder`].
pub(super) enable_context_menu: bool,
/// A flag to indicate if we are currently inserting a completion item.
pub(super) completion_inserting: bool,
/// A flag to indicate if we have a pending update to the text.
///
/// If true, will call some update (for example LSP, Syntax Highlight) before render.
_pending_update: bool,
/// A flag to indicate if we should ignore the next completion event. /// A flag to indicate if we should ignore the next completion event.
pub(super) silent_replace_text: bool, pub(super) silent_replace_text: bool,
/// A flag to indicate if we should emit InputEvents. /// A flag to indicate if we should emit InputEvents.
@@ -403,8 +384,6 @@ pub struct InputState {
/// The second element is the column (usize), fallback to use this. /// The second element is the column (usize), fallback to use this.
pub(super) preferred_column: Option<(Pixels, usize)>, pub(super) preferred_column: Option<(Pixels, usize)>,
_subscriptions: Vec<Subscription>, _subscriptions: Vec<Subscription>,
pub(super) _context_menu_task: Task<Result<()>>,
} }
impl EventEmitter<InputEvent> for InputState {} impl EventEmitter<InputEvent> for InputState {}
@@ -479,15 +458,10 @@ impl InputState {
placeholder: SharedString::default(), placeholder: SharedString::default(),
mask_pattern: MaskPattern::default(), mask_pattern: MaskPattern::default(),
text_align: TextAlign::Left, text_align: TextAlign::Left,
context_menu_builder: None,
enable_context_menu: true,
completion_inserting: false,
silent_replace_text: false, silent_replace_text: false,
emit_events: true, emit_events: true,
size: Size::default(), size: Size::default(),
_subscriptions, _subscriptions,
_context_menu_task: Task::ready(Ok(())),
_pending_update: false,
cursor_line_end_affinity: false, cursor_line_end_affinity: false,
} }
} }
@@ -533,15 +507,6 @@ impl InputState {
self self
} }
/// Sets whether the context menu that shows on right-click is enabled.
///
/// The context menu is enabled by default.
/// This value will be ignored if a custom context menu is defined on the input.
pub fn context_menu(mut self, enable: bool) -> Self {
self.enable_context_menu = enable;
self
}
/// Set whether search UI allows replacement, default is true. /// Set whether search UI allows replacement, default is true.
pub fn replaceable(mut self, allow: bool) -> Self { pub fn replaceable(mut self, allow: bool) -> Self {
self.replaceable = allow; self.replaceable = allow;
@@ -625,26 +590,21 @@ impl InputState {
new_language: impl Into<SharedString>, new_language: impl Into<SharedString>,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) {
match &mut self.mode { if let InputMode::CodeEditor {
InputMode::CodeEditor { language,
language, parse_task,
parse_task, ..
.. } = &mut self.mode
} => { {
*language = new_language.into(); *language = new_language.into();
parse_task.borrow_mut().take(); parse_task.borrow_mut().take();
}
_ => {}
} }
cx.notify(); cx.notify();
} }
fn reset_highlighter(&mut self, cx: &mut Context<Self>) { fn reset_highlighter(&mut self, cx: &mut Context<Self>) {
match &mut self.mode { if let InputMode::CodeEditor { parse_task, .. } = &mut self.mode {
InputMode::CodeEditor { parse_task, .. } => { parse_task.borrow_mut().take();
parse_task.borrow_mut().take();
}
_ => {}
} }
cx.notify(); cx.notify();
} }
@@ -713,10 +673,6 @@ impl InputState {
self.selected_range.clear(); self.selected_range.clear();
} }
if self.mode.is_code_editor() {
self._pending_update = true;
}
// Move scroll to top // Move scroll to top
self.scroll_handle.set_offset(point(px(0.), px(0.))); self.scroll_handle.set_offset(point(px(0.), px(0.)));
@@ -902,9 +858,6 @@ impl InputState {
pub fn default_value(mut self, value: impl Into<SharedString>) -> Self { pub fn default_value(mut self, value: impl Into<SharedString>) -> Self {
let text: SharedString = value.into(); let text: SharedString = value.into();
self.text = Rope::from(text.as_str()); self.text = Rope::from(text.as_str());
// Note: We can't call display_map.set_text here because it needs cx.
// The text will be set during prepare_if_need in element.rs
self._pending_update = true;
self self
} }
@@ -1146,13 +1099,11 @@ impl InputState {
offset += 1; offset += 1;
} }
let line = self self.text_for_range(self.range_to_utf16(&(0..offset + 1)), &mut None, window, cx)
.text_for_range(self.range_to_utf16(&(0..offset + 1)), &mut None, window, cx)
.unwrap_or_default() .unwrap_or_default()
.rfind('\n') .rfind('\n')
.map(|i| i + 1) .map(|i| i + 1)
.unwrap_or(0); .unwrap_or(0)
line
} }
/// Get indent string of next line. /// Get indent string of next line.
@@ -1188,9 +1139,9 @@ impl InputState {
} }
if next_indent.len() > current_indent.len() { if next_indent.len() > current_indent.len() {
return next_indent; next_indent
} else { } else {
return current_indent; current_indent
} }
} }
@@ -1361,10 +1312,10 @@ impl InputState {
) { ) {
// If there have IME marked range and is empty (Means pressed Esc to abort IME typing) // If there have IME marked range and is empty (Means pressed Esc to abort IME typing)
// Clear the marked range. // Clear the marked range.
if let Some(ime_marked_range) = &self.ime_marked_range { if let Some(ime_marked_range) = &self.ime_marked_range
if ime_marked_range.len() == 0 { && ime_marked_range.is_empty()
self.ime_marked_range = None; {
} self.ime_marked_range = None;
} }
self.selecting = true; self.selecting = true;
@@ -1402,25 +1353,6 @@ impl InputState {
self.selected_word_range = None; self.selected_word_range = None;
} }
pub(super) fn on_mouse_move(
&mut self,
event: &MouseMoveEvent,
_window: &mut Window,
_cx: &mut Context<Self>,
) {
// Check if mouse is within bounds
let within_bounds = self
.last_bounds
.as_ref()
.map(|bounds| bounds.contains(&event.position))
.unwrap_or(false);
if !within_bounds {
// Clear hover when mouse leaves the input
return;
}
}
pub(super) fn on_scroll_wheel( pub(super) fn on_scroll_wheel(
&mut self, &mut self,
event: &ScrollWheelEvent, event: &ScrollWheelEvent,
@@ -2041,22 +1973,6 @@ impl InputState {
self.replace_text_in_range(range_utf16, new_text, window, cx); self.replace_text_in_range(range_utf16, new_text, window, cx);
self.silent_replace_text = false; self.silent_replace_text = false;
} }
/// Update fold candidates from tree-sitter syntax tree (full extraction).
/// Used only on initial load or language changes.
fn update_fold_candidates(&mut self) {
if !self.mode.is_folding() {
return;
}
}
/// Incrementally update fold candidates after a text edit.
/// Only traverses the edited region of the syntax tree instead of the full tree.
fn update_fold_candidates_incremental(&mut self, _edit_range: &Range<usize>, _new_text: &str) {
if !self.mode.is_folding() {
return;
}
}
} }
impl EntityInputHandler for InputState { impl EntityInputHandler for InputState {
@@ -2105,7 +2021,7 @@ impl EntityInputHandler for InputState {
&mut self, &mut self,
range_utf16: Option<Range<usize>>, range_utf16: Option<Range<usize>>,
new_text: &str, new_text: &str,
window: &mut Window, _window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) {
if self.disabled { if self.disabled {
@@ -2147,7 +2063,7 @@ impl EntityInputHandler for InputState {
} }
} }
self.push_history(&old_text, &range, &new_text); self.push_history(&old_text, &range, new_text);
self.history.end_grouping(); self.history.end_grouping();
// Adjust folds before updating wrap map: remove overlapping folds and shift others // Adjust folds before updating wrap map: remove overlapping folds and shift others
@@ -2156,7 +2072,6 @@ impl EntityInputHandler for InputState {
self.display_map self.display_map
.on_text_changed(&self.text, &range, &Rope::from(new_text), cx); .on_text_changed(&self.text, &range, &Rope::from(new_text), cx);
self.update_fold_candidates_incremental(&range, new_text);
self.selected_range = (new_offset..new_offset).into(); self.selected_range = (new_offset..new_offset).into();
self.ime_marked_range.take(); self.ime_marked_range.take();
self.update_preferred_column(); self.update_preferred_column();
@@ -2206,8 +2121,6 @@ impl EntityInputHandler for InputState {
self.display_map self.display_map
.on_text_changed(&self.text, &range, &Rope::from(new_text), cx); .on_text_changed(&self.text, &range, &Rope::from(new_text), cx);
self.update_fold_candidates_incremental(&range, new_text);
if new_text.is_empty() { if new_text.is_empty() {
// Cancel selection, when cancel IME input. // Cancel selection, when cancel IME input.
self.selected_range = (range.start..range.start).into(); self.selected_range = (range.start..range.start).into();
@@ -2253,24 +2166,24 @@ impl EntityInputHandler for InputState {
let index_offset = last_layout.visible_line_byte_offsets[vi]; let index_offset = last_layout.visible_line_byte_offsets[vi];
if start_origin.is_none() { if start_origin.is_none()
if let Some(p) = line.position_for_index( && let Some(p) = line.position_for_index(
range.start.saturating_sub(index_offset), range.start.saturating_sub(index_offset),
last_layout, last_layout,
false, false,
) { )
start_origin = Some(p + point(px(0.), y_offset)); {
} start_origin = Some(p + point(px(0.), y_offset));
} }
if end_origin.is_none() { if end_origin.is_none()
if let Some(p) = line.position_for_index( && let Some(p) = line.position_for_index(
range.end.saturating_sub(index_offset), range.end.saturating_sub(index_offset),
last_layout, last_layout,
false, false,
) { )
end_origin = Some(p + point(px(0.), y_offset)); {
} end_origin = Some(p + point(px(0.), y_offset));
} }
y_offset += line.size(line_height).height; y_offset += line.size(line_height).height;
@@ -2316,11 +2229,6 @@ impl Focusable for InputState {
impl Render for InputState { impl Render for InputState {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement { fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
if self._pending_update {
self.update_fold_candidates();
self._pending_update = false;
}
div() div()
.id("input-state") .id("input-state")
.flex_1() .flex_1()

View File

@@ -288,7 +288,7 @@ where
}); });
}); });
} }
InputEvent::PressEnter { secondary, shift } => self.on_action_confirm( InputEvent::PressEnter { secondary, .. } => self.on_action_confirm(
&Confirm { &Confirm {
secondary: *secondary, secondary: *secondary,
}, },