mirror of https://github.com/helix-editor/helix
Adds support for right-hand gutters
parent
7e7a98560e
commit
8b504f1fe5
|
@ -35,6 +35,7 @@
|
|||
| `cursorcolumn` | Highlight all columns with a cursor | `false` |
|
||||
| `continue-comments` | if helix should automatically add a line comment token if you create a new line inside a comment. | `true` |
|
||||
| `gutters` | Gutters to display: Available are `diagnostics` and `diff` and `line-numbers` and `spacer`, note that `diagnostics` also includes other features like breakpoints, 1-width padding will be inserted if gutters is non-empty | `["diagnostics", "spacer", "line-numbers", "spacer", "diff"]` |
|
||||
| `gutters` | Gutters to display on the right-hand side of the window. Configuration is identical to the `gutters` key above, but specified gutters will be rendered right-to-left. | `[]` |
|
||||
| `auto-completion` | Enable automatic pop up of auto-completion | `true` |
|
||||
| `path-completion` | Enable filepath completion. Show files and directories if an existing path at the cursor was recognized, either absolute or relative to the current opened document or current working directory (if the buffer is not yet saved). Defaults to true. | `true` |
|
||||
| `auto-format` | Enable automatic formatting on save | `true` |
|
||||
|
|
|
@ -25,7 +25,7 @@ use helix_core::{
|
|||
use helix_view::{
|
||||
annotations::diagnostics::DiagnosticFilter,
|
||||
document::{Mode, SCRATCH_BUFFER_NAME},
|
||||
editor::{CompleteAction, CursorShapeConfig},
|
||||
editor::{CompleteAction, CursorShapeConfig, GutterType},
|
||||
graphics::{Color, CursorKind, Modifier, Rect, Style},
|
||||
input::{KeyEvent, MouseButton, MouseEvent, MouseEventKind},
|
||||
keyboard::{KeyCode, KeyModifiers},
|
||||
|
@ -165,7 +165,7 @@ impl EditorView {
|
|||
|
||||
let gutter_overflow = view.gutter_offset(doc) == 0;
|
||||
if !gutter_overflow {
|
||||
Self::render_gutter(
|
||||
Self::render_gutters(
|
||||
editor,
|
||||
doc,
|
||||
view,
|
||||
|
@ -661,7 +661,7 @@ impl EditorView {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn render_gutter<'d>(
|
||||
pub fn render_gutters<'d>(
|
||||
editor: &'d Editor,
|
||||
doc: &'d Document,
|
||||
view: &View,
|
||||
|
@ -677,20 +677,22 @@ impl EditorView {
|
|||
.map(|range| range.cursor_line(text))
|
||||
.collect();
|
||||
|
||||
let mut offset = 0;
|
||||
let mut left_offset = 0;
|
||||
let mut right_offset = 0;
|
||||
|
||||
let gutter_style = theme.get("ui.gutter");
|
||||
let gutter_selected_style = theme.get("ui.gutter.selected");
|
||||
let gutter_style_virtual = theme.get("ui.gutter.virtual");
|
||||
let gutter_selected_style_virtual = theme.get("ui.gutter.selected.virtual");
|
||||
|
||||
for gutter_type in view.gutters() {
|
||||
let render_gutter_item = move |viewport: Rect, offset: u16, gutter_type: &GutterType| {
|
||||
let mut gutter = gutter_type.style(editor, doc, view, theme, is_focused);
|
||||
let width = gutter_type.width(view, doc);
|
||||
// avoid lots of small allocations by reusing a text buffer for each line
|
||||
let mut text = String::with_capacity(width);
|
||||
let cursors = cursors.clone();
|
||||
let gutter_decoration = move |renderer: &mut TextRenderer, pos: LinePos| {
|
||||
|
||||
move |renderer: &mut TextRenderer, pos: LinePos| {
|
||||
// TODO handle softwrap in gutters
|
||||
let selected = cursors.contains(&pos.doc_line);
|
||||
let x = viewport.x + offset;
|
||||
|
@ -719,10 +721,27 @@ impl EditorView {
|
|||
);
|
||||
}
|
||||
text.clear();
|
||||
};
|
||||
decoration_manager.add_decoration(gutter_decoration);
|
||||
}
|
||||
};
|
||||
|
||||
offset += width as u16;
|
||||
for gutter_type in view.gutters() {
|
||||
let decoration = render_gutter_item(viewport, left_offset, gutter_type);
|
||||
decoration_manager.add_decoration(decoration);
|
||||
|
||||
left_offset += gutter_type.width(view, doc) as u16;
|
||||
}
|
||||
|
||||
for gutter_type in view.gutters_right() {
|
||||
right_offset += gutter_type.width(view, doc) as u16;
|
||||
|
||||
// Offset is moved prior to rendering right-hand gutter items
|
||||
// since string rendering happens from left to right
|
||||
let decoration = render_gutter_item(
|
||||
viewport,
|
||||
viewport.width.saturating_sub(right_offset),
|
||||
gutter_type,
|
||||
);
|
||||
decoration_manager.add_decoration(decoration);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -263,6 +263,8 @@ pub struct Config {
|
|||
pub cursorcolumn: bool,
|
||||
#[serde(deserialize_with = "deserialize_gutter_seq_or_struct")]
|
||||
pub gutters: GutterConfig,
|
||||
#[serde(deserialize_with = "deserialize_gutter_seq_or_struct")]
|
||||
pub gutters_right: GutterConfig,
|
||||
/// Middle click paste support. Defaults to true.
|
||||
pub middle_click_paste: bool,
|
||||
/// Automatic insertion of pairs to parentheses, brackets,
|
||||
|
@ -969,6 +971,7 @@ impl Default for Config {
|
|||
cursorline: false,
|
||||
cursorcolumn: false,
|
||||
gutters: GutterConfig::default(),
|
||||
gutters_right: GutterConfig::from(Vec::new()),
|
||||
middle_click_paste: true,
|
||||
auto_pairs: AutoPairConfig::default(),
|
||||
auto_completion: true,
|
||||
|
@ -1679,7 +1682,13 @@ impl Editor {
|
|||
.try_get(self.tree.focus)
|
||||
.filter(|v| id == v.doc) // Different Document
|
||||
.cloned()
|
||||
.unwrap_or_else(|| View::new(id, self.config().gutters.clone()));
|
||||
.unwrap_or_else(|| {
|
||||
View::new(
|
||||
id,
|
||||
self.config().gutters.clone(),
|
||||
self.config().gutters_right.clone(),
|
||||
)
|
||||
});
|
||||
let view_id = self.tree.split(
|
||||
view,
|
||||
match action {
|
||||
|
@ -1863,7 +1872,11 @@ impl Editor {
|
|||
.map(|(&doc_id, _)| doc_id)
|
||||
.next()
|
||||
.unwrap_or_else(|| self.new_document(Document::default(self.config.clone())));
|
||||
let view = View::new(doc_id, self.config().gutters.clone());
|
||||
let view = View::new(
|
||||
doc_id,
|
||||
self.config().gutters.clone(),
|
||||
self.config().gutters_right.clone(),
|
||||
);
|
||||
let view_id = self.tree.insert(view);
|
||||
let doc = doc_mut!(self, &doc_id);
|
||||
doc.ensure_view_init(view_id);
|
||||
|
|
|
@ -338,7 +338,11 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_default_gutter_widths() {
|
||||
let mut view = View::new(DocumentId::default(), GutterConfig::default());
|
||||
let mut view = View::new(
|
||||
DocumentId::default(),
|
||||
GutterConfig::default(),
|
||||
GutterConfig::from(Vec::new()),
|
||||
);
|
||||
view.area = Rect::new(40, 40, 40, 40);
|
||||
|
||||
let rope = Rope::from_str("abc\n\tdef");
|
||||
|
@ -363,7 +367,11 @@ mod tests {
|
|||
..Default::default()
|
||||
};
|
||||
|
||||
let mut view = View::new(DocumentId::default(), gutters);
|
||||
let mut view = View::new(
|
||||
DocumentId::default(),
|
||||
gutters,
|
||||
GutterConfig::from(Vec::new()),
|
||||
);
|
||||
view.area = Rect::new(40, 40, 40, 40);
|
||||
|
||||
let rope = Rope::from_str("abc\n\tdef");
|
||||
|
@ -381,7 +389,11 @@ mod tests {
|
|||
line_numbers: GutterLineNumbersConfig { min_width: 10 },
|
||||
};
|
||||
|
||||
let mut view = View::new(DocumentId::default(), gutters);
|
||||
let mut view = View::new(
|
||||
DocumentId::default(),
|
||||
gutters,
|
||||
GutterConfig::from(Vec::new()),
|
||||
);
|
||||
view.area = Rect::new(40, 40, 40, 40);
|
||||
|
||||
let rope = Rope::from_str("abc\n\tdef");
|
||||
|
@ -403,7 +415,11 @@ mod tests {
|
|||
line_numbers: GutterLineNumbersConfig { min_width: 1 },
|
||||
};
|
||||
|
||||
let mut view = View::new(DocumentId::default(), gutters);
|
||||
let mut view = View::new(
|
||||
DocumentId::default(),
|
||||
gutters,
|
||||
GutterConfig::from(Vec::new()),
|
||||
);
|
||||
view.area = Rect::new(40, 40, 40, 40);
|
||||
|
||||
let rope = Rope::from_str("a\nb");
|
||||
|
|
|
@ -736,22 +736,38 @@ mod test {
|
|||
width: 180,
|
||||
height: 80,
|
||||
});
|
||||
let mut view = View::new(DocumentId::default(), GutterConfig::default());
|
||||
let mut view = View::new(
|
||||
DocumentId::default(),
|
||||
GutterConfig::default(),
|
||||
GutterConfig::from(Vec::new()),
|
||||
);
|
||||
view.area = Rect::new(0, 0, 180, 80);
|
||||
tree.insert(view);
|
||||
|
||||
let l0 = tree.focus;
|
||||
let view = View::new(DocumentId::default(), GutterConfig::default());
|
||||
let view = View::new(
|
||||
DocumentId::default(),
|
||||
GutterConfig::default(),
|
||||
GutterConfig::from(Vec::new()),
|
||||
);
|
||||
tree.split(view, Layout::Vertical);
|
||||
let r0 = tree.focus;
|
||||
|
||||
tree.focus = l0;
|
||||
let view = View::new(DocumentId::default(), GutterConfig::default());
|
||||
let view = View::new(
|
||||
DocumentId::default(),
|
||||
GutterConfig::default(),
|
||||
GutterConfig::from(Vec::new()),
|
||||
);
|
||||
tree.split(view, Layout::Horizontal);
|
||||
let l1 = tree.focus;
|
||||
|
||||
tree.focus = l0;
|
||||
let view = View::new(DocumentId::default(), GutterConfig::default());
|
||||
let view = View::new(
|
||||
DocumentId::default(),
|
||||
GutterConfig::default(),
|
||||
GutterConfig::from(Vec::new()),
|
||||
);
|
||||
tree.split(view, Layout::Vertical);
|
||||
|
||||
// Tree in test
|
||||
|
@ -792,28 +808,44 @@ mod test {
|
|||
});
|
||||
|
||||
let doc_l0 = DocumentId::default();
|
||||
let mut view = View::new(doc_l0, GutterConfig::default());
|
||||
let mut view = View::new(
|
||||
doc_l0,
|
||||
GutterConfig::default(),
|
||||
GutterConfig::from(Vec::new()),
|
||||
);
|
||||
view.area = Rect::new(0, 0, 180, 80);
|
||||
tree.insert(view);
|
||||
|
||||
let l0 = tree.focus;
|
||||
|
||||
let doc_r0 = DocumentId::default();
|
||||
let view = View::new(doc_r0, GutterConfig::default());
|
||||
let view = View::new(
|
||||
doc_r0,
|
||||
GutterConfig::default(),
|
||||
GutterConfig::from(Vec::new()),
|
||||
);
|
||||
tree.split(view, Layout::Vertical);
|
||||
let r0 = tree.focus;
|
||||
|
||||
tree.focus = l0;
|
||||
|
||||
let doc_l1 = DocumentId::default();
|
||||
let view = View::new(doc_l1, GutterConfig::default());
|
||||
let view = View::new(
|
||||
doc_l1,
|
||||
GutterConfig::default(),
|
||||
GutterConfig::from(Vec::new()),
|
||||
);
|
||||
tree.split(view, Layout::Horizontal);
|
||||
let l1 = tree.focus;
|
||||
|
||||
tree.focus = l0;
|
||||
|
||||
let doc_l2 = DocumentId::default();
|
||||
let view = View::new(doc_l2, GutterConfig::default());
|
||||
let view = View::new(
|
||||
doc_l2,
|
||||
GutterConfig::default(),
|
||||
GutterConfig::from(Vec::new()),
|
||||
);
|
||||
tree.split(view, Layout::Vertical);
|
||||
let l2 = tree.focus;
|
||||
|
||||
|
@ -908,19 +940,35 @@ mod test {
|
|||
width: tree_area_width,
|
||||
height: 80,
|
||||
});
|
||||
let mut view = View::new(DocumentId::default(), GutterConfig::default());
|
||||
let mut view = View::new(
|
||||
DocumentId::default(),
|
||||
GutterConfig::default(),
|
||||
GutterConfig::from(Vec::new()),
|
||||
);
|
||||
view.area = Rect::new(0, 0, 180, 80);
|
||||
tree.insert(view);
|
||||
|
||||
let view = View::new(DocumentId::default(), GutterConfig::default());
|
||||
let view = View::new(
|
||||
DocumentId::default(),
|
||||
GutterConfig::default(),
|
||||
GutterConfig::from(Vec::new()),
|
||||
);
|
||||
tree.split(view, Layout::Vertical);
|
||||
|
||||
let view = View::new(DocumentId::default(), GutterConfig::default());
|
||||
let view = View::new(
|
||||
DocumentId::default(),
|
||||
GutterConfig::default(),
|
||||
GutterConfig::from(Vec::new()),
|
||||
);
|
||||
tree.split(view, Layout::Horizontal);
|
||||
|
||||
tree.remove(tree.focus);
|
||||
|
||||
let view = View::new(DocumentId::default(), GutterConfig::default());
|
||||
let view = View::new(
|
||||
DocumentId::default(),
|
||||
GutterConfig::default(),
|
||||
GutterConfig::from(Vec::new()),
|
||||
);
|
||||
tree.split(view, Layout::Vertical);
|
||||
|
||||
// Make sure that we only have one level in the tree.
|
||||
|
@ -946,12 +994,20 @@ mod test {
|
|||
width: tree_area_width,
|
||||
height: tree_area_height,
|
||||
});
|
||||
let mut view = View::new(DocumentId::default(), GutterConfig::default());
|
||||
let mut view = View::new(
|
||||
DocumentId::default(),
|
||||
GutterConfig::default(),
|
||||
GutterConfig::from(Vec::new()),
|
||||
);
|
||||
view.area = Rect::new(0, 0, tree_area_width, tree_area_height);
|
||||
tree.insert(view);
|
||||
|
||||
for _ in 0..9 {
|
||||
let view = View::new(DocumentId::default(), GutterConfig::default());
|
||||
let view = View::new(
|
||||
DocumentId::default(),
|
||||
GutterConfig::default(),
|
||||
GutterConfig::from(Vec::new()),
|
||||
);
|
||||
tree.split(view, Layout::Vertical);
|
||||
}
|
||||
|
||||
|
|
|
@ -142,6 +142,8 @@ pub struct View {
|
|||
pub object_selections: Vec<Selection>,
|
||||
/// all gutter-related configuration settings, used primarily for gutter rendering
|
||||
pub gutters: GutterConfig,
|
||||
/// all configuration settings related to the right-hand gutter
|
||||
pub gutters_right: GutterConfig,
|
||||
/// A mapping between documents and the last history revision the view was updated at.
|
||||
/// Changes between documents and views are synced lazily when switching windows. This
|
||||
/// mapping keeps track of the last applied history revision so that only new changes
|
||||
|
@ -168,7 +170,7 @@ impl fmt::Debug for View {
|
|||
}
|
||||
|
||||
impl View {
|
||||
pub fn new(doc: DocumentId, gutters: GutterConfig) -> Self {
|
||||
pub fn new(doc: DocumentId, gutters: GutterConfig, gutters_right: GutterConfig) -> Self {
|
||||
Self {
|
||||
id: ViewId::default(),
|
||||
doc,
|
||||
|
@ -178,6 +180,7 @@ impl View {
|
|||
last_modified_docs: [None, None],
|
||||
object_selections: Vec::new(),
|
||||
gutters,
|
||||
gutters_right,
|
||||
doc_revisions: HashMap::new(),
|
||||
diagnostics_handler: DiagnosticsHandler::new(),
|
||||
}
|
||||
|
@ -191,7 +194,10 @@ impl View {
|
|||
}
|
||||
|
||||
pub fn inner_area(&self, doc: &Document) -> Rect {
|
||||
self.area.clip_left(self.gutter_offset(doc)).clip_bottom(1) // -1 for statusline
|
||||
self.area
|
||||
.clip_left(self.gutter_offset(doc))
|
||||
.clip_right(self.gutter_offset_right(doc))
|
||||
.clip_bottom(1) // -1 for statusline
|
||||
}
|
||||
|
||||
pub fn inner_height(&self) -> usize {
|
||||
|
@ -199,13 +205,20 @@ impl View {
|
|||
}
|
||||
|
||||
pub fn inner_width(&self, doc: &Document) -> u16 {
|
||||
self.area.clip_left(self.gutter_offset(doc)).width
|
||||
self.area
|
||||
.clip_left(self.gutter_offset(doc))
|
||||
.clip_right(self.gutter_offset_right(doc))
|
||||
.width
|
||||
}
|
||||
|
||||
pub fn gutters(&self) -> &[GutterType] {
|
||||
&self.gutters.layout
|
||||
}
|
||||
|
||||
pub fn gutters_right(&self) -> &[GutterType] {
|
||||
&self.gutters_right.layout
|
||||
}
|
||||
|
||||
pub fn gutter_offset(&self, doc: &Document) -> u16 {
|
||||
let total_width = self
|
||||
.gutters
|
||||
|
@ -220,6 +233,20 @@ impl View {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn gutter_offset_right(&self, doc: &Document) -> u16 {
|
||||
let total_width = self
|
||||
.gutters_right
|
||||
.layout
|
||||
.iter()
|
||||
.map(|gutter| gutter.width(self, doc) as u16)
|
||||
.sum();
|
||||
if total_width < self.area.width {
|
||||
total_width
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
pub fn offset_coords_to_in_view(
|
||||
&self,
|
||||
|
@ -695,7 +722,11 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_text_pos_at_screen_coords() {
|
||||
let mut view = View::new(DocumentId::default(), GutterConfig::default());
|
||||
let mut view = View::new(
|
||||
DocumentId::default(),
|
||||
GutterConfig::default(),
|
||||
GutterConfig::from(Vec::new()),
|
||||
);
|
||||
view.area = Rect::new(40, 40, 40, 40);
|
||||
let rope = Rope::from_str("abc\n\tdef");
|
||||
let mut doc = Document::from(
|
||||
|
@ -870,6 +901,7 @@ mod tests {
|
|||
layout: vec![GutterType::Diagnostics],
|
||||
line_numbers: GutterLineNumbersConfig::default(),
|
||||
},
|
||||
GutterConfig::from(Vec::new()),
|
||||
);
|
||||
view.area = Rect::new(40, 40, 40, 40);
|
||||
let rope = Rope::from_str("abc\n\tdef");
|
||||
|
@ -900,6 +932,7 @@ mod tests {
|
|||
layout: vec![],
|
||||
line_numbers: GutterLineNumbersConfig::default(),
|
||||
},
|
||||
GutterConfig::from(Vec::new()),
|
||||
);
|
||||
view.area = Rect::new(40, 40, 40, 40);
|
||||
let rope = Rope::from_str("abc\n\tdef");
|
||||
|
@ -924,7 +957,11 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_text_pos_at_screen_coords_cjk() {
|
||||
let mut view = View::new(DocumentId::default(), GutterConfig::default());
|
||||
let mut view = View::new(
|
||||
DocumentId::default(),
|
||||
GutterConfig::default(),
|
||||
GutterConfig::from(Vec::new()),
|
||||
);
|
||||
view.area = Rect::new(40, 40, 40, 40);
|
||||
let rope = Rope::from_str("Hi! こんにちは皆さん");
|
||||
let mut doc = Document::from(
|
||||
|
@ -1008,7 +1045,11 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_text_pos_at_screen_coords_graphemes() {
|
||||
let mut view = View::new(DocumentId::default(), GutterConfig::default());
|
||||
let mut view = View::new(
|
||||
DocumentId::default(),
|
||||
GutterConfig::default(),
|
||||
GutterConfig::from(Vec::new()),
|
||||
);
|
||||
view.area = Rect::new(40, 40, 40, 40);
|
||||
let rope = Rope::from_str("Hèl̀l̀ò world!");
|
||||
let mut doc = Document::from(
|
||||
|
|
Loading…
Reference in New Issue