From 762fd08e4e52849683ef249370a7785c1ec94b81 Mon Sep 17 00:00:00 2001 From: CedricMeu Date: Fri, 22 Sep 2023 21:53:35 +0200 Subject: [PATCH 1/9] Added behaviour to wrap bufferline --- helix-term/src/ui/editor.rs | 55 +++++++++++++++++++++---------------- 1 file changed, 32 insertions(+), 23 deletions(-) diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index dd6fff087bee..cd932634c758 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -18,7 +18,7 @@ use helix_core::{ movement::Direction, syntax::{self, HighlightEvent}, text_annotations::TextAnnotations, - unicode::width::UnicodeWidthStr, + unicode::{segmentation::UnicodeSegmentation, width::UnicodeWidthStr}, visual_offset_from_block, Change, Position, Range, Selection, Transaction, }; use helix_view::{ @@ -520,16 +520,8 @@ impl EditorView { } /// Render bufferline at the top - pub fn render_bufferline(editor: &Editor, viewport: Rect, surface: &mut Surface) { + pub fn render_bufferline(editor: &Editor, viewport: Rect, surface: &mut Surface) -> u16 { let scratch = PathBuf::from(SCRATCH_BUFFER_NAME); // default filename to use for scratch buffer - surface.clear_with( - viewport, - editor - .theme - .try_get("ui.bufferline.background") - .unwrap_or_else(|| editor.theme.get("ui.statusline")), - ); - let bufferline_active = editor .theme .try_get("ui.bufferline.active") @@ -541,8 +533,11 @@ impl EditorView { .unwrap_or_else(|| editor.theme.get("ui.statusline.inactive")); let mut x = viewport.x; + let mut y = 0; let current_doc = view!(editor).doc; + let mut tabs = Vec::<(String, u16, u16, Style)>::new(); + for doc in editor.documents() { let fname = doc .path() @@ -559,17 +554,34 @@ impl EditorView { }; let text = format!(" {}{} ", fname, if doc.is_modified() { "[+]" } else { "" }); - let used_width = viewport.x.saturating_sub(x); - let rem_width = surface.area.width.saturating_sub(used_width); + let text_width = text.grapheme_indices(true).count() as u16; - x = surface - .set_stringn(x, viewport.y, text, rem_width as usize, style) - .0; - - if x >= surface.area.right() { - break; + if x + text_width > surface.area.width { + y += 1; + x = 0; } + + tabs.push((text, x, y, style)); + x += text_width; } + + let height = if x != 0 { y + 1 } else { y }; + + let viewport = viewport.with_height(height); + + surface.clear_with( + viewport, + editor + .theme + .try_get("ui.bufferline.background") + .unwrap_or_else(|| editor.theme.get("ui.statusline")), + ); + + for (text, x, y, style) in tabs { + surface.set_string(x, y, text, style); + } + + height } pub fn render_gutter<'d>( @@ -1412,16 +1424,13 @@ impl Component for EditorView { // -1 for commandline and -1 for bufferline let mut editor_area = area.clip_bottom(1); if use_bufferline { - editor_area = editor_area.clip_top(1); + let bufferline_height = Self::render_bufferline(cx.editor, area, surface); + editor_area = editor_area.clip_top(bufferline_height); } // if the terminal size suddenly changed, we need to trigger a resize cx.editor.resize(editor_area); - if use_bufferline { - Self::render_bufferline(cx.editor, area.with_height(1), surface); - } - for (view, is_focused) in cx.editor.tree.views() { let doc = cx.editor.document(view.doc).unwrap(); self.render_view(cx.editor, doc, view, area, surface, is_focused); From 2042eda60dd247c44f88a034b218645f9e885421 Mon Sep 17 00:00:00 2001 From: CedricMeu Date: Sat, 23 Sep 2023 13:46:40 +0200 Subject: [PATCH 2/9] Added scrolling and extended bufferline configuration --- helix-term/src/ui/editor.rs | 157 +++++++++++++++++++++++++++++------- helix-view/src/editor.rs | 23 +++++- 2 files changed, 151 insertions(+), 29 deletions(-) diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index cd932634c758..117dcbcd3952 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -23,7 +23,7 @@ use helix_core::{ }; use helix_view::{ document::{Mode, SavePoint, SCRATCH_BUFFER_NAME}, - editor::{CompleteAction, CursorShapeConfig}, + editor::{BufferLine, BufferLineStyle, CompleteAction, CursorShapeConfig}, graphics::{Color, CursorKind, Modifier, Rect, Style}, input::{KeyEvent, MouseButton, MouseEvent, MouseEventKind}, keyboard::{KeyCode, KeyModifiers}, @@ -58,6 +58,16 @@ pub enum InsertEvent { RequestCompletion, } +#[derive(Debug, Clone)] +pub struct BufferTab { + active: bool, + text: String, + width: u16, + x: i32, + y: i32, + style: Style, +} + impl Default for EditorView { fn default() -> Self { Self::new(Keymaps::default()) @@ -520,8 +530,25 @@ impl EditorView { } /// Render bufferline at the top - pub fn render_bufferline(editor: &Editor, viewport: Rect, surface: &mut Surface) -> u16 { - let scratch = PathBuf::from(SCRATCH_BUFFER_NAME); // default filename to use for scratch buffer + pub fn render_bufferline( + editor: &Editor, + viewport: Rect, + surface: &mut Surface, + config: BufferLine, + ) -> u16 { + // check if bufferline should be rendered + use helix_view::editor::BufferLineShow; + let use_bufferline = match config.show { + BufferLineShow::Always => true, + BufferLineShow::Multiple if editor.documents.len() > 1 => true, + _ => false, + }; + + if !use_bufferline { + return 0; + } + + // Define styles let bufferline_active = editor .theme .try_get("ui.bufferline.active") @@ -532,12 +559,14 @@ impl EditorView { .try_get("ui.bufferline") .unwrap_or_else(|| editor.theme.get("ui.statusline.inactive")); - let mut x = viewport.x; - let mut y = 0; + let mut x = viewport.x as i32; + let mut y = viewport.y as i32; let current_doc = view!(editor).doc; - let mut tabs = Vec::<(String, u16, u16, Style)>::new(); + // Keep track of the tabs + let mut buffertabs = Vec::new(); + let scratch = PathBuf::from(SCRATCH_BUFFER_NAME); // default filename to use for scratch buffer for doc in editor.documents() { let fname = doc .path() @@ -547,25 +576,37 @@ impl EditorView { .to_str() .unwrap_or_default(); - let style = if current_doc == doc.id() { + let active = current_doc == doc.id(); + + let style = if active { bufferline_active } else { bufferline_inactive }; let text = format!(" {}{} ", fname, if doc.is_modified() { "[+]" } else { "" }); - let text_width = text.grapheme_indices(true).count() as u16; + let text_width = text.grapheme_indices(true).count(); - if x + text_width > surface.area.width { - y += 1; - x = 0; + if config.style == BufferLineStyle::Wrap + && x.saturating_add(text_width as i32) >= viewport.right() as i32 + { + y = y.saturating_add(1); + x = viewport.x as _; } - tabs.push((text, x, y, style)); - x += text_width; + buffertabs.push(BufferTab { + active, + text, + width: text_width as _, + x, + y, + style, + }); + x = x.saturating_add(text_width as _); } - let height = if x != 0 { y + 1 } else { y }; + let height = + (if x != 0 { y.saturating_add(1) } else { y } as u16).saturating_sub(viewport.y); let viewport = viewport.with_height(height); @@ -577,8 +618,77 @@ impl EditorView { .unwrap_or_else(|| editor.theme.get("ui.statusline")), ); - for (text, x, y, style) in tabs { - surface.set_string(x, y, text, style); + if config.style == BufferLineStyle::Scroll { + let viewport_center = (viewport.width as f64 / 2.).floor() as i32 + viewport.x as i32; + + let maybe_active_buffertab = buffertabs.iter().find(|tab| tab.active); + + if let Some(tab) = maybe_active_buffertab { + log::debug!("Activae buffer found"); + let active_buffertab_center = (tab.width as f64 / 2.).floor() as i32 + tab.x; + + let right_of_center = active_buffertab_center as i32 - viewport_center as i32; + + log::debug!( + "Viewport center {}, tab center {}, right of center {}", + viewport_center, + active_buffertab_center, + right_of_center + ); + + if right_of_center > 0 { + let rightmost = buffertabs.last().unwrap(); + let full_width = rightmost.x + rightmost.width as i32; + + let max_displacement = (full_width - viewport.width as i32).max(0); + let displacement = right_of_center.min(max_displacement); + log::debug!( + "Full width {}, max displacement {}, displacement {}", + full_width, + max_displacement, + displacement + ); + + for tab in buffertabs.iter_mut() { + tab.x = tab.x.saturating_sub(displacement.abs()); + } + } // If on center, or left of center, nothing to do + } // If no active buffer, keep everything scrolled to leftmost + } + + for tab in buffertabs.iter_mut() { + if tab.x < viewport.x as i32 { + if tab.x + tab.width as i32 > viewport.x as i32 { + let new_width = tab.width as i32 + tab.x; + + tab.text = tab + .text + .graphemes(true) + .into_iter() + .skip((tab.width as i32 - new_width) as usize) + .collect(); + + tab.width -= new_width as u16; + tab.x = viewport.x as _; + } else { + // skip tabs completely of screen + continue; + } + } + if tab.x > viewport.right() as i32 { + // Stop when off screen + break; + } + + let _ = surface + .set_stringn( + tab.x as _, + tab.y as _, + tab.text.clone(), + (viewport.right() as usize).saturating_sub(tab.x as _), + tab.style, + ) + .0; } height @@ -1413,20 +1523,11 @@ impl Component for EditorView { surface.set_style(area, cx.editor.theme.get("ui.background")); let config = cx.editor.config(); - // check if bufferline should be rendered - use helix_view::editor::BufferLine; - let use_bufferline = match config.bufferline { - BufferLine::Always => true, - BufferLine::Multiple if cx.editor.documents.len() > 1 => true, - _ => false, - }; - // -1 for commandline and -1 for bufferline let mut editor_area = area.clip_bottom(1); - if use_bufferline { - let bufferline_height = Self::render_bufferline(cx.editor, area, surface); - editor_area = editor_area.clip_top(bufferline_height); - } + let buffer_line_height = + Self::render_bufferline(cx.editor, area, surface, config.bufferline.clone()); + editor_area = editor_area.clip_top(buffer_line_height); // if the terminal size suddenly changed, we need to trigger a resize cx.editor.resize(editor_area); diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 2265633dfced..153e967874d8 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -576,10 +576,18 @@ impl Default for CursorShapeConfig { } } +/// bufferline render modes +#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "kebab-case", default, deny_unknown_fields)] +pub struct BufferLine { + pub show: BufferLineShow, + pub style: BufferLineStyle, +} + /// bufferline render modes #[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] -pub enum BufferLine { +pub enum BufferLineShow { /// Don't render bufferline #[default] Never, @@ -589,6 +597,19 @@ pub enum BufferLine { Multiple, } +/// bufferline render +#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "kebab-case")] +pub enum BufferLineStyle { + /// Don't render bufferline + #[default] + Overflow, + /// Always render + Wrap, + /// Only if multiple buffers are open + Scroll, +} + #[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] pub enum LineNumber { From 67753154d3934692fd609911904595c89123a55f Mon Sep 17 00:00:00 2001 From: CedricMeu Date: Sat, 23 Sep 2023 16:18:31 +0200 Subject: [PATCH 3/9] Updated documentation --- book/src/configuration.md | 16 +++++++++++++++- helix-view/src/editor.rs | 12 +++++++----- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/book/src/configuration.md b/book/src/configuration.md index 3b78481e3103..588c16c1950e 100644 --- a/book/src/configuration.md +++ b/book/src/configuration.md @@ -59,13 +59,27 @@ Its settings will be merged with the configuration directory `config.toml` and t | `true-color` | Set to `true` to override automatic detection of terminal truecolor support in the event of a false negative | `false` | | `undercurl` | Set to `true` to override automatic detection of terminal undercurl support in the event of a false negative | `false` | | `rulers` | List of column positions at which to display the rulers. Can be overridden by language specific `rulers` in `languages.toml` file | `[]` | -| `bufferline` | Renders a line at the top of the editor displaying open buffers. Can be `always`, `never` or `multiple` (only shown if more than one buffer is in use) | `never` | | `color-modes` | Whether to color the mode indicator with different colors depending on the mode itself | `false` | | `text-width` | Maximum line length. Used for the `:reflow` command and soft-wrapping if `soft-wrap.wrap-at-text-width` is set | `80` | | `workspace-lsp-roots` | Directories relative to the workspace root that are treated as LSP roots. Should only be set in `.helix/config.toml` | `[]` | | `default-line-ending` | The line ending to use for new documents. Can be `native`, `lf`, `crlf`, `ff`, `cr` or `nel`. `native` uses the platform's native line ending (`crlf` on Windows, otherwise `lf`). | `native` | | `insert-final-newline` | Whether to automatically insert a trailing line-ending on write if missing | `true` | +### `[editor.bufferline]` Section + +Allows configuring the bufferline at the top of the editor. + +```toml +[editor.bufferline] +show = "always" +style = "scroll" +``` + +| Key | Description | Default | +| --- | --- | --- | +| `show` | Can be `always`, `never` or `multiple` (only shown if more than one buffer is in use) | `never` | +| `style` | Can be `overflow`, `wrap` or `scroll`. | `overflow` | + ### `[editor.statusline]` Section Allows configuring the statusline at the bottom of the editor. diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 153e967874d8..3ca06cd5b0d9 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -576,11 +576,13 @@ impl Default for CursorShapeConfig { } } -/// bufferline render modes +/// bufferline configuration #[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "kebab-case", default, deny_unknown_fields)] pub struct BufferLine { + // Set when to show the bufferline pub show: BufferLineShow, + // Set how to handle overflowing pub style: BufferLineStyle, } @@ -597,16 +599,16 @@ pub enum BufferLineShow { Multiple, } -/// bufferline render +/// bufferline render style #[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] pub enum BufferLineStyle { - /// Don't render bufferline + /// Overflow bufferline on the right #[default] Overflow, - /// Always render + /// Wrap when the bufferline overflows Wrap, - /// Only if multiple buffers are open + /// Scroll active buffer as centered in the bufferline as possible Scroll, } From 041540128549da90b2f9d13cf218e280da7bb3de Mon Sep 17 00:00:00 2001 From: CedricMeu Date: Sat, 23 Sep 2023 16:47:22 +0200 Subject: [PATCH 4/9] Cleaned up code --- helix-term/src/ui/editor.rs | 52 ++++++++++++++----------------------- 1 file changed, 20 insertions(+), 32 deletions(-) diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 117dcbcd3952..94ffc725e890 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -563,7 +563,7 @@ impl EditorView { let mut y = viewport.y as i32; let current_doc = view!(editor).doc; - // Keep track of the tabs + // Gather info on buffertabs let mut buffertabs = Vec::new(); let scratch = PathBuf::from(SCRATCH_BUFFER_NAME); // default filename to use for scratch buffer @@ -609,7 +609,6 @@ impl EditorView { (if x != 0 { y.saturating_add(1) } else { y } as u16).saturating_sub(viewport.y); let viewport = viewport.with_height(height); - surface.clear_with( viewport, editor @@ -621,44 +620,32 @@ impl EditorView { if config.style == BufferLineStyle::Scroll { let viewport_center = (viewport.width as f64 / 2.).floor() as i32 + viewport.x as i32; - let maybe_active_buffertab = buffertabs.iter().find(|tab| tab.active); + let active_buffertab = buffertabs.iter().find(|tab| tab.active).unwrap(); - if let Some(tab) = maybe_active_buffertab { - log::debug!("Activae buffer found"); - let active_buffertab_center = (tab.width as f64 / 2.).floor() as i32 + tab.x; + let active_buffertab_center = + (active_buffertab.width as f64 / 2.).floor() as i32 + active_buffertab.x; - let right_of_center = active_buffertab_center as i32 - viewport_center as i32; + let right_of_center = active_buffertab_center as i32 - viewport_center as i32; - log::debug!( - "Viewport center {}, tab center {}, right of center {}", - viewport_center, - active_buffertab_center, - right_of_center - ); + if right_of_center > 0 { + let rightmost = buffertabs.last().unwrap(); + let full_width = rightmost.x + rightmost.width as i32; - if right_of_center > 0 { - let rightmost = buffertabs.last().unwrap(); - let full_width = rightmost.x + rightmost.width as i32; - - let max_displacement = (full_width - viewport.width as i32).max(0); - let displacement = right_of_center.min(max_displacement); - log::debug!( - "Full width {}, max displacement {}, displacement {}", - full_width, - max_displacement, - displacement - ); + let max_displacement = (full_width - viewport.width as i32).max(0); + let displacement = right_of_center.min(max_displacement); - for tab in buffertabs.iter_mut() { - tab.x = tab.x.saturating_sub(displacement.abs()); - } - } // If on center, or left of center, nothing to do - } // If no active buffer, keep everything scrolled to leftmost + for tab in buffertabs.iter_mut() { + tab.x = tab.x.saturating_sub(displacement.abs()); + } + } // If on center, or left of center, nothing to do } + // Itterate over buffertabs, skip or slice them if left off screen, stop if right of screen. + // If wrapping no buffers will go off screen and all are drawn. for tab in buffertabs.iter_mut() { if tab.x < viewport.x as i32 { if tab.x + tab.width as i32 > viewport.x as i32 { + // Draw on screen portion let new_width = tab.width as i32 + tab.x; tab.text = tab @@ -671,15 +658,16 @@ impl EditorView { tab.width -= new_width as u16; tab.x = viewport.x as _; } else { - // skip tabs completely of screen + // Skip tabs completely of screen continue; } } if tab.x > viewport.right() as i32 { - // Stop when off screen + // Stop when off screen to the right break; } + // Actually put the string on the screen let _ = surface .set_stringn( tab.x as _, From 919f5af0bcccf38f735c96fc2970f73698d9a31a Mon Sep 17 00:00:00 2001 From: CedricMeu Date: Wed, 27 Sep 2023 10:05:28 +0200 Subject: [PATCH 5/9] Opinionated to scrolling --- book/src/configuration.md | 15 +------ helix-term/src/ui/editor.rs | 88 ++++++++++++++----------------------- helix-view/src/editor.rs | 25 +---------- 3 files changed, 34 insertions(+), 94 deletions(-) diff --git a/book/src/configuration.md b/book/src/configuration.md index 588c16c1950e..14e9baa7ccaa 100644 --- a/book/src/configuration.md +++ b/book/src/configuration.md @@ -59,26 +59,13 @@ Its settings will be merged with the configuration directory `config.toml` and t | `true-color` | Set to `true` to override automatic detection of terminal truecolor support in the event of a false negative | `false` | | `undercurl` | Set to `true` to override automatic detection of terminal undercurl support in the event of a false negative | `false` | | `rulers` | List of column positions at which to display the rulers. Can be overridden by language specific `rulers` in `languages.toml` file | `[]` | +| `bufferline` | Renders a line at the top of the editor displaying open buffers. Can be `always`, `never` or `multiple` (only shown if more than one buffer is in use) | `never` | | `color-modes` | Whether to color the mode indicator with different colors depending on the mode itself | `false` | | `text-width` | Maximum line length. Used for the `:reflow` command and soft-wrapping if `soft-wrap.wrap-at-text-width` is set | `80` | | `workspace-lsp-roots` | Directories relative to the workspace root that are treated as LSP roots. Should only be set in `.helix/config.toml` | `[]` | | `default-line-ending` | The line ending to use for new documents. Can be `native`, `lf`, `crlf`, `ff`, `cr` or `nel`. `native` uses the platform's native line ending (`crlf` on Windows, otherwise `lf`). | `native` | | `insert-final-newline` | Whether to automatically insert a trailing line-ending on write if missing | `true` | -### `[editor.bufferline]` Section - -Allows configuring the bufferline at the top of the editor. - -```toml -[editor.bufferline] -show = "always" -style = "scroll" -``` - -| Key | Description | Default | -| --- | --- | --- | -| `show` | Can be `always`, `never` or `multiple` (only shown if more than one buffer is in use) | `never` | -| `style` | Can be `overflow`, `wrap` or `scroll`. | `overflow` | ### `[editor.statusline]` Section diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 94ffc725e890..9ef1ea9f5fec 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -23,7 +23,7 @@ use helix_core::{ }; use helix_view::{ document::{Mode, SavePoint, SCRATCH_BUFFER_NAME}, - editor::{BufferLine, BufferLineStyle, CompleteAction, CursorShapeConfig}, + editor::{BufferLine, CompleteAction, CursorShapeConfig}, graphics::{Color, CursorKind, Modifier, Rect, Style}, input::{KeyEvent, MouseButton, MouseEvent, MouseEventKind}, keyboard::{KeyCode, KeyModifiers}, @@ -64,7 +64,6 @@ pub struct BufferTab { text: String, width: u16, x: i32, - y: i32, style: Style, } @@ -530,24 +529,7 @@ impl EditorView { } /// Render bufferline at the top - pub fn render_bufferline( - editor: &Editor, - viewport: Rect, - surface: &mut Surface, - config: BufferLine, - ) -> u16 { - // check if bufferline should be rendered - use helix_view::editor::BufferLineShow; - let use_bufferline = match config.show { - BufferLineShow::Always => true, - BufferLineShow::Multiple if editor.documents.len() > 1 => true, - _ => false, - }; - - if !use_bufferline { - return 0; - } - + pub fn render_bufferline(editor: &Editor, viewport: Rect, surface: &mut Surface) { // Define styles let bufferline_active = editor .theme @@ -560,7 +542,6 @@ impl EditorView { .unwrap_or_else(|| editor.theme.get("ui.statusline.inactive")); let mut x = viewport.x as i32; - let mut y = viewport.y as i32; let current_doc = view!(editor).doc; // Gather info on buffertabs @@ -587,28 +568,16 @@ impl EditorView { let text = format!(" {}{} ", fname, if doc.is_modified() { "[+]" } else { "" }); let text_width = text.grapheme_indices(true).count(); - if config.style == BufferLineStyle::Wrap - && x.saturating_add(text_width as i32) >= viewport.right() as i32 - { - y = y.saturating_add(1); - x = viewport.x as _; - } - buffertabs.push(BufferTab { active, text, width: text_width as _, x, - y, style, }); x = x.saturating_add(text_width as _); } - let height = - (if x != 0 { y.saturating_add(1) } else { y } as u16).saturating_sub(viewport.y); - - let viewport = viewport.with_height(height); surface.clear_with( viewport, editor @@ -617,31 +586,29 @@ impl EditorView { .unwrap_or_else(|| editor.theme.get("ui.statusline")), ); - if config.style == BufferLineStyle::Scroll { - let viewport_center = (viewport.width as f64 / 2.).floor() as i32 + viewport.x as i32; + // Scroll the tabs correctly + let viewport_center = (viewport.width as f64 / 2.).floor() as i32 + viewport.x as i32; - let active_buffertab = buffertabs.iter().find(|tab| tab.active).unwrap(); + let active_buffertab = buffertabs.iter().find(|tab| tab.active).unwrap(); - let active_buffertab_center = - (active_buffertab.width as f64 / 2.).floor() as i32 + active_buffertab.x; + let active_buffertab_center = + (active_buffertab.width as f64 / 2.).floor() as i32 + active_buffertab.x; - let right_of_center = active_buffertab_center as i32 - viewport_center as i32; + let right_of_center = active_buffertab_center as i32 - viewport_center as i32; - if right_of_center > 0 { - let rightmost = buffertabs.last().unwrap(); - let full_width = rightmost.x + rightmost.width as i32; + if right_of_center > 0 { + let rightmost = buffertabs.last().unwrap(); + let full_width = rightmost.x + rightmost.width as i32; - let max_displacement = (full_width - viewport.width as i32).max(0); - let displacement = right_of_center.min(max_displacement); + let max_displacement = (full_width - viewport.width as i32).max(0); + let displacement = right_of_center.min(max_displacement); - for tab in buffertabs.iter_mut() { - tab.x = tab.x.saturating_sub(displacement.abs()); - } - } // If on center, or left of center, nothing to do - } + for tab in buffertabs.iter_mut() { + tab.x = tab.x.saturating_sub(displacement.abs()); + } + } // If on center, or left of center, nothing to do // Itterate over buffertabs, skip or slice them if left off screen, stop if right of screen. - // If wrapping no buffers will go off screen and all are drawn. for tab in buffertabs.iter_mut() { if tab.x < viewport.x as i32 { if tab.x + tab.width as i32 > viewport.x as i32 { @@ -671,15 +638,13 @@ impl EditorView { let _ = surface .set_stringn( tab.x as _, - tab.y as _, + viewport.y, tab.text.clone(), (viewport.right() as usize).saturating_sub(tab.x as _), tab.style, ) .0; } - - height } pub fn render_gutter<'d>( @@ -1511,15 +1476,26 @@ impl Component for EditorView { surface.set_style(area, cx.editor.theme.get("ui.background")); let config = cx.editor.config(); + // check if bufferline should be rendered + let use_bufferline = match config.bufferline { + BufferLine::Always => true, + BufferLine::Multiple if cx.editor.documents.len() > 1 => true, + _ => false, + }; + // -1 for commandline and -1 for bufferline let mut editor_area = area.clip_bottom(1); - let buffer_line_height = - Self::render_bufferline(cx.editor, area, surface, config.bufferline.clone()); - editor_area = editor_area.clip_top(buffer_line_height); + if use_bufferline { + editor_area = editor_area.clip_top(1); + } // if the terminal size suddenly changed, we need to trigger a resize cx.editor.resize(editor_area); + if use_bufferline { + Self::render_bufferline(cx.editor, area.with_height(1), surface); + } + for (view, is_focused) in cx.editor.tree.views() { let doc = cx.editor.document(view.doc).unwrap(); self.render_view(cx.editor, doc, view, area, surface, is_focused); diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 3ca06cd5b0d9..2265633dfced 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -576,20 +576,10 @@ impl Default for CursorShapeConfig { } } -/// bufferline configuration -#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "kebab-case", default, deny_unknown_fields)] -pub struct BufferLine { - // Set when to show the bufferline - pub show: BufferLineShow, - // Set how to handle overflowing - pub style: BufferLineStyle, -} - /// bufferline render modes #[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] -pub enum BufferLineShow { +pub enum BufferLine { /// Don't render bufferline #[default] Never, @@ -599,19 +589,6 @@ pub enum BufferLineShow { Multiple, } -/// bufferline render style -#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "kebab-case")] -pub enum BufferLineStyle { - /// Overflow bufferline on the right - #[default] - Overflow, - /// Wrap when the bufferline overflows - Wrap, - /// Scroll active buffer as centered in the bufferline as possible - Scroll, -} - #[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] pub enum LineNumber { From a1ec38a31e3438f84875cb1e58c3f51e98f0cd51 Mon Sep 17 00:00:00 2001 From: CedricMeu Date: Wed, 27 Sep 2023 16:40:26 +0200 Subject: [PATCH 6/9] Removed stray newline --- book/src/configuration.md | 1 - 1 file changed, 1 deletion(-) diff --git a/book/src/configuration.md b/book/src/configuration.md index 14e9baa7ccaa..3b78481e3103 100644 --- a/book/src/configuration.md +++ b/book/src/configuration.md @@ -66,7 +66,6 @@ Its settings will be merged with the configuration directory `config.toml` and t | `default-line-ending` | The line ending to use for new documents. Can be `native`, `lf`, `crlf`, `ff`, `cr` or `nel`. `native` uses the platform's native line ending (`crlf` on Windows, otherwise `lf`). | `native` | | `insert-final-newline` | Whether to automatically insert a trailing line-ending on write if missing | `true` | - ### `[editor.statusline]` Section Allows configuring the statusline at the bottom of the editor. From cbbfb2fc6942c6b0715cdc1d167a644b38e91e0c Mon Sep 17 00:00:00 2001 From: CedricMeu Date: Fri, 21 Jun 2024 10:03:10 +0200 Subject: [PATCH 7/9] Added markers to indicate under and overflow --- helix-term/src/ui/editor.rs | 47 ++++++++++++++++++++++++++++++------- 1 file changed, 38 insertions(+), 9 deletions(-) diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 40d48c893b26..a16b96acdd43 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -645,6 +645,8 @@ impl EditorView { ); // Scroll the tabs correctly + // The idea is to align the center of the buffer tab + // as close to the center of the viewport as possible let viewport_center = (viewport.width as f64 / 2.).floor() as i32 + viewport.x as i32; let active_buffertab = buffertabs.iter().find(|tab| tab.active).unwrap(); @@ -654,17 +656,30 @@ impl EditorView { let right_of_center = active_buffertab_center as i32 - viewport_center as i32; - if right_of_center > 0 { - let rightmost = buffertabs.last().unwrap(); - let full_width = rightmost.x + rightmost.width as i32; + // If the active tab falls on the right, we have to move it left by some amount. + // For easthetics, I've chosen to have the rightmost tab not to scroll further left + // than needed, clamping it to the right of the viewport. - let max_displacement = (full_width - viewport.width as i32).max(0); - let displacement = right_of_center.min(max_displacement); + // Get the full width of the bufferline + let rightmost = buffertabs.last().unwrap(); + let full_width = rightmost.x + rightmost.width as i32; - for tab in buffertabs.iter_mut() { - tab.x = tab.x.saturating_sub(displacement.abs()); - } - } // If on center, or left of center, nothing to do + // The maximum possible displacement is amount of overflow on the right + // of the viewport. If no overflow, maximum displacement is 0. + let max_displacement = (full_width - viewport.width as i32).max(0); + + // This part clamps the scrolling of the bufferline to the right of the viewport. + let displacement = right_of_center.min(max_displacement).max(0); + + // If there's any displacement, there's underflow of the bufferline. + let mark_underflow = displacement > 0; + + // If the displacement is not at max, there's overflow of the bufferline. + let mark_overflow = displacement < max_displacement; + + for tab in buffertabs.iter_mut() { + tab.x = tab.x.saturating_sub(displacement.abs()); + } // Itterate over buffertabs, skip or slice them if left off screen, stop if right of screen. for tab in buffertabs.iter_mut() { @@ -703,6 +718,20 @@ impl EditorView { ) .0; } + + // Add under and overflow markers. + let markers = editor + .theme + .try_get("ui.bufferline") + .unwrap_or_else(|| editor.theme.get("ui.bufferline.active")); + + if mark_underflow { + let _ = surface.set_string(viewport.left(), viewport.top(), " < ", markers); + } + + if mark_overflow { + let _ = surface.set_string(viewport.right() - 3, viewport.top(), " > ", markers); + } } pub fn render_gutter<'d>( From 319a0025e31ec5727a77106852271ea3e5e35f5b Mon Sep 17 00:00:00 2001 From: CedricMeu Date: Fri, 21 Jun 2024 10:11:06 +0200 Subject: [PATCH 8/9] Small cleanup --- helix-term/src/ui/editor.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index a16b96acdd43..1d43fbcdac72 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -669,7 +669,7 @@ impl EditorView { let max_displacement = (full_width - viewport.width as i32).max(0); // This part clamps the scrolling of the bufferline to the right of the viewport. - let displacement = right_of_center.min(max_displacement).max(0); + let displacement = right_of_center.clamp(0, max_displacement); // If there's any displacement, there's underflow of the bufferline. let mark_underflow = displacement > 0; @@ -678,7 +678,7 @@ impl EditorView { let mark_overflow = displacement < max_displacement; for tab in buffertabs.iter_mut() { - tab.x = tab.x.saturating_sub(displacement.abs()); + tab.x = tab.x.saturating_sub(displacement); } // Itterate over buffertabs, skip or slice them if left off screen, stop if right of screen. From fab4fa7894067ea975c65b966d175331f2c610a3 Mon Sep 17 00:00:00 2001 From: CedricMeu Date: Fri, 21 Jun 2024 13:37:55 +0200 Subject: [PATCH 9/9] Added `ui.bufferline.marker` key to style the underflow and overflow markers --- book/src/themes.md | 3 ++- helix-term/src/ui/editor.rs | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/book/src/themes.md b/book/src/themes.md index e3b95c0a7af0..c205fce5dc83 100644 --- a/book/src/themes.md +++ b/book/src/themes.md @@ -293,8 +293,9 @@ These scopes are used for theming the editor interface: | `ui.statusline.select` | Statusline mode during select mode ([only if `editor.color-modes` is enabled][editor-section]) | | `ui.statusline.separator` | Separator character in statusline | | `ui.bufferline` | Style for the buffer line | -| `ui.bufferline.active` | Style for the active buffer in buffer line | +| `ui.bufferline.active` | Style for the active buffer in buffer line | | `ui.bufferline.background` | Style for bufferline background | +| `ui.bufferline.marker` | Style for bufferline underflow and overflow markers | | `ui.popup` | Documentation popups (e.g. Space + k) | | `ui.popup.info` | Prompt for multiple key options | | `ui.window` | Borderlines separating splits | diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 1d43fbcdac72..8f3272d6211e 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -722,8 +722,8 @@ impl EditorView { // Add under and overflow markers. let markers = editor .theme - .try_get("ui.bufferline") - .unwrap_or_else(|| editor.theme.get("ui.bufferline.active")); + .try_get("ui.bufferline.marker") + .unwrap_or_else(|| editor.theme.get("ui.bufferline")); if mark_underflow { let _ = surface.set_string(viewport.left(), viewport.top(), " < ", markers);