From 790aa5d476f2263f2a08a705bb82c7ce5b8d2b53 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 24 Aug 2023 12:53:40 -0600 Subject: [PATCH 1/4] Add relative_line_mode Co-Authored-By: joseph@zed.dev --- assets/settings/default.json | 1 + crates/editor/src/editor_settings.rs | 2 + crates/editor/src/element.rs | 81 +++++++++++++++++++++++++--- 3 files changed, 77 insertions(+), 7 deletions(-) diff --git a/assets/settings/default.json b/assets/settings/default.json index 24412b883b..c6c3ff5991 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -98,6 +98,7 @@ // Whether to show selections in the scrollbar. "selections": true }, + "relative_line_numbers": false, // Inlay hint related settings "inlay_hints": { // Global switch to toggle hints on and off, switched off by default. diff --git a/crates/editor/src/editor_settings.rs b/crates/editor/src/editor_settings.rs index f4499b5651..b06f23429a 100644 --- a/crates/editor/src/editor_settings.rs +++ b/crates/editor/src/editor_settings.rs @@ -9,6 +9,7 @@ pub struct EditorSettings { pub show_completions_on_input: bool, pub use_on_type_format: bool, pub scrollbar: Scrollbar, + pub relative_line_numbers: bool, } #[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] @@ -34,6 +35,7 @@ pub struct EditorSettingsContent { pub show_completions_on_input: Option, pub use_on_type_format: Option, pub scrollbar: Option, + pub relative_line_numbers: Option, } #[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 3ba807308c..d088fc2e3a 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -1439,10 +1439,47 @@ impl EditorElement { .collect() } + fn calculate_relative_line_numbers( + &self, + rows: &Range, + buffer_rows: &Vec>, + relative_to: Option, + ) -> HashMap { + let mut relative_rows: HashMap = Default::default(); + if let Some(relative_to) = relative_to { + let head_idx = (relative_to - rows.start) as usize; + let mut delta = 1; + let mut i = head_idx + 1; + while i < buffer_rows.len() { + if buffer_rows[i].is_some() { + relative_rows.insert(i, delta); + delta += 1; + } + i += 1; + } + delta = 1; + i = head_idx; + while i > 0 && buffer_rows[i].is_none() { + i -= 1; + } + + while i > 0 { + i -= 1; + if buffer_rows[i].is_some() { + relative_rows.insert(i, delta); + delta += 1; + } + } + } + + relative_rows + } + fn layout_line_numbers( &self, rows: Range, active_rows: &BTreeMap, + newest_selection_head: Option, is_singleton: bool, snapshot: &EditorSnapshot, cx: &ViewContext, @@ -1455,21 +1492,33 @@ impl EditorElement { let mut line_number_layouts = Vec::with_capacity(rows.len()); let mut fold_statuses = Vec::with_capacity(rows.len()); let mut line_number = String::new(); - for (ix, row) in snapshot + let is_relative = settings::get::(cx).relative_line_numbers; + let relative_to = if is_relative { + newest_selection_head.map(|head| head.row()) + } else { + None + }; + + let buffer_rows = snapshot .buffer_rows(rows.start) .take((rows.end - rows.start) as usize) - .enumerate() - { + .collect::>(); + + let relative_rows = self.calculate_relative_line_numbers(&rows, &buffer_rows, relative_to); + + for (ix, row) in buffer_rows.iter().enumerate() { let display_row = rows.start + ix as u32; let (active, color) = if active_rows.contains_key(&display_row) { (true, style.line_number_active) } else { (false, style.line_number) }; - if let Some(buffer_row) = row { + if let Some(buffer_row) = *row { if include_line_numbers { line_number.clear(); - write!(&mut line_number, "{}", buffer_row + 1).unwrap(); + let default_number = buffer_row + 1; + let number = relative_rows.get(&ix).unwrap_or(&default_number); + write!(&mut line_number, "{}", number).unwrap(); line_number_layouts.push(Some(cx.text_layout_cache().layout_str( &line_number, style.text.font_size, @@ -2296,6 +2345,7 @@ impl Element for EditorElement { let (line_number_layouts, fold_statuses) = self.layout_line_numbers( start_row..end_row, &active_rows, + newest_selection_head, is_singleton, &snapshot, cx, @@ -3054,7 +3104,6 @@ mod tests { #[gpui::test] fn test_layout_line_numbers(cx: &mut TestAppContext) { init_test(cx, |_| {}); - let editor = cx .add_window(|cx| { let buffer = MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx); @@ -3066,10 +3115,28 @@ mod tests { let layouts = editor.update(cx, |editor, cx| { let snapshot = editor.snapshot(cx); element - .layout_line_numbers(0..6, &Default::default(), false, &snapshot, cx) + .layout_line_numbers(0..6, &Default::default(), None, false, &snapshot, cx) .0 }); assert_eq!(layouts.len(), 6); + + let relative_rows = editor.update(cx, |editor, cx| { + let snapshot = editor.snapshot(cx); + + let rows = 0..6; + let buffer_rows = snapshot + .buffer_rows(rows.start) + .take((rows.end - rows.start) as usize) + .collect::>(); + + element.calculate_relative_line_numbers(&rows, &buffer_rows, Some(3)) + }); + assert_eq!(relative_rows[&0], 3); + assert_eq!(relative_rows[&1], 2); + assert_eq!(relative_rows[&2], 1); + // current line has no relative number + assert_eq!(relative_rows[&4], 1); + assert_eq!(relative_rows[&5], 2); } #[gpui::test] From 8d5dc266a3b83f1c7b776049b96820f433dbcffa Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 24 Aug 2023 23:39:13 -0600 Subject: [PATCH 2/4] Fix relative line numbers when newest cursor offscreen --- crates/editor/src/element.rs | 112 ++++++++++++++++++++++------------- 1 file changed, 71 insertions(+), 41 deletions(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index d088fc2e3a..04f84fb616 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -1441,34 +1441,48 @@ impl EditorElement { fn calculate_relative_line_numbers( &self, + snapshot: &EditorSnapshot, rows: &Range, - buffer_rows: &Vec>, relative_to: Option, - ) -> HashMap { - let mut relative_rows: HashMap = Default::default(); - if let Some(relative_to) = relative_to { - let head_idx = (relative_to - rows.start) as usize; - let mut delta = 1; - let mut i = head_idx + 1; - while i < buffer_rows.len() { - if buffer_rows[i].is_some() { - relative_rows.insert(i, delta); - delta += 1; - } - i += 1; - } - delta = 1; - i = head_idx; - while i > 0 && buffer_rows[i].is_none() { - i -= 1; - } + ) -> HashMap { + let mut relative_rows: HashMap = Default::default(); + let Some(relative_to) = relative_to else { + return relative_rows; + }; - while i > 0 { - i -= 1; - if buffer_rows[i].is_some() { - relative_rows.insert(i, delta); - delta += 1; + let start = rows.start.min(relative_to); + let end = rows.end.max(relative_to); + + let buffer_rows = snapshot + .buffer_rows(start) + .take(1 + (end - start) as usize) + .collect::>(); + + let head_idx = relative_to - start; + let mut delta = 1; + let mut i = head_idx + 1; + while i < buffer_rows.len() as u32 { + if buffer_rows[i as usize].is_some() { + if rows.contains(&(i + start)) { + relative_rows.insert(i + start, delta); } + delta += 1; + } + i += 1; + } + delta = 1; + i = head_idx.min(buffer_rows.len() as u32 - 1); + while i > 0 && buffer_rows[i as usize].is_none() { + i -= 1; + } + + while i > 0 { + i -= 1; + if buffer_rows[i as usize].is_some() { + if rows.contains(&(i + start)) { + relative_rows.insert(i + start, delta); + } + delta += 1; } } @@ -1499,25 +1513,26 @@ impl EditorElement { None }; - let buffer_rows = snapshot + let relative_rows = self.calculate_relative_line_numbers(&snapshot, &rows, relative_to); + + for (ix, row) in snapshot .buffer_rows(rows.start) .take((rows.end - rows.start) as usize) - .collect::>(); - - let relative_rows = self.calculate_relative_line_numbers(&rows, &buffer_rows, relative_to); - - for (ix, row) in buffer_rows.iter().enumerate() { + .enumerate() + { let display_row = rows.start + ix as u32; let (active, color) = if active_rows.contains_key(&display_row) { (true, style.line_number_active) } else { (false, style.line_number) }; - if let Some(buffer_row) = *row { + if let Some(buffer_row) = row { if include_line_numbers { line_number.clear(); let default_number = buffer_row + 1; - let number = relative_rows.get(&ix).unwrap_or(&default_number); + let number = relative_rows + .get(&(ix as u32 + rows.start)) + .unwrap_or(&default_number); write!(&mut line_number, "{}", number).unwrap(); line_number_layouts.push(Some(cx.text_layout_cache().layout_str( &line_number, @@ -2345,7 +2360,7 @@ impl Element for EditorElement { let (line_number_layouts, fold_statuses) = self.layout_line_numbers( start_row..end_row, &active_rows, - newest_selection_head, + newest_selection_head.or_else(|| Some(editor.selections.newest_display(cx).head())), is_singleton, &snapshot, cx, @@ -3122,14 +3137,7 @@ mod tests { let relative_rows = editor.update(cx, |editor, cx| { let snapshot = editor.snapshot(cx); - - let rows = 0..6; - let buffer_rows = snapshot - .buffer_rows(rows.start) - .take((rows.end - rows.start) as usize) - .collect::>(); - - element.calculate_relative_line_numbers(&rows, &buffer_rows, Some(3)) + element.calculate_relative_line_numbers(&snapshot, &(0..6), Some(3)) }); assert_eq!(relative_rows[&0], 3); assert_eq!(relative_rows[&1], 2); @@ -3137,6 +3145,28 @@ mod tests { // current line has no relative number assert_eq!(relative_rows[&4], 1); assert_eq!(relative_rows[&5], 2); + + // works if cursor is before screen + let relative_rows = editor.update(cx, |editor, cx| { + let snapshot = editor.snapshot(cx); + + element.calculate_relative_line_numbers(&snapshot, &(3..6), Some(1)) + }); + assert_eq!(relative_rows.len(), 3); + assert_eq!(relative_rows[&3], 2); + assert_eq!(relative_rows[&4], 3); + assert_eq!(relative_rows[&5], 4); + + // works if cursor is after screen + let relative_rows = editor.update(cx, |editor, cx| { + let snapshot = editor.snapshot(cx); + + element.calculate_relative_line_numbers(&snapshot, &(0..3), Some(6)) + }); + assert_eq!(relative_rows.len(), 3); + assert_eq!(relative_rows[&0], 5); + assert_eq!(relative_rows[&1], 4); + assert_eq!(relative_rows[&2], 3); } #[gpui::test] From f18cdcba546f0965aa2708324064e4639c46eac5 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Fri, 25 Aug 2023 10:56:29 -0600 Subject: [PATCH 3/4] Fix relative line numbers in vim visual mode In visual mode when your selection ends with a newline we show the cursor at the end of the previous line (not the start of the current line). We had only been accounting for this if the cursor was on-screen. --- crates/editor/src/element.rs | 33 ++++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 04f84fb616..2a623b9b6b 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -1493,7 +1493,7 @@ impl EditorElement { &self, rows: Range, active_rows: &BTreeMap, - newest_selection_head: Option, + newest_selection_head: DisplayPoint, is_singleton: bool, snapshot: &EditorSnapshot, cx: &ViewContext, @@ -1508,7 +1508,7 @@ impl EditorElement { let mut line_number = String::new(); let is_relative = settings::get::(cx).relative_line_numbers; let relative_to = if is_relative { - newest_selection_head.map(|head| head.row()) + Some(newest_selection_head.row()) } else { None }; @@ -2357,10 +2357,22 @@ impl Element for EditorElement { }) .collect(); + let head_for_relative = newest_selection_head.unwrap_or_else(|| { + let newest = editor.selections.newest::(cx); + SelectionLayout::new( + newest, + editor.selections.line_mode, + editor.cursor_shape, + &snapshot.display_snapshot, + true, + ) + .head + }); + let (line_number_layouts, fold_statuses) = self.layout_line_numbers( start_row..end_row, &active_rows, - newest_selection_head.or_else(|| Some(editor.selections.newest_display(cx).head())), + head_for_relative, is_singleton, &snapshot, cx, @@ -3130,14 +3142,21 @@ mod tests { let layouts = editor.update(cx, |editor, cx| { let snapshot = editor.snapshot(cx); element - .layout_line_numbers(0..6, &Default::default(), None, false, &snapshot, cx) + .layout_line_numbers( + 0..6, + &Default::default(), + DisplayPoint::new(0, 0), + false, + &snapshot, + cx, + ) .0 }); assert_eq!(layouts.len(), 6); let relative_rows = editor.update(cx, |editor, cx| { let snapshot = editor.snapshot(cx); - element.calculate_relative_line_numbers(&snapshot, &(0..6), Some(3)) + element.calculate_relative_line_numbers(&snapshot, &(0..6), 3) }); assert_eq!(relative_rows[&0], 3); assert_eq!(relative_rows[&1], 2); @@ -3150,7 +3169,7 @@ mod tests { let relative_rows = editor.update(cx, |editor, cx| { let snapshot = editor.snapshot(cx); - element.calculate_relative_line_numbers(&snapshot, &(3..6), Some(1)) + element.calculate_relative_line_numbers(&snapshot, &(3..6), 1) }); assert_eq!(relative_rows.len(), 3); assert_eq!(relative_rows[&3], 2); @@ -3161,7 +3180,7 @@ mod tests { let relative_rows = editor.update(cx, |editor, cx| { let snapshot = editor.snapshot(cx); - element.calculate_relative_line_numbers(&snapshot, &(0..3), Some(6)) + element.calculate_relative_line_numbers(&snapshot, &(0..3), 6) }); assert_eq!(relative_rows.len(), 3); assert_eq!(relative_rows[&0], 5); From bde67b2b9c6fff9d1977e43efd251bff023a53b0 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Fri, 25 Aug 2023 11:08:39 -0600 Subject: [PATCH 4/4] Fix merge-conflict --- crates/editor/src/element.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 2a623b9b6b..6f93b07f65 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -2365,6 +2365,7 @@ impl Element for EditorElement { editor.cursor_shape, &snapshot.display_snapshot, true, + true, ) .head }); @@ -3156,7 +3157,7 @@ mod tests { let relative_rows = editor.update(cx, |editor, cx| { let snapshot = editor.snapshot(cx); - element.calculate_relative_line_numbers(&snapshot, &(0..6), 3) + element.calculate_relative_line_numbers(&snapshot, &(0..6), Some(3)) }); assert_eq!(relative_rows[&0], 3); assert_eq!(relative_rows[&1], 2); @@ -3169,7 +3170,7 @@ mod tests { let relative_rows = editor.update(cx, |editor, cx| { let snapshot = editor.snapshot(cx); - element.calculate_relative_line_numbers(&snapshot, &(3..6), 1) + element.calculate_relative_line_numbers(&snapshot, &(3..6), Some(1)) }); assert_eq!(relative_rows.len(), 3); assert_eq!(relative_rows[&3], 2); @@ -3180,7 +3181,7 @@ mod tests { let relative_rows = editor.update(cx, |editor, cx| { let snapshot = editor.snapshot(cx); - element.calculate_relative_line_numbers(&snapshot, &(0..3), 6) + element.calculate_relative_line_numbers(&snapshot, &(0..3), Some(6)) }); assert_eq!(relative_rows.len(), 3); assert_eq!(relative_rows[&0], 5);