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
Added scrolling and extended bufferline configuration
  • Loading branch information
CedricMeu committed Sep 23, 2023
commit 2042eda60dd247c44f88a034b218645f9e885421
157 changes: 129 additions & 28 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::{CompleteAction, CursorShapeConfig},
editor::{BufferLine, BufferLineStyle, CompleteAction, CursorShapeConfig},
graphics::{Color, CursorKind, Modifier, Rect, Style},
input::{KeyEvent, MouseButton, MouseEvent, MouseEventKind},
keyboard::{KeyCode, KeyModifiers},
Expand Down Expand Up @@ -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())
Expand Down Expand Up @@ -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")
Expand All @@ -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()
Expand All @@ -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);

Expand All @@ -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
Expand Down Expand Up @@ -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);
Expand Down
23 changes: 22 additions & 1 deletion helix-view/src/editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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 {
Expand Down