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)),
|
window_bounds: Some(WindowBounds::Windowed(window_bounds)),
|
||||||
titlebar: Some(TitleBar::title_bar_options()),
|
titlebar: Some(TitleBar::title_bar_options()),
|
||||||
window_min_size: Some(gpui::Size {
|
window_min_size: Some(gpui::Size {
|
||||||
width: px(640.),
|
width: px(480.),
|
||||||
height: px(480.),
|
height: px(320.),
|
||||||
}),
|
}),
|
||||||
kind: WindowKind::Normal,
|
kind: WindowKind::Normal,
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
|
|
|
||||||
|
|
@ -338,18 +338,23 @@ impl TextElement {
|
||||||
return 0..1;
|
return 0..1;
|
||||||
}
|
}
|
||||||
|
|
||||||
let scroll_top = -state.scroll_handle.offset().y;
|
let Some(last_layout) = state.last_layout.as_ref() else {
|
||||||
let total_lines = state.text_wrapper.lines.len();
|
return 0..1;
|
||||||
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);
|
|
||||||
|
|
||||||
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;
|
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);
|
visible_range.end = (ix + 1).min(total_lines);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -435,7 +440,6 @@ pub(super) struct PrepaintState {
|
||||||
last_layout: LastLayout,
|
last_layout: LastLayout,
|
||||||
/// The lines only contains the visible lines in the viewport, based on `visible_range`.
|
/// The lines only contains the visible lines in the viewport, based on `visible_range`.
|
||||||
line_numbers: Option<Vec<SmallVec<[WrappedLine; 1]>>>,
|
line_numbers: Option<Vec<SmallVec<[WrappedLine; 1]>>>,
|
||||||
line_number_width: Pixels,
|
|
||||||
/// Size of the scrollable area by entire lines.
|
/// Size of the scrollable area by entire lines.
|
||||||
scroll_size: Size<Pixels>,
|
scroll_size: Size<Pixels>,
|
||||||
cursor_bounds: Option<Bounds<Pixels>>,
|
cursor_bounds: Option<Bounds<Pixels>>,
|
||||||
|
|
@ -657,7 +661,7 @@ impl Element for TextElement {
|
||||||
};
|
};
|
||||||
|
|
||||||
let wrap_width = if multi_line {
|
let wrap_width = if multi_line {
|
||||||
Some(bounds.size.width - line_number_width - RIGHT_MARGIN)
|
Some(bounds.size.width - line_number_width)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
@ -796,10 +800,11 @@ impl Element for TextElement {
|
||||||
lines: Rc::new(lines),
|
lines: Rc::new(lines),
|
||||||
line_height,
|
line_height,
|
||||||
visible_range,
|
visible_range,
|
||||||
|
line_number_width,
|
||||||
|
wrap_width,
|
||||||
},
|
},
|
||||||
scroll_size,
|
scroll_size,
|
||||||
line_numbers,
|
line_numbers,
|
||||||
line_number_width,
|
|
||||||
cursor_bounds,
|
cursor_bounds,
|
||||||
cursor_scroll_offset,
|
cursor_scroll_offset,
|
||||||
current_line_index,
|
current_line_index,
|
||||||
|
|
@ -915,7 +920,10 @@ impl Element for TextElement {
|
||||||
.skip(visible_range.start)
|
.skip(visible_range.start)
|
||||||
.take(visible_range.len())
|
.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);
|
_ = line.paint(p, line_height, TextAlign::Left, None, window, cx);
|
||||||
offset_y += line.size(line_height).height;
|
offset_y += line.size(line_height).height;
|
||||||
}
|
}
|
||||||
|
|
@ -934,7 +942,6 @@ impl Element for TextElement {
|
||||||
state.set_input_bounds(input_bounds, cx);
|
state.set_input_bounds(input_bounds, cx);
|
||||||
state.last_selected_range = Some(selected_range);
|
state.last_selected_range = Some(selected_range);
|
||||||
state.scroll_size = prepaint.scroll_size;
|
state.scroll_size = prepaint.scroll_size;
|
||||||
state.line_number_width = prepaint.line_number_width;
|
|
||||||
state
|
state
|
||||||
.scroll_handle
|
.scroll_handle
|
||||||
.set_offset(prepaint.cursor_scroll_offset);
|
.set_offset(prepaint.cursor_scroll_offset);
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,11 @@ impl DiagnosticPopover {
|
||||||
let Some(range) = self.marker.range.as_ref() else {
|
let Some(range) = self.marker.range.as_ref() else {
|
||||||
return None;
|
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
|
let (_, _, start_pos) = self
|
||||||
.state
|
.state
|
||||||
|
|
|
||||||
|
|
@ -222,6 +222,10 @@ pub(super) struct LastLayout {
|
||||||
pub(super) line_height: Pixels,
|
pub(super) line_height: Pixels,
|
||||||
/// The visible range (no wrap) of lines in the viewport.
|
/// The visible range (no wrap) of lines in the viewport.
|
||||||
pub(super) visible_range: Range<usize>,
|
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 {
|
impl Deref for LastLayout {
|
||||||
|
|
@ -268,7 +272,6 @@ pub struct InputState {
|
||||||
pub(super) scroll_state: ScrollbarState,
|
pub(super) scroll_state: ScrollbarState,
|
||||||
/// The size of the scrollable content.
|
/// The size of the scrollable content.
|
||||||
pub(crate) scroll_size: gpui::Size<Pixels>,
|
pub(crate) scroll_size: gpui::Size<Pixels>,
|
||||||
pub(crate) line_number_width: Pixels,
|
|
||||||
|
|
||||||
/// The mask pattern for formatting the input text
|
/// The mask pattern for formatting the input text
|
||||||
pub(crate) mask_pattern: MaskPattern,
|
pub(crate) mask_pattern: MaskPattern,
|
||||||
|
|
@ -344,7 +347,6 @@ impl InputState {
|
||||||
scroll_state: ScrollbarState::default(),
|
scroll_state: ScrollbarState::default(),
|
||||||
scroll_size: gpui::size(px(0.), px(0.)),
|
scroll_size: gpui::size(px(0.), px(0.)),
|
||||||
preferred_x_offset: None,
|
preferred_x_offset: None,
|
||||||
line_number_width: px(0.),
|
|
||||||
placeholder: SharedString::default(),
|
placeholder: SharedString::default(),
|
||||||
mask_pattern: MaskPattern::default(),
|
mask_pattern: MaskPattern::default(),
|
||||||
diagnostic_popover: None,
|
diagnostic_popover: None,
|
||||||
|
|
@ -1749,6 +1751,7 @@ impl InputState {
|
||||||
};
|
};
|
||||||
|
|
||||||
let line_height = last_layout.line_height;
|
let line_height = last_layout.line_height;
|
||||||
|
let line_number_width = last_layout.line_number_width;
|
||||||
|
|
||||||
// TIP: About the IBeam cursor
|
// TIP: About the IBeam cursor
|
||||||
//
|
//
|
||||||
|
|
@ -1760,7 +1763,7 @@ impl InputState {
|
||||||
//
|
//
|
||||||
// - included the input padding.
|
// - included the input padding.
|
||||||
// - included the scroll offset.
|
// - 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 index = 0;
|
||||||
let mut y_offset = px(0.);
|
let mut y_offset = px(0.);
|
||||||
|
|
@ -2095,10 +2098,12 @@ impl InputState {
|
||||||
self.input_bounds = new_bounds;
|
self.input_bounds = new_bounds;
|
||||||
|
|
||||||
// Update text_wrapper wrap_width if changed.
|
// Update text_wrapper wrap_width if changed.
|
||||||
if wrap_width_changed {
|
if let Some(last_layout) = self.last_layout.as_ref() {
|
||||||
self.text_wrapper
|
if wrap_width_changed {
|
||||||
.set_wrap_width(Some(new_bounds.size.width), cx);
|
self.text_wrapper.set_wrap_width(last_layout.wrap_width, cx);
|
||||||
self.mode.update_auto_grow(&self.text_wrapper);
|
self.mode.update_auto_grow(&self.text_wrapper);
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2256,11 +2261,12 @@ impl EntityInputHandler for InputState {
|
||||||
) -> Option<Bounds<Pixels>> {
|
) -> Option<Bounds<Pixels>> {
|
||||||
let last_layout = self.last_layout.as_ref()?;
|
let last_layout = self.last_layout.as_ref()?;
|
||||||
let line_height = last_layout.line_height;
|
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 range = self.range_from_utf16(&range_utf16);
|
||||||
|
|
||||||
let mut start_origin = None;
|
let mut start_origin = None;
|
||||||
let mut end_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 y_offset = px(0.);
|
||||||
let mut index_offset = 0;
|
let mut index_offset = 0;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,17 +6,15 @@ use gpui::{App, Font, LineFragment, Pixels, SharedString};
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
pub(super) struct LineWrap {
|
pub(super) struct LineWrap {
|
||||||
/// The number of soft wrapped lines of this line (Not include first line.)
|
/// 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,
|
pub(super) wrap_lines: usize,
|
||||||
/// The range of the line text in the entire text.
|
/// The range of the line text in the entire text.
|
||||||
pub(super) range: Range<usize>,
|
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
|
/// 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
|
/// After use lines to calculate the scroll size of the TextArea
|
||||||
|
|
@ -70,7 +68,6 @@ impl TextWrapper {
|
||||||
let mut line_wrapper = cx
|
let mut line_wrapper = cx
|
||||||
.text_system()
|
.text_system()
|
||||||
.line_wrapper(self.font.clone(), self.font_size);
|
.line_wrapper(self.font.clone(), self.font_size);
|
||||||
|
|
||||||
let mut prev_line_ix = 0;
|
let mut prev_line_ix = 0;
|
||||||
for line in text.split('\n') {
|
for line in text.split('\n') {
|
||||||
let mut line_wraps = vec![];
|
let mut line_wraps = vec![];
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue