Extract gutters into helix-view
This commit is contained in:
parent
27c1a84f05
commit
225e8ccf31
4 changed files with 117 additions and 100 deletions
|
@ -17,7 +17,6 @@ use helix_core::{
|
||||||
};
|
};
|
||||||
use helix_view::{
|
use helix_view::{
|
||||||
document::{Mode, SCRATCH_BUFFER_NAME},
|
document::{Mode, SCRATCH_BUFFER_NAME},
|
||||||
editor::{Config, LineNumber},
|
|
||||||
graphics::{CursorKind, Modifier, Rect, Style},
|
graphics::{CursorKind, Modifier, Rect, Style},
|
||||||
info::Info,
|
info::Info,
|
||||||
input::KeyEvent,
|
input::KeyEvent,
|
||||||
|
@ -421,97 +420,12 @@ impl EditorView {
|
||||||
.map(|range| range.cursor_line(text))
|
.map(|range| range.cursor_line(text))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
use std::fmt::Write;
|
|
||||||
|
|
||||||
fn diagnostic<'doc>(
|
|
||||||
doc: &'doc Document,
|
|
||||||
_view: &View,
|
|
||||||
theme: &Theme,
|
|
||||||
_config: &Config,
|
|
||||||
_is_focused: bool,
|
|
||||||
_width: usize,
|
|
||||||
) -> GutterFn<'doc> {
|
|
||||||
let warning = theme.get("warning");
|
|
||||||
let error = theme.get("error");
|
|
||||||
let info = theme.get("info");
|
|
||||||
let hint = theme.get("hint");
|
|
||||||
let diagnostics = doc.diagnostics();
|
|
||||||
|
|
||||||
Box::new(move |line: usize, _selected: bool, out: &mut String| {
|
|
||||||
use helix_core::diagnostic::Severity;
|
|
||||||
if let Some(diagnostic) = diagnostics.iter().find(|d| d.line == line) {
|
|
||||||
write!(out, "●").unwrap();
|
|
||||||
return Some(match diagnostic.severity {
|
|
||||||
Some(Severity::Error) => error,
|
|
||||||
Some(Severity::Warning) | None => warning,
|
|
||||||
Some(Severity::Info) => info,
|
|
||||||
Some(Severity::Hint) => hint,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
None
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn line_number<'doc>(
|
|
||||||
doc: &'doc Document,
|
|
||||||
view: &View,
|
|
||||||
theme: &Theme,
|
|
||||||
config: &Config,
|
|
||||||
is_focused: bool,
|
|
||||||
width: usize,
|
|
||||||
) -> GutterFn<'doc> {
|
|
||||||
let text = doc.text().slice(..);
|
|
||||||
let last_line = view.last_line(doc);
|
|
||||||
// Whether to draw the line number for the last line of the
|
|
||||||
// document or not. We only draw it if it's not an empty line.
|
|
||||||
let draw_last = text.line_to_byte(last_line) < text.len_bytes();
|
|
||||||
|
|
||||||
let linenr = theme.get("ui.linenr");
|
|
||||||
let linenr_select: Style = theme.try_get("ui.linenr.selected").unwrap_or(linenr);
|
|
||||||
|
|
||||||
let current_line = doc
|
|
||||||
.text()
|
|
||||||
.char_to_line(doc.selection(view.id).primary().cursor(text));
|
|
||||||
|
|
||||||
let config = config.line_number;
|
|
||||||
|
|
||||||
Box::new(move |line: usize, selected: bool, out: &mut String| {
|
|
||||||
if line == last_line && !draw_last {
|
|
||||||
write!(out, "{:>1$}", '~', width).unwrap();
|
|
||||||
Some(linenr)
|
|
||||||
} else {
|
|
||||||
let line = match config {
|
|
||||||
LineNumber::Absolute => line + 1,
|
|
||||||
LineNumber::Relative => {
|
|
||||||
if current_line == line {
|
|
||||||
line + 1
|
|
||||||
} else {
|
|
||||||
abs_diff(current_line, line)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let style = if selected && is_focused {
|
|
||||||
linenr_select
|
|
||||||
} else {
|
|
||||||
linenr
|
|
||||||
};
|
|
||||||
write!(out, "{:>1$}", line, width).unwrap();
|
|
||||||
Some(style)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
type GutterFn<'doc> = Box<dyn Fn(usize, bool, &mut String) -> Option<Style> + 'doc>;
|
|
||||||
type Gutter =
|
|
||||||
for<'doc> fn(&'doc Document, &View, &Theme, &Config, bool, usize) -> GutterFn<'doc>;
|
|
||||||
let gutters: &[(Gutter, usize)] = &[(diagnostic, 1), (line_number, 5)];
|
|
||||||
|
|
||||||
let mut offset = 0;
|
let mut offset = 0;
|
||||||
|
|
||||||
// avoid lots of small allocations by reusing a text buffer for each line
|
// avoid lots of small allocations by reusing a text buffer for each line
|
||||||
let mut text = String::with_capacity(8);
|
let mut text = String::with_capacity(8);
|
||||||
|
|
||||||
for (constructor, width) in gutters {
|
for (constructor, width) in view.gutters() {
|
||||||
let gutter = constructor(doc, view, theme, config, is_focused, *width);
|
let gutter = constructor(doc, view, theme, config, is_focused, *width);
|
||||||
text.reserve(*width); // ensure there's enough space for the gutter
|
text.reserve(*width); // ensure there's enough space for the gutter
|
||||||
for (i, line) in (view.offset.row..(last_line + 1)).enumerate() {
|
for (i, line) in (view.offset.row..(last_line + 1)).enumerate() {
|
||||||
|
@ -1214,12 +1128,3 @@ fn canonicalize_key(key: &mut KeyEvent) {
|
||||||
key.modifiers.remove(KeyModifiers::SHIFT)
|
key.modifiers.remove(KeyModifiers::SHIFT)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
const fn abs_diff(a: usize, b: usize) -> usize {
|
|
||||||
if a > b {
|
|
||||||
a - b
|
|
||||||
} else {
|
|
||||||
b - a
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
95
helix-view/src/gutter.rs
Normal file
95
helix-view/src/gutter.rs
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
use std::fmt::Write;
|
||||||
|
|
||||||
|
use crate::{editor::Config, graphics::Style, Document, Theme, View};
|
||||||
|
|
||||||
|
pub type GutterFn<'doc> = Box<dyn Fn(usize, bool, &mut String) -> Option<Style> + 'doc>;
|
||||||
|
pub type Gutter =
|
||||||
|
for<'doc> fn(&'doc Document, &View, &Theme, &Config, bool, usize) -> GutterFn<'doc>;
|
||||||
|
|
||||||
|
pub fn diagnostic<'doc>(
|
||||||
|
doc: &'doc Document,
|
||||||
|
_view: &View,
|
||||||
|
theme: &Theme,
|
||||||
|
_config: &Config,
|
||||||
|
_is_focused: bool,
|
||||||
|
_width: usize,
|
||||||
|
) -> GutterFn<'doc> {
|
||||||
|
let warning = theme.get("warning");
|
||||||
|
let error = theme.get("error");
|
||||||
|
let info = theme.get("info");
|
||||||
|
let hint = theme.get("hint");
|
||||||
|
let diagnostics = doc.diagnostics();
|
||||||
|
|
||||||
|
Box::new(move |line: usize, _selected: bool, out: &mut String| {
|
||||||
|
use helix_core::diagnostic::Severity;
|
||||||
|
if let Some(diagnostic) = diagnostics.iter().find(|d| d.line == line) {
|
||||||
|
write!(out, "●").unwrap();
|
||||||
|
return Some(match diagnostic.severity {
|
||||||
|
Some(Severity::Error) => error,
|
||||||
|
Some(Severity::Warning) | None => warning,
|
||||||
|
Some(Severity::Info) => info,
|
||||||
|
Some(Severity::Hint) => hint,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
None
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn line_number<'doc>(
|
||||||
|
doc: &'doc Document,
|
||||||
|
view: &View,
|
||||||
|
theme: &Theme,
|
||||||
|
config: &Config,
|
||||||
|
is_focused: bool,
|
||||||
|
width: usize,
|
||||||
|
) -> GutterFn<'doc> {
|
||||||
|
let text = doc.text().slice(..);
|
||||||
|
let last_line = view.last_line(doc);
|
||||||
|
// Whether to draw the line number for the last line of the
|
||||||
|
// document or not. We only draw it if it's not an empty line.
|
||||||
|
let draw_last = text.line_to_byte(last_line) < text.len_bytes();
|
||||||
|
|
||||||
|
let linenr = theme.get("ui.linenr");
|
||||||
|
let linenr_select: Style = theme.try_get("ui.linenr.selected").unwrap_or(linenr);
|
||||||
|
|
||||||
|
let current_line = doc
|
||||||
|
.text()
|
||||||
|
.char_to_line(doc.selection(view.id).primary().cursor(text));
|
||||||
|
|
||||||
|
let config = config.line_number;
|
||||||
|
|
||||||
|
Box::new(move |line: usize, selected: bool, out: &mut String| {
|
||||||
|
if line == last_line && !draw_last {
|
||||||
|
write!(out, "{:>1$}", '~', width).unwrap();
|
||||||
|
Some(linenr)
|
||||||
|
} else {
|
||||||
|
use crate::editor::LineNumber;
|
||||||
|
let line = match config {
|
||||||
|
LineNumber::Absolute => line + 1,
|
||||||
|
LineNumber::Relative => {
|
||||||
|
if current_line == line {
|
||||||
|
line + 1
|
||||||
|
} else {
|
||||||
|
abs_diff(current_line, line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let style = if selected && is_focused {
|
||||||
|
linenr_select
|
||||||
|
} else {
|
||||||
|
linenr
|
||||||
|
};
|
||||||
|
write!(out, "{:>1$}", line, width).unwrap();
|
||||||
|
Some(style)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
const fn abs_diff(a: usize, b: usize) -> usize {
|
||||||
|
if a > b {
|
||||||
|
a - b
|
||||||
|
} else {
|
||||||
|
b - a
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,6 +5,7 @@ pub mod clipboard;
|
||||||
pub mod document;
|
pub mod document;
|
||||||
pub mod editor;
|
pub mod editor;
|
||||||
pub mod graphics;
|
pub mod graphics;
|
||||||
|
pub mod gutter;
|
||||||
pub mod info;
|
pub mod info;
|
||||||
pub mod input;
|
pub mod input;
|
||||||
pub mod keyboard;
|
pub mod keyboard;
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
use crate::{graphics::Rect, Document, DocumentId, ViewId};
|
use crate::{
|
||||||
|
graphics::Rect,
|
||||||
|
gutter::{self, Gutter},
|
||||||
|
Document, DocumentId, ViewId,
|
||||||
|
};
|
||||||
use helix_core::{
|
use helix_core::{
|
||||||
graphemes::{grapheme_width, RopeGraphemes},
|
graphemes::{grapheme_width, RopeGraphemes},
|
||||||
line_ending::line_end_char_index,
|
line_ending::line_end_char_index,
|
||||||
|
@ -60,6 +64,8 @@ impl JumpList {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const GUTTERS: &[(Gutter, usize)] = &[(gutter::diagnostic, 1), (gutter::line_number, 5)];
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct View {
|
pub struct View {
|
||||||
pub id: ViewId,
|
pub id: ViewId,
|
||||||
|
@ -83,10 +89,19 @@ impl View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn gutters(&self) -> &[(Gutter, usize)] {
|
||||||
|
GUTTERS
|
||||||
|
}
|
||||||
|
|
||||||
pub fn inner_area(&self) -> Rect {
|
pub fn inner_area(&self) -> Rect {
|
||||||
// TODO: not ideal
|
// TODO: cache this
|
||||||
const OFFSET: u16 = 7; // 1 diagnostic + 5 linenr + 1 gutter
|
let offset = self
|
||||||
self.area.clip_left(OFFSET).clip_bottom(1) // -1 for statusline
|
.gutters()
|
||||||
|
.iter()
|
||||||
|
.map(|(_, width)| *width as u16)
|
||||||
|
.sum::<u16>()
|
||||||
|
+ 1; // +1 for some space between gutters and line
|
||||||
|
self.area.clip_left(offset).clip_bottom(1) // -1 for statusline
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
|
@ -276,6 +291,7 @@ mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use helix_core::Rope;
|
use helix_core::Rope;
|
||||||
const OFFSET: u16 = 7; // 1 diagnostic + 5 linenr + 1 gutter
|
const OFFSET: u16 = 7; // 1 diagnostic + 5 linenr + 1 gutter
|
||||||
|
// const OFFSET: u16 = GUTTERS.iter().map(|(_, width)| *width as u16).sum();
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_text_pos_at_screen_coords() {
|
fn test_text_pos_at_screen_coords() {
|
||||||
|
|
Loading…
Add table
Reference in a new issue