diff --git a/src/bin/edit/documents.rs b/src/bin/edit/documents.rs index d221db8f6fef..de45f79b3031 100644 --- a/src/bin/edit/documents.rs +++ b/src/bin/edit/documents.rs @@ -219,7 +219,7 @@ impl DocumentManager { { let mut tb = buffer.borrow_mut(); tb.set_insert_final_newline(!cfg!(windows)); // As mandated by POSIX. - tb.set_margin_enabled(true); + tb.set_line_numbers(true); tb.set_line_highlight_enabled(true); } Ok(buffer) diff --git a/src/bin/edit/draw_menubar.rs b/src/bin/edit/draw_menubar.rs index 07b7c713d056..b494fbf40e71 100644 --- a/src/bin/edit/draw_menubar.rs +++ b/src/bin/edit/draw_menubar.rs @@ -105,6 +105,8 @@ fn draw_menu_view(ctx: &mut Context, state: &mut State) { if let Some(doc) = state.documents.active() { let mut tb = doc.buffer.borrow_mut(); + let relative_numbers = tb.is_relative_line_numbers(); + let line_number = tb.is_line_numbers_enabled(); let word_wrap = tb.is_word_wrap_enabled(); if ctx.menubar_menu_button(loc(LocId::ViewDocumentPicker), 'P', kbmod::CTRL | vk::P) { @@ -113,6 +115,14 @@ fn draw_menu_view(ctx: &mut Context, state: &mut State) { if ctx.menubar_menu_button(loc(LocId::FileGoto), 'G', kbmod::CTRL | vk::G) { state.wants_goto = true; } + if ctx.menubar_menu_checkbox(loc(LocId::ViewLineNumbers), 'N', kbmod::ALT | vk::L, line_number) { + tb.set_line_numbers(!line_number); + ctx.needs_rerender(); + } + if line_number && ctx.menubar_menu_checkbox(loc(LocId::ViewRelativeLineNumbers), 'R', kbmod::ALT | vk::R, relative_numbers) { + tb.set_relative_line_numbers(!relative_numbers); + ctx.needs_rerender(); + } if ctx.menubar_menu_checkbox(loc(LocId::ViewWordWrap), 'W', kbmod::ALT | vk::Z, word_wrap) { tb.set_word_wrap(!word_wrap); ctx.needs_rerender(); diff --git a/src/bin/edit/localization.rs b/src/bin/edit/localization.rs index 0443781363b3..466c8ec8c104 100644 --- a/src/bin/edit/localization.rs +++ b/src/bin/edit/localization.rs @@ -43,6 +43,8 @@ pub enum LocId { ViewFocusStatusbar, ViewWordWrap, ViewDocumentPicker, + ViewLineNumbers, + ViewRelativeLineNumbers, // Help menu Help, @@ -527,6 +529,34 @@ const S_LANG_LUT: [[&str; LangId::Count as usize]; LocId::Count as usize] = [ /* zh_hans */ "文档选择器…", /* zh_hant */ "文件選擇器…", ], + // ViewLineNumbers + [ + /* en */ "Show Line Numbers", + /* de */ "Zeilennummern anzeigen", + /* es */ "Mostrar números de línea", + /* fr */ "Afficher les numéros de ligne", + /* it */ "Mostra numeri di riga", + /* ja */ "行番号を表示する", + /* ko */ "줄 번호 표시", + /* pt_br */ "Mostrar números de linha", + /* ru */ "Показать номера строк", + /* zh_hans */ "显示行号", + /* zh_hant */ "顯示行號", + ], + // ViewRelativeLineNumbers + [ + /* en */ "Use Relative Line Numbers", + /* de */ "Verwende relative Zeilennummern", + /* es */ "Utiliza números de línea relativos", + /* fr */ "Utilisez des numéros de ligne relatifs", + /* it */ "Utilizza numeri di riga relativi", + /* ja */ "相対行番号を使用してください", + /* ko */ "상대 행 번호를 사용하십시오.", + /* pt_br */ "Use números relativos de linhas", + /* ru */ "Используйте относительные номера строк", + /* zh_hans */ "使用相对行号", + /* zh_hant */ "使用相对行号", + ], // Help (a menu bar item) [ diff --git a/src/buffer/mod.rs b/src/buffer/mod.rs index d341db8c5120..852b352f6ee0 100644 --- a/src/buffer/mod.rs +++ b/src/buffer/mod.rs @@ -81,6 +81,15 @@ enum HistoryType { Delete, } +/// The line number type controls how the line numbers are rendered. +#[derive(Default, Copy, Clone, Eq, PartialEq)] +enum LineNumber { + Absent, + #[default] + Present, + Relative, +} + /// An undo/redo entry. struct HistoryEntry { /// [`TextBuffer::cursor`] position before the change was made. @@ -195,12 +204,12 @@ pub struct TextBuffer { width: CoordType, margin_width: CoordType, - margin_enabled: bool, word_wrap_column: CoordType, word_wrap_enabled: bool, tab_size: CoordType, indent_with_tabs: bool, line_highlight_enabled: bool, + line_numbers: LineNumber, ruler: CoordType, encoding: &'static str, newlines_are_crlf: bool, @@ -242,12 +251,12 @@ impl TextBuffer { width: 0, margin_width: 0, - margin_enabled: false, word_wrap_column: 0, word_wrap_enabled: false, tab_size: 4, indent_with_tabs: false, line_highlight_enabled: false, + line_numbers: LineNumber::Relative, ruler: 0, encoding: "UTF-8", newlines_are_crlf: cfg!(windows), // Windows users want CRLF @@ -421,17 +430,6 @@ impl TextBuffer { self.margin_width } - /// Is the left margin enabled? - pub fn set_margin_enabled(&mut self, enabled: bool) -> bool { - if self.margin_enabled == enabled { - false - } else { - self.margin_enabled = enabled; - self.reflow(); - true - } - } - /// Gets the width of the text contents for layout. pub fn text_width(&self) -> CoordType { self.width - self.margin_width @@ -504,6 +502,11 @@ impl TextBuffer { } } + /// Sets a ruler column, e.g. 80. + pub fn set_ruler(&mut self, column: CoordType) { + self.ruler = column; + } + /// Returns whether tabs are used for indentation. pub fn indent_with_tabs(&self) -> bool { self.indent_with_tabs @@ -519,16 +522,37 @@ impl TextBuffer { self.line_highlight_enabled = enabled; } - /// Sets a ruler column, e.g. 80. - pub fn set_ruler(&mut self, column: CoordType) { - self.ruler = column; + /// Returns whether line numbers are visible. + pub fn is_line_numbers_enabled(&self) -> bool { + self.line_numbers != LineNumber::Absent + } + + /// Returns whether line numbers are visible and relative. + pub fn is_relative_line_numbers(&self) -> bool { + self.line_numbers == LineNumber::Relative + } + + /// Sets the visibility of row numbers. + pub fn set_line_numbers(&mut self, enabled: bool) { + self.line_numbers = if enabled { LineNumber::Present } else { LineNumber::Absent }; + self.reflow(); + } + + /// Sets the visibility of relative row numbers. + pub fn set_relative_line_numbers(&mut self, enabled: bool) { + self.line_numbers = if enabled { + LineNumber::Relative + } else { + LineNumber::Present + }; + self.reflow(); } pub fn reflow(&mut self) { // +1 onto logical_lines, because line numbers are 1-based. // +1 onto log10, because we want the digit width and not the actual log10. // +3 onto log10, because we append " | " to the line numbers to form the margin. - self.margin_width = if self.margin_enabled { + self.margin_width = if self.line_numbers != LineNumber::Absent { self.stats.logical_lines.ilog10() as CoordType + 4 } else { 0 @@ -1513,12 +1537,17 @@ impl TextBuffer { let scratch = scratch_arena(None); let width = destination.width(); let height = destination.height(); - let line_number_width = self.margin_width.max(3) as usize - 3; let text_width = width - self.margin_width; let mut visualizer_buf = [0xE2, 0x90, 0x80]; // U+2400 in UTF8 let mut line = ArenaString::new_in(&scratch); let mut visual_pos_x_max = 0; + let line_number_width = if self.line_numbers != LineNumber::Absent { + self.margin_width.max(3) as usize - 3 + } else { + 0 + }; + // Pick the cursor closer to the `origin.y`. let mut cursor = { let a = self.cursor; @@ -1552,6 +1581,14 @@ impl TextBuffer { } if line_number_width != 0 { + let line_number = cursor_beg.logical_pos.y + 1; + let mut print_number = line_number; + if self.line_numbers == LineNumber::Relative { + let cursor_position = self.cursor.logical_pos.y + 1; + let relative_postion = (line_number - cursor_position).abs(); + print_number = if relative_postion == 0 { line_number } else { relative_postion }; + }; + if visual_line >= self.stats.visual_lines { // Past the end of the buffer? Place " | " in the margin. // Since we know that we won't see line numbers greater than i64::MAX (9223372036854775807) @@ -1562,10 +1599,10 @@ impl TextBuffer { line.push_str(&MARGIN_TEMPLATE[off..]); } else if self.word_wrap_column <= 0 || cursor_beg.logical_pos.x == 0 { // Regular line? Place "123 | " in the margin. - _ = write!(line, "{:1$} │ ", cursor_beg.logical_pos.y + 1, line_number_width); + _ = write!(line, "{:1$} │ ", print_number, line_number_width); } else { // Wrapped line? Place " ... | " in the margin. - let number_width = (cursor_beg.logical_pos.y + 1).ilog10() as usize + 1; + let number_width = print_number.ilog10() as usize + 1; _ = write!( line, "{0:1$}{0:∙<2$} │ ",