input: Add MovePageUp, MovePageDown keymap to multi-line mode. (#969)
This commit is contained in:
parent
8762e3482a
commit
a478f000d4
3 changed files with 91 additions and 50 deletions
|
|
@ -432,8 +432,6 @@ impl TextElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) struct PrepaintState {
|
pub(super) struct PrepaintState {
|
||||||
/// The visible range (no wrap) of lines in the viewport.
|
|
||||||
visible_range: Range<usize>,
|
|
||||||
/// The lines of entire lines.
|
/// The lines of entire lines.
|
||||||
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`.
|
||||||
|
|
@ -798,6 +796,7 @@ impl Element for TextElement {
|
||||||
last_layout: LastLayout {
|
last_layout: LastLayout {
|
||||||
lines: Rc::new(lines),
|
lines: Rc::new(lines),
|
||||||
line_height,
|
line_height,
|
||||||
|
visible_range,
|
||||||
},
|
},
|
||||||
scroll_size,
|
scroll_size,
|
||||||
line_numbers,
|
line_numbers,
|
||||||
|
|
@ -806,7 +805,6 @@ impl Element for TextElement {
|
||||||
cursor_scroll_offset,
|
cursor_scroll_offset,
|
||||||
current_line_index,
|
current_line_index,
|
||||||
selection_path,
|
selection_path,
|
||||||
visible_range,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -824,7 +822,7 @@ impl Element for TextElement {
|
||||||
let focused = focus_handle.is_focused(window);
|
let focused = focus_handle.is_focused(window);
|
||||||
let bounds = prepaint.bounds;
|
let bounds = prepaint.bounds;
|
||||||
let selected_range = self.input.read(cx).selected_range.clone();
|
let selected_range = self.input.read(cx).selected_range.clone();
|
||||||
let visible_range = &prepaint.visible_range;
|
let visible_range = &prepaint.last_layout.visible_range;
|
||||||
|
|
||||||
window.handle_input(
|
window.handle_input(
|
||||||
&focus_handle,
|
&focus_handle,
|
||||||
|
|
|
||||||
|
|
@ -55,17 +55,19 @@ actions!(
|
||||||
Outdent,
|
Outdent,
|
||||||
IndentInline,
|
IndentInline,
|
||||||
OutdentInline,
|
OutdentInline,
|
||||||
Up,
|
MoveUp,
|
||||||
Down,
|
MoveDown,
|
||||||
Left,
|
MoveLeft,
|
||||||
Right,
|
MoveRight,
|
||||||
|
MoveHome,
|
||||||
|
MoveEnd,
|
||||||
|
MovePageUp,
|
||||||
|
MovePageDown,
|
||||||
SelectUp,
|
SelectUp,
|
||||||
SelectDown,
|
SelectDown,
|
||||||
SelectLeft,
|
SelectLeft,
|
||||||
SelectRight,
|
SelectRight,
|
||||||
SelectAll,
|
SelectAll,
|
||||||
Home,
|
|
||||||
End,
|
|
||||||
SelectToStartOfLine,
|
SelectToStartOfLine,
|
||||||
SelectToEndOfLine,
|
SelectToEndOfLine,
|
||||||
SelectToStart,
|
SelectToStart,
|
||||||
|
|
@ -84,7 +86,6 @@ actions!(
|
||||||
MoveToEnd,
|
MoveToEnd,
|
||||||
MoveToPreviousWord,
|
MoveToPreviousWord,
|
||||||
MoveToNextWord,
|
MoveToNextWord,
|
||||||
TextChanged,
|
|
||||||
Escape
|
Escape
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
@ -118,10 +119,12 @@ pub fn init(cx: &mut App) {
|
||||||
KeyBinding::new("enter", Enter { secondary: false }, Some(CONTEXT)),
|
KeyBinding::new("enter", Enter { secondary: false }, Some(CONTEXT)),
|
||||||
KeyBinding::new("secondary-enter", Enter { secondary: true }, Some(CONTEXT)),
|
KeyBinding::new("secondary-enter", Enter { secondary: true }, Some(CONTEXT)),
|
||||||
KeyBinding::new("escape", Escape, Some(CONTEXT)),
|
KeyBinding::new("escape", Escape, Some(CONTEXT)),
|
||||||
KeyBinding::new("up", Up, Some(CONTEXT)),
|
KeyBinding::new("up", MoveUp, Some(CONTEXT)),
|
||||||
KeyBinding::new("down", Down, Some(CONTEXT)),
|
KeyBinding::new("down", MoveDown, Some(CONTEXT)),
|
||||||
KeyBinding::new("left", Left, Some(CONTEXT)),
|
KeyBinding::new("left", MoveLeft, Some(CONTEXT)),
|
||||||
KeyBinding::new("right", Right, Some(CONTEXT)),
|
KeyBinding::new("right", MoveRight, Some(CONTEXT)),
|
||||||
|
KeyBinding::new("pageup", MovePageUp, Some(CONTEXT)),
|
||||||
|
KeyBinding::new("pagedown", MovePageDown, Some(CONTEXT)),
|
||||||
KeyBinding::new("tab", IndentInline, Some(CONTEXT)),
|
KeyBinding::new("tab", IndentInline, Some(CONTEXT)),
|
||||||
KeyBinding::new("shift-tab", OutdentInline, Some(CONTEXT)),
|
KeyBinding::new("shift-tab", OutdentInline, Some(CONTEXT)),
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
|
|
@ -136,8 +139,8 @@ pub fn init(cx: &mut App) {
|
||||||
KeyBinding::new("shift-right", SelectRight, Some(CONTEXT)),
|
KeyBinding::new("shift-right", SelectRight, Some(CONTEXT)),
|
||||||
KeyBinding::new("shift-up", SelectUp, Some(CONTEXT)),
|
KeyBinding::new("shift-up", SelectUp, Some(CONTEXT)),
|
||||||
KeyBinding::new("shift-down", SelectDown, Some(CONTEXT)),
|
KeyBinding::new("shift-down", SelectDown, Some(CONTEXT)),
|
||||||
KeyBinding::new("home", Home, Some(CONTEXT)),
|
KeyBinding::new("home", MoveHome, Some(CONTEXT)),
|
||||||
KeyBinding::new("end", End, Some(CONTEXT)),
|
KeyBinding::new("end", MoveEnd, Some(CONTEXT)),
|
||||||
KeyBinding::new("shift-home", SelectToStartOfLine, Some(CONTEXT)),
|
KeyBinding::new("shift-home", SelectToStartOfLine, Some(CONTEXT)),
|
||||||
KeyBinding::new("shift-end", SelectToEndOfLine, Some(CONTEXT)),
|
KeyBinding::new("shift-end", SelectToEndOfLine, Some(CONTEXT)),
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
|
|
@ -175,13 +178,13 @@ pub fn init(cx: &mut App) {
|
||||||
#[cfg(not(target_os = "macos"))]
|
#[cfg(not(target_os = "macos"))]
|
||||||
KeyBinding::new("ctrl-v", Paste, Some(CONTEXT)),
|
KeyBinding::new("ctrl-v", Paste, Some(CONTEXT)),
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
KeyBinding::new("ctrl-a", Home, Some(CONTEXT)),
|
KeyBinding::new("ctrl-a", MoveHome, Some(CONTEXT)),
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
KeyBinding::new("cmd-left", Home, Some(CONTEXT)),
|
KeyBinding::new("cmd-left", MoveHome, Some(CONTEXT)),
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
KeyBinding::new("ctrl-e", End, Some(CONTEXT)),
|
KeyBinding::new("ctrl-e", MoveEnd, Some(CONTEXT)),
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
KeyBinding::new("cmd-right", End, Some(CONTEXT)),
|
KeyBinding::new("cmd-right", MoveEnd, Some(CONTEXT)),
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
KeyBinding::new("cmd-z", Undo, Some(CONTEXT)),
|
KeyBinding::new("cmd-z", Undo, Some(CONTEXT)),
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
|
|
@ -217,6 +220,8 @@ pub(super) struct LastLayout {
|
||||||
pub(super) lines: Rc<SmallVec<[WrappedLine; 1]>>,
|
pub(super) lines: Rc<SmallVec<[WrappedLine; 1]>>,
|
||||||
/// The line_height of text layout, this will change will InputElement painted.
|
/// The line_height of text layout, this will change will InputElement painted.
|
||||||
pub(super) line_height: Pixels,
|
pub(super) line_height: Pixels,
|
||||||
|
/// The visible range (no wrap) of lines in the viewport.
|
||||||
|
pub(super) visible_range: Range<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Deref for LastLayout {
|
impl Deref for LastLayout {
|
||||||
|
|
@ -555,8 +560,9 @@ impl InputState {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Move the cursor vertically by one line (up or down) while preserving the column if possible.
|
/// Move the cursor vertically by one line (up or down) while preserving the column if possible.
|
||||||
/// direction: -1 for up, +1 for down
|
///
|
||||||
fn move_vertical(&mut self, direction: i32, window: &mut Window, cx: &mut Context<Self>) {
|
/// 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.is_single_line() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -568,7 +574,7 @@ impl InputState {
|
||||||
let offset = self.cursor_offset();
|
let offset = self.cursor_offset();
|
||||||
let preferred_x_offset = self.preferred_x_offset;
|
let preferred_x_offset = self.preferred_x_offset;
|
||||||
let line_height = last_layout.line_height;
|
let line_height = last_layout.line_height;
|
||||||
let (current_line_index, current_sub_line, current_pos) =
|
let (current_line, current_sub_line, current_pos) =
|
||||||
self.line_and_position_for_offset(offset);
|
self.line_and_position_for_offset(offset);
|
||||||
|
|
||||||
let Some(current_pos) = current_pos else {
|
let Some(current_pos) = current_pos else {
|
||||||
|
|
@ -579,30 +585,32 @@ impl InputState {
|
||||||
.preferred_x_offset
|
.preferred_x_offset
|
||||||
.unwrap_or_else(|| current_pos.x + bounds.origin.x);
|
.unwrap_or_else(|| current_pos.x + bounds.origin.x);
|
||||||
|
|
||||||
let mut new_line_index = current_line_index;
|
let mut new_line = current_line;
|
||||||
let mut new_sub_line = current_sub_line as i32;
|
let mut new_sub_line = current_sub_line as i32;
|
||||||
|
new_sub_line += if move_lines > 0 { 1 } else { -1 };
|
||||||
new_sub_line += direction;
|
|
||||||
|
|
||||||
// Handle moving above the first line
|
// Handle moving above the first line
|
||||||
if direction == -1 && new_line_index == 0 && new_sub_line < 0 {
|
if move_lines < 0 && current_line == 0 && new_sub_line < 0 {
|
||||||
// Move cursor to the beginning of the text
|
// Move cursor to the beginning of the text
|
||||||
self.move_to(0, window, cx);
|
self.move_to(0, window, cx);
|
||||||
|
self.preferred_x_offset = preferred_x_offset;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if new_sub_line < 0 {
|
if new_sub_line < 0 {
|
||||||
if new_line_index > 0 {
|
new_line = new_line
|
||||||
new_line_index -= 1;
|
.saturating_add_signed(move_lines)
|
||||||
new_sub_line = last_layout.lines[new_line_index].wrap_boundaries.len() as i32;
|
.max(0)
|
||||||
|
.min(last_layout.lines.len().saturating_sub(1));
|
||||||
|
new_sub_line = last_layout.lines[new_line].wrap_boundaries.len() as i32;
|
||||||
} else {
|
} else {
|
||||||
new_sub_line = 0;
|
let max_sub_line = last_layout.lines[current_line].wrap_boundaries.len() as i32;
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let max_sub_line = last_layout.lines[new_line_index].wrap_boundaries.len() as i32;
|
|
||||||
if new_sub_line > max_sub_line {
|
if new_sub_line > max_sub_line {
|
||||||
if new_line_index < last_layout.lines.len() - 1 {
|
if new_line < last_layout.lines.len() - 1 {
|
||||||
new_line_index += 1;
|
new_line = new_line
|
||||||
|
.saturating_add_signed(move_lines)
|
||||||
|
.max(0)
|
||||||
|
.min(last_layout.lines.len().saturating_sub(1));
|
||||||
new_sub_line = 0;
|
new_sub_line = 0;
|
||||||
} else {
|
} else {
|
||||||
new_sub_line = max_sub_line;
|
new_sub_line = max_sub_line;
|
||||||
|
|
@ -611,11 +619,11 @@ impl InputState {
|
||||||
}
|
}
|
||||||
|
|
||||||
// If after adjustment, still at the same position, do not proceed
|
// If after adjustment, still at the same position, do not proceed
|
||||||
if new_line_index == current_line_index && new_sub_line == current_sub_line as i32 {
|
if new_line == current_line && new_sub_line == current_sub_line as i32 {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let target_line = &last_layout.lines[new_line_index];
|
let target_line = &last_layout.lines[new_line];
|
||||||
let line_x = current_x - bounds.origin.x;
|
let line_x = current_x - bounds.origin.x;
|
||||||
let target_sub_line = new_sub_line as usize;
|
let target_sub_line = new_sub_line as usize;
|
||||||
|
|
||||||
|
|
@ -628,11 +636,13 @@ impl InputState {
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut prev_lines_offset = 0;
|
let mut prev_lines_offset = 0;
|
||||||
for (i, l) in last_layout.lines.iter().enumerate() {
|
for (_, line) in last_layout
|
||||||
if i == new_line_index {
|
.lines
|
||||||
break;
|
.iter()
|
||||||
}
|
.enumerate()
|
||||||
prev_lines_offset += l.len() + 1;
|
.take_while(|(i, _)| *i < new_line)
|
||||||
|
{
|
||||||
|
prev_lines_offset += line.len() + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
let new_offset = (prev_lines_offset + new_local_index).min(self.text.len());
|
let new_offset = (prev_lines_offset + new_local_index).min(self.text.len());
|
||||||
|
|
@ -815,7 +825,7 @@ impl InputState {
|
||||||
self.focus_handle.focus(window);
|
self.focus_handle.focus(window);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn left(&mut self, _: &Left, window: &mut Window, cx: &mut Context<Self>) {
|
pub(super) fn left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
self.pause_blink_cursor(cx);
|
self.pause_blink_cursor(cx);
|
||||||
if self.selected_range.is_empty() {
|
if self.selected_range.is_empty() {
|
||||||
self.move_to(self.previous_boundary(self.cursor_offset()), window, cx);
|
self.move_to(self.previous_boundary(self.cursor_offset()), window, cx);
|
||||||
|
|
@ -824,7 +834,7 @@ impl InputState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn right(&mut self, _: &Right, window: &mut Window, cx: &mut Context<Self>) {
|
pub(super) fn right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
self.pause_blink_cursor(cx);
|
self.pause_blink_cursor(cx);
|
||||||
if self.selected_range.is_empty() {
|
if self.selected_range.is_empty() {
|
||||||
self.move_to(self.next_boundary(self.selected_range.end), window, cx);
|
self.move_to(self.next_boundary(self.selected_range.end), window, cx);
|
||||||
|
|
@ -833,7 +843,7 @@ impl InputState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn up(&mut self, _: &Up, window: &mut Window, cx: &mut Context<Self>) {
|
pub(super) fn up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
if self.is_single_line() {
|
if self.is_single_line() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -849,7 +859,7 @@ impl InputState {
|
||||||
self.move_vertical(-1, window, cx);
|
self.move_vertical(-1, window, cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn down(&mut self, _: &Down, window: &mut Window, cx: &mut Context<Self>) {
|
pub(super) fn down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
if self.is_single_line() {
|
if self.is_single_line() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -866,6 +876,37 @@ impl InputState {
|
||||||
self.move_vertical(1, window, cx);
|
self.move_vertical(1, window, cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(super) fn page_up(&mut self, _: &MovePageUp, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
|
if self.is_single_line() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(last_layout) = &self.last_layout else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let display_lines = (self.input_bounds.size.height / last_layout.line_height) as isize;
|
||||||
|
self.move_vertical(-display_lines, window, cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn page_down(
|
||||||
|
&mut self,
|
||||||
|
_: &MovePageDown,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) {
|
||||||
|
if self.is_single_line() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(last_layout) = &self.last_layout else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let display_lines = (self.input_bounds.size.height / last_layout.line_height) as isize;
|
||||||
|
self.move_vertical(display_lines, window, cx);
|
||||||
|
}
|
||||||
|
|
||||||
pub(super) fn select_left(
|
pub(super) fn select_left(
|
||||||
&mut self,
|
&mut self,
|
||||||
_: &SelectLeft,
|
_: &SelectLeft,
|
||||||
|
|
@ -915,13 +956,13 @@ impl InputState {
|
||||||
self.select_to(self.text.len(), window, cx)
|
self.select_to(self.text.len(), window, cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn home(&mut self, _: &Home, window: &mut Window, cx: &mut Context<Self>) {
|
pub(super) fn home(&mut self, _: &MoveHome, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
self.pause_blink_cursor(cx);
|
self.pause_blink_cursor(cx);
|
||||||
let offset = self.start_of_line(window, cx);
|
let offset = self.start_of_line(window, cx);
|
||||||
self.move_to(offset, window, cx);
|
self.move_to(offset, window, cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn end(&mut self, _: &End, window: &mut Window, cx: &mut Context<Self>) {
|
pub(super) fn end(&mut self, _: &MoveEnd, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
self.pause_blink_cursor(cx);
|
self.pause_blink_cursor(cx);
|
||||||
let offset = self.end_of_line(window, cx);
|
let offset = self.end_of_line(window, cx);
|
||||||
self.move_to(offset, window, cx);
|
self.move_to(offset, window, cx);
|
||||||
|
|
|
||||||
|
|
@ -214,6 +214,8 @@ impl RenderOnce for TextInput {
|
||||||
.on_action(window.listener_for(&self.state, InputState::down))
|
.on_action(window.listener_for(&self.state, InputState::down))
|
||||||
.on_action(window.listener_for(&self.state, InputState::select_up))
|
.on_action(window.listener_for(&self.state, InputState::select_up))
|
||||||
.on_action(window.listener_for(&self.state, InputState::select_down))
|
.on_action(window.listener_for(&self.state, InputState::select_down))
|
||||||
|
.on_action(window.listener_for(&self.state, InputState::page_up))
|
||||||
|
.on_action(window.listener_for(&self.state, InputState::page_down))
|
||||||
})
|
})
|
||||||
.on_action(window.listener_for(&self.state, InputState::select_all))
|
.on_action(window.listener_for(&self.state, InputState::select_all))
|
||||||
.on_action(window.listener_for(&self.state, InputState::select_to_start_of_line))
|
.on_action(window.listener_for(&self.state, InputState::select_to_start_of_line))
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue