Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Scrolling Bufferline #8362

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
Prev Previous commit
Next Next commit
Opinionated to scrolling
  • Loading branch information
CedricMeu committed Sep 27, 2023
commit 919f5af0bcccf38f735c96fc2970f73698d9a31a
15 changes: 1 addition & 14 deletions book/src/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
88 changes: 32 additions & 56 deletions helix-term/src/ui/editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down Expand Up @@ -64,7 +64,6 @@ pub struct BufferTab {
text: String,
width: u16,
x: i32,
y: i32,
style: Style,
}

Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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 {
Expand Down Expand Up @@ -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>(
Expand Down Expand Up @@ -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);
Expand Down
25 changes: 1 addition & 24 deletions helix-view/src/editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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 {
Expand Down