|
|
@ -23,7 +23,7 @@ use helix_core::{
|
|
|
|
};
|
|
|
|
};
|
|
|
|
use helix_view::{
|
|
|
|
use helix_view::{
|
|
|
|
document::{Mode, SavePoint, SCRATCH_BUFFER_NAME},
|
|
|
|
document::{Mode, SavePoint, SCRATCH_BUFFER_NAME},
|
|
|
|
editor::{CompleteAction, CursorShapeConfig},
|
|
|
|
editor::{BufferLine, BufferLineStyle, CompleteAction, CursorShapeConfig},
|
|
|
|
graphics::{Color, CursorKind, Modifier, Rect, Style},
|
|
|
|
graphics::{Color, CursorKind, Modifier, Rect, Style},
|
|
|
|
input::{KeyEvent, MouseButton, MouseEvent, MouseEventKind},
|
|
|
|
input::{KeyEvent, MouseButton, MouseEvent, MouseEventKind},
|
|
|
|
keyboard::{KeyCode, KeyModifiers},
|
|
|
|
keyboard::{KeyCode, KeyModifiers},
|
|
|
@ -58,6 +58,16 @@ pub enum InsertEvent {
|
|
|
|
RequestCompletion,
|
|
|
|
RequestCompletion,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
|
|
|
|
pub struct BufferTab {
|
|
|
|
|
|
|
|
active: bool,
|
|
|
|
|
|
|
|
text: String,
|
|
|
|
|
|
|
|
width: u16,
|
|
|
|
|
|
|
|
x: i32,
|
|
|
|
|
|
|
|
y: i32,
|
|
|
|
|
|
|
|
style: Style,
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
impl Default for EditorView {
|
|
|
|
impl Default for EditorView {
|
|
|
|
fn default() -> Self {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self::new(Keymaps::default())
|
|
|
|
Self::new(Keymaps::default())
|
|
|
@ -520,8 +530,25 @@ impl EditorView {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Render bufferline at the top
|
|
|
|
/// Render bufferline at the top
|
|
|
|
pub fn render_bufferline(editor: &Editor, viewport: Rect, surface: &mut Surface) -> u16 {
|
|
|
|
pub fn render_bufferline(
|
|
|
|
let scratch = PathBuf::from(SCRATCH_BUFFER_NAME); // default filename to use for scratch buffer
|
|
|
|
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
|
|
|
|
let bufferline_active = editor
|
|
|
|
.theme
|
|
|
|
.theme
|
|
|
|
.try_get("ui.bufferline.active")
|
|
|
|
.try_get("ui.bufferline.active")
|
|
|
@ -532,12 +559,14 @@ impl EditorView {
|
|
|
|
.try_get("ui.bufferline")
|
|
|
|
.try_get("ui.bufferline")
|
|
|
|
.unwrap_or_else(|| editor.theme.get("ui.statusline.inactive"));
|
|
|
|
.unwrap_or_else(|| editor.theme.get("ui.statusline.inactive"));
|
|
|
|
|
|
|
|
|
|
|
|
let mut x = viewport.x;
|
|
|
|
let mut x = viewport.x as i32;
|
|
|
|
let mut y = 0;
|
|
|
|
let mut y = viewport.y as i32;
|
|
|
|
let current_doc = view!(editor).doc;
|
|
|
|
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() {
|
|
|
|
for doc in editor.documents() {
|
|
|
|
let fname = doc
|
|
|
|
let fname = doc
|
|
|
|
.path()
|
|
|
|
.path()
|
|
|
@ -547,25 +576,37 @@ impl EditorView {
|
|
|
|
.to_str()
|
|
|
|
.to_str()
|
|
|
|
.unwrap_or_default();
|
|
|
|
.unwrap_or_default();
|
|
|
|
|
|
|
|
|
|
|
|
let style = if current_doc == doc.id() {
|
|
|
|
let active = current_doc == doc.id();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let style = if active {
|
|
|
|
bufferline_active
|
|
|
|
bufferline_active
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
bufferline_inactive
|
|
|
|
bufferline_inactive
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
let text = format!(" {}{} ", fname, if doc.is_modified() { "[+]" } else { "" });
|
|
|
|
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 {
|
|
|
|
if config.style == BufferLineStyle::Wrap
|
|
|
|
y += 1;
|
|
|
|
&& x.saturating_add(text_width as i32) >= viewport.right() as i32
|
|
|
|
x = 0;
|
|
|
|
{
|
|
|
|
|
|
|
|
y = y.saturating_add(1);
|
|
|
|
|
|
|
|
x = viewport.x as _;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
tabs.push((text, x, y, style));
|
|
|
|
buffertabs.push(BufferTab {
|
|
|
|
x += text_width;
|
|
|
|
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);
|
|
|
|
let viewport = viewport.with_height(height);
|
|
|
|
|
|
|
|
|
|
|
@ -577,8 +618,77 @@ impl EditorView {
|
|
|
|
.unwrap_or_else(|| editor.theme.get("ui.statusline")),
|
|
|
|
.unwrap_or_else(|| editor.theme.get("ui.statusline")),
|
|
|
|
);
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
for (text, x, y, style) in tabs {
|
|
|
|
if config.style == BufferLineStyle::Scroll {
|
|
|
|
surface.set_string(x, y, text, style);
|
|
|
|
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
|
|
|
|
height
|
|
|
@ -1413,20 +1523,11 @@ impl Component for EditorView {
|
|
|
|
surface.set_style(area, cx.editor.theme.get("ui.background"));
|
|
|
|
surface.set_style(area, cx.editor.theme.get("ui.background"));
|
|
|
|
let config = cx.editor.config();
|
|
|
|
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
|
|
|
|
// -1 for commandline and -1 for bufferline
|
|
|
|
let mut editor_area = area.clip_bottom(1);
|
|
|
|
let mut editor_area = area.clip_bottom(1);
|
|
|
|
if use_bufferline {
|
|
|
|
let buffer_line_height =
|
|
|
|
let bufferline_height = Self::render_bufferline(cx.editor, area, surface);
|
|
|
|
Self::render_bufferline(cx.editor, area, surface, config.bufferline.clone());
|
|
|
|
editor_area = editor_area.clip_top(bufferline_height);
|
|
|
|
editor_area = editor_area.clip_top(buffer_line_height);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// if the terminal size suddenly changed, we need to trigger a resize
|
|
|
|
// if the terminal size suddenly changed, we need to trigger a resize
|
|
|
|
cx.editor.resize(editor_area);
|
|
|
|
cx.editor.resize(editor_area);
|
|
|
|