input: Fix visible range when multi-line text have soft wrap. (#1155)
## Before <img width="1130" height="783" alt="image" src="https://github.com/user-attachments/assets/3e0db5f4-bb84-480f-8b38-dbe24e6c6214" /> ## After https://github.com/user-attachments/assets/fdaed71e-3ab8-4258-a1eb-b731181ca31c
This commit is contained in:
parent
7fc1544a10
commit
fb41e63ea8
5 changed files with 45 additions and 31 deletions
|
|
@ -182,8 +182,8 @@ where
|
|||
window_bounds: Some(WindowBounds::Windowed(window_bounds)),
|
||||
titlebar: Some(TitleBar::title_bar_options()),
|
||||
window_min_size: Some(gpui::Size {
|
||||
width: px(640.),
|
||||
height: px(480.),
|
||||
width: px(480.),
|
||||
height: px(320.),
|
||||
}),
|
||||
kind: WindowKind::Normal,
|
||||
#[cfg(target_os = "linux")]
|
||||
|
|
|
|||
|
|
@ -338,18 +338,23 @@ impl TextElement {
|
|||
return 0..1;
|
||||
}
|
||||
|
||||
let scroll_top = -state.scroll_handle.offset().y;
|
||||
let total_lines = state.text_wrapper.lines.len();
|
||||
let mut visible_range = 0..total_lines;
|
||||
let mut line_top = px(0.);
|
||||
for (ix, line) in state.text_wrapper.lines.iter().enumerate() {
|
||||
line_top += line.height(line_height);
|
||||
let Some(last_layout) = state.last_layout.as_ref() else {
|
||||
return 0..1;
|
||||
};
|
||||
|
||||
if line_top < scroll_top {
|
||||
let scroll_top = state.scroll_handle.offset().y;
|
||||
let total_lines = last_layout.lines.len();
|
||||
|
||||
let mut visible_range = 0..total_lines;
|
||||
let mut line_bottom = px(0.);
|
||||
for (ix, line) in last_layout.lines.iter().enumerate() {
|
||||
line_bottom += (line.wrap_boundaries.len() + 1) * line_height;
|
||||
|
||||
if line_bottom < -scroll_top {
|
||||
visible_range.start = ix;
|
||||
}
|
||||
|
||||
if line_top > scroll_top + input_height {
|
||||
if line_bottom + scroll_top >= input_height {
|
||||
visible_range.end = (ix + 1).min(total_lines);
|
||||
break;
|
||||
}
|
||||
|
|
@ -435,7 +440,6 @@ pub(super) struct PrepaintState {
|
|||
last_layout: LastLayout,
|
||||
/// The lines only contains the visible lines in the viewport, based on `visible_range`.
|
||||
line_numbers: Option<Vec<SmallVec<[WrappedLine; 1]>>>,
|
||||
line_number_width: Pixels,
|
||||
/// Size of the scrollable area by entire lines.
|
||||
scroll_size: Size<Pixels>,
|
||||
cursor_bounds: Option<Bounds<Pixels>>,
|
||||
|
|
@ -657,7 +661,7 @@ impl Element for TextElement {
|
|||
};
|
||||
|
||||
let wrap_width = if multi_line {
|
||||
Some(bounds.size.width - line_number_width - RIGHT_MARGIN)
|
||||
Some(bounds.size.width - line_number_width)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
|
@ -796,10 +800,11 @@ impl Element for TextElement {
|
|||
lines: Rc::new(lines),
|
||||
line_height,
|
||||
visible_range,
|
||||
line_number_width,
|
||||
wrap_width,
|
||||
},
|
||||
scroll_size,
|
||||
line_numbers,
|
||||
line_number_width,
|
||||
cursor_bounds,
|
||||
cursor_scroll_offset,
|
||||
current_line_index,
|
||||
|
|
@ -915,7 +920,10 @@ impl Element for TextElement {
|
|||
.skip(visible_range.start)
|
||||
.take(visible_range.len())
|
||||
{
|
||||
let p = point(origin.x + prepaint.line_number_width, origin.y + offset_y);
|
||||
let p = point(
|
||||
origin.x + prepaint.last_layout.line_number_width,
|
||||
origin.y + offset_y,
|
||||
);
|
||||
_ = line.paint(p, line_height, TextAlign::Left, None, window, cx);
|
||||
offset_y += line.size(line_height).height;
|
||||
}
|
||||
|
|
@ -934,7 +942,6 @@ impl Element for TextElement {
|
|||
state.set_input_bounds(input_bounds, cx);
|
||||
state.last_selected_range = Some(selected_range);
|
||||
state.scroll_size = prepaint.scroll_size;
|
||||
state.line_number_width = prepaint.line_number_width;
|
||||
state
|
||||
.scroll_handle
|
||||
.set_offset(prepaint.cursor_scroll_offset);
|
||||
|
|
|
|||
|
|
@ -34,7 +34,11 @@ impl DiagnosticPopover {
|
|||
let Some(range) = self.marker.range.as_ref() else {
|
||||
return None;
|
||||
};
|
||||
let line_number_width = self.state.read(cx).line_number_width;
|
||||
let Some(last_layout) = self.state.read(cx).last_layout.as_ref() else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let line_number_width = last_layout.line_number_width;
|
||||
|
||||
let (_, _, start_pos) = self
|
||||
.state
|
||||
|
|
|
|||
|
|
@ -222,6 +222,10 @@ pub(super) struct LastLayout {
|
|||
pub(super) line_height: Pixels,
|
||||
/// The visible range (no wrap) of lines in the viewport.
|
||||
pub(super) visible_range: Range<usize>,
|
||||
/// The wrap width of text layout, this will change will InputElement painted.
|
||||
pub(super) wrap_width: Option<Pixels>,
|
||||
/// The line number width of text layout.
|
||||
pub(super) line_number_width: Pixels,
|
||||
}
|
||||
|
||||
impl Deref for LastLayout {
|
||||
|
|
@ -268,7 +272,6 @@ pub struct InputState {
|
|||
pub(super) scroll_state: ScrollbarState,
|
||||
/// The size of the scrollable content.
|
||||
pub(crate) scroll_size: gpui::Size<Pixels>,
|
||||
pub(crate) line_number_width: Pixels,
|
||||
|
||||
/// The mask pattern for formatting the input text
|
||||
pub(crate) mask_pattern: MaskPattern,
|
||||
|
|
@ -344,7 +347,6 @@ impl InputState {
|
|||
scroll_state: ScrollbarState::default(),
|
||||
scroll_size: gpui::size(px(0.), px(0.)),
|
||||
preferred_x_offset: None,
|
||||
line_number_width: px(0.),
|
||||
placeholder: SharedString::default(),
|
||||
mask_pattern: MaskPattern::default(),
|
||||
diagnostic_popover: None,
|
||||
|
|
@ -1749,6 +1751,7 @@ impl InputState {
|
|||
};
|
||||
|
||||
let line_height = last_layout.line_height;
|
||||
let line_number_width = last_layout.line_number_width;
|
||||
|
||||
// TIP: About the IBeam cursor
|
||||
//
|
||||
|
|
@ -1760,7 +1763,7 @@ impl InputState {
|
|||
//
|
||||
// - included the input padding.
|
||||
// - included the scroll offset.
|
||||
let inner_position = position - bounds.origin - point(self.line_number_width, px(0.));
|
||||
let inner_position = position - bounds.origin - point(line_number_width, px(0.));
|
||||
|
||||
let mut index = 0;
|
||||
let mut y_offset = px(0.);
|
||||
|
|
@ -2095,10 +2098,12 @@ impl InputState {
|
|||
self.input_bounds = new_bounds;
|
||||
|
||||
// Update text_wrapper wrap_width if changed.
|
||||
if wrap_width_changed {
|
||||
self.text_wrapper
|
||||
.set_wrap_width(Some(new_bounds.size.width), cx);
|
||||
self.mode.update_auto_grow(&self.text_wrapper);
|
||||
if let Some(last_layout) = self.last_layout.as_ref() {
|
||||
if wrap_width_changed {
|
||||
self.text_wrapper.set_wrap_width(last_layout.wrap_width, cx);
|
||||
self.mode.update_auto_grow(&self.text_wrapper);
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2256,11 +2261,12 @@ impl EntityInputHandler for InputState {
|
|||
) -> Option<Bounds<Pixels>> {
|
||||
let last_layout = self.last_layout.as_ref()?;
|
||||
let line_height = last_layout.line_height;
|
||||
let line_number_width = last_layout.line_number_width;
|
||||
let range = self.range_from_utf16(&range_utf16);
|
||||
|
||||
let mut start_origin = None;
|
||||
let mut end_origin = None;
|
||||
let line_number_origin = point(self.line_number_width, px(0.));
|
||||
let line_number_origin = point(line_number_width, px(0.));
|
||||
let mut y_offset = px(0.);
|
||||
let mut index_offset = 0;
|
||||
|
||||
|
|
|
|||
|
|
@ -6,17 +6,15 @@ use gpui::{App, Font, LineFragment, Pixels, SharedString};
|
|||
#[allow(unused)]
|
||||
pub(super) struct LineWrap {
|
||||
/// The number of soft wrapped lines of this line (Not include first line.)
|
||||
///
|
||||
/// FIXME: Here in somecase, the `line_wrapper.wrap_line` has returned different
|
||||
/// like the `window.text_system().shape_text`. So, this value may not equal
|
||||
/// the actual rendered lines.
|
||||
pub(super) wrap_lines: usize,
|
||||
/// The range of the line text in the entire text.
|
||||
pub(super) range: Range<usize>,
|
||||
}
|
||||
|
||||
impl LineWrap {
|
||||
pub(super) fn height(&self, line_height: Pixels) -> Pixels {
|
||||
line_height * (self.wrap_lines + 1)
|
||||
}
|
||||
}
|
||||
|
||||
/// Used to prepare the text with soft_wrap to be get lines to displayed in the TextArea
|
||||
///
|
||||
/// After use lines to calculate the scroll size of the TextArea
|
||||
|
|
@ -70,7 +68,6 @@ impl TextWrapper {
|
|||
let mut line_wrapper = cx
|
||||
.text_system()
|
||||
.line_wrapper(self.font.clone(), self.font_size);
|
||||
|
||||
let mut prev_line_ix = 0;
|
||||
for line in text.split('\n') {
|
||||
let mut line_wraps = vec![];
|
||||
|
|
|
|||
Loading…
Reference in a new issue