@@ -4,8 +4,8 @@ use helix_core::{
5 regex::{self, Regex},
5 regex::{self, Regex},
6 register, selection,
6 register, selection,
7− state::{Direction, Granularity, State},
7+ state::{coords_at_pos, pos_at_coords, Direction, Granularity, State},
8− Change, ChangeSet, Range, Selection, Tendril, Transaction,
8+ Change, ChangeSet, Position, Range, Selection, Tendril, Transaction,
11use once_cell::sync::Lazy;
11use once_cell::sync::Lazy;
@@ -222,62 +222,60 @@ pub fn extend_next_word_end(cx: &mut Context) {
222 doc.set_selection(selection);
222 doc.set_selection(selection);
225−pub fn page_up(cx: &mut Context) {
225+fn scroll(view: &mut View, offset: usize, direction: Direction) {
226− let view = cx.view();
226+ use Direction::*;
227+ let text = view.doc.text().slice(..);
229+ let last_line = view.last_line();
230+ let cursor = coords_at_pos(text, view.doc.selection().cursor());
231+ let doc_last_line = text.len_lines() - 1;
227− if view.first_line < PADDING {
233+ if direction == Backward && view.first_line == 0
234+ || direction == Forward && last_line == doc_last_line
231− view.first_line = view.first_line.saturating_sub(view.area.height as usize);
239+ let scrolloff = PADDING; // min(user pref, half win width/height)
241+ // cursor visual offset
242+ let cursor_off = cursor.row - view.first_line;
233− if !view.check_cursor_in_view() {
244+ view.first_line = match direction {
234− let text = view.doc.text();
245+ Forward => view.first_line + offset,
235− let pos = text.line_to_char(view.last_line().saturating_sub(PADDING));
246+ Backward => view.first_line.saturating_sub(offset),
236− view.doc.set_selection(Selection::point(pos));
248+ .min(doc_last_line);
250+ // clamp into viewport
251+ let line = (view.first_line + cursor_off).clamp(
252+ view.first_line + scrolloff,
253+ view.first_line + view.last_line().saturating_sub(scrolloff),
256+ let pos = pos_at_coords(text, Position::new(line, cursor.col)); // this func will properly truncate to line end
257+ view.doc.set_selection(Selection::point(pos));
240−pub fn page_down(cx: &mut Context) {
260+pub fn page_up(cx: &mut Context) {
241 let view = cx.view();
261 let view = cx.view();
242− view.first_line += view.area.height as usize + PADDING;
262+ scroll(view, view.area.height as usize, Direction::Backward);
244− if view.first_line < view.doc.text().len_lines() {
265+pub fn page_down(cx: &mut Context) {
245− let text = view.doc.text();
266+ let view = cx.view();
246− let pos = text.line_to_char(view.first_line as usize);
267+ scroll(view, view.area.height as usize, Direction::Forward);
247− view.doc.set_selection(Selection::point(pos));
251pub fn half_page_up(cx: &mut Context) {
270pub fn half_page_up(cx: &mut Context) {
252 let view = cx.view();
271 let view = cx.view();
253− if view.first_line < PADDING {
257− view.first_line = view
259− .saturating_sub(view.area.height as usize / 2);
272+ scroll(view, view.area.height as usize / 2, Direction::Backward);
261− if !view.check_cursor_in_view() {
262− let text = &view.doc.text();
263− let pos = text.line_to_char(view.last_line() - PADDING);
264− view.doc.set_selection(Selection::point(pos));
268pub fn half_page_down(cx: &mut Context) {
275pub fn half_page_down(cx: &mut Context) {
269 let view = cx.view();
276 let view = cx.view();
270− let lines = view.doc.text().len_lines();
271− if view.first_line < lines.saturating_sub(view.area.height as usize) {
272− view.first_line += view.area.height as usize / 2;
277+ scroll(view, view.area.height as usize / 2, Direction::Forward);
274− if !view.check_cursor_in_view() {
275− let text = view.doc.text();
276− let pos = text.line_to_char(view.first_line as usize);
277− view.doc.set_selection(Selection::point(pos));
280−// avoid select by default by having a visual mode switch that makes movements into selects
282pub fn extend_char_left(cx: &mut Context) {
280pub fn extend_char_left(cx: &mut Context) {
283 let count = cx.count;
281 let count = cx.count;
@@ -439,8 +437,10 @@ pub fn search_selection(cx: &mut Context) {
439pub fn select_line(cx: &mut Context) {
437pub fn select_line(cx: &mut Context) {
440 let count = cx.count;
438 let count = cx.count;
441 let doc = cx.doc();
439 let doc = cx.doc();
442 let pos = doc.selection().primary();
441 let pos = doc.selection().primary();
443 let text = doc.text();
442 let text = doc.text();
444 let line = text.char_to_line(pos.head);
444 let line = text.char_to_line(pos.head);
445 let start = text.line_to_char(line);
445 let start = text.line_to_char(line);
446 let end = text.line_to_char(line + count).saturating_sub(1);
446 let end = text.line_to_char(line + count).saturating_sub(1);