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 24412b883bf0be12cb2639dd54dec7f70adf6882..c6c3ff59918d190809ee37ddf71b1fd19fe58e23 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 f4499b5651cc158c66df3faad7f0ecf707e01bb6..b06f23429a15b17368c8da23a755ad2fe3c637c5 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 3ba807308c6718e2fcbf7e8edf0f9c9f9d08824e..d088fc2e3a1659e65df42d872c659abf162dda58 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 | 108 ++++++++++++++++++++++------------- 1 file changed, 69 insertions(+), 39 deletions(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index d088fc2e3a1659e65df42d872c659abf162dda58..04f84fb6164238407b7ffc2cde1f955624f6b00c 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; + ) -> HashMap { + let mut relative_rows: HashMap = Default::default(); + let Some(relative_to) = relative_to else { + return relative_rows; + }; + + 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); } - i += 1; - } - delta = 1; - i = head_idx; - while i > 0 && buffer_rows[i].is_none() { - i -= 1; + 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].is_some() { - relative_rows.insert(i, delta); - delta += 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 04f84fb6164238407b7ffc2cde1f955624f6b00c..2a623b9b6bcb0a14d7acb423b6407fa1b410c59e 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 2a623b9b6bcb0a14d7acb423b6407fa1b410c59e..6f93b07f654067ceb5a95eb86173396d647cc7a9 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);