mirror of
https://github.com/helix-editor/helix.git
synced 2024-11-22 09:26:19 +04:00
Make gutters configurable (#1967)
* config option line numbers none * view tests * added tests * doc * comment * Make gutters configurable * docu * docu * rm none docu * order * order * precedence * simpler * rm todo * fixed clippy * order * double quotes * only allow diagnostics and line-numbers * tests * docu * format * rm short variant and more docu * performance improvements * typo * rename
This commit is contained in:
parent
450f348925
commit
b04c425c63
@ -37,6 +37,7 @@ ### `[editor]` Section
|
||||
| `scroll-lines` | Number of lines to scroll per scroll wheel step. | `3` |
|
||||
| `shell` | Shell to use when running external commands. | Unix: `["sh", "-c"]`<br/>Windows: `["cmd", "/C"]` |
|
||||
| `line-number` | Line number display: `absolute` simply shows each line's number, while `relative` shows the distance from the current line. When unfocused or in insert mode, `relative` will still show absolute line numbers. | `absolute` |
|
||||
| `gutters` | Gutters to display: Available are `diagnostics` and `line-numbers`, note that `diagnostics` also includes other features like breakpoints | `["diagnostics", "line-numbers"]` |
|
||||
| `auto-completion` | Enable automatic pop up of auto-completion. | `true` |
|
||||
| `idle-timeout` | Time in milliseconds since last keypress before idle timers trigger. Used for autocompletion, set to 0 for instant. | `400` |
|
||||
| `completion-trigger-len` | The min-length of word under cursor to trigger autocompletion | `2` |
|
||||
|
@ -466,7 +466,7 @@ pub fn render_gutter(
|
||||
// avoid lots of small allocations by reusing a text buffer for each line
|
||||
let mut text = String::with_capacity(8);
|
||||
|
||||
for (constructor, width) in view.gutters() {
|
||||
for (constructor, width) in &view.gutters {
|
||||
let gutter = constructor(editor, doc, view, theme, is_focused, *width);
|
||||
text.reserve(*width); // ensure there's enough space for the gutter
|
||||
for (i, line) in (view.offset.row..(last_line + 1)).enumerate() {
|
||||
|
@ -117,6 +117,8 @@ pub struct Config {
|
||||
pub shell: Vec<String>,
|
||||
/// Line number mode.
|
||||
pub line_number: LineNumber,
|
||||
/// Gutters. Default ["diagnostics", "line-numbers"]
|
||||
pub gutters: Vec<GutterType>,
|
||||
/// Middle click paste support. Defaults to true.
|
||||
pub middle_click_paste: bool,
|
||||
/// Automatic insertion of pairs to parentheses, brackets,
|
||||
@ -238,6 +240,27 @@ fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub enum GutterType {
|
||||
/// Show diagnostics and other features like breakpoints
|
||||
Diagnostics,
|
||||
/// Show line numbers
|
||||
LineNumbers,
|
||||
}
|
||||
|
||||
impl std::str::FromStr for GutterType {
|
||||
type Err = anyhow::Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s.to_lowercase().as_str() {
|
||||
"diagnostics" => Ok(Self::Diagnostics),
|
||||
"line-numbers" => Ok(Self::LineNumbers),
|
||||
_ => anyhow::bail!("Gutter type can only be `diagnostics` or `line-numbers`."),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
@ -250,6 +273,7 @@ fn default() -> Self {
|
||||
vec!["sh".to_owned(), "-c".to_owned()]
|
||||
},
|
||||
line_number: LineNumber::Absolute,
|
||||
gutters: vec![GutterType::Diagnostics, GutterType::LineNumbers],
|
||||
middle_click_paste: true,
|
||||
auto_pairs: AutoPairConfig::default(),
|
||||
auto_completion: true,
|
||||
@ -579,7 +603,7 @@ pub fn switch(&mut self, id: DocumentId, action: Action) {
|
||||
return;
|
||||
}
|
||||
Action::HorizontalSplit | Action::VerticalSplit => {
|
||||
let view = View::new(id);
|
||||
let view = View::new(id, self.config().gutters.clone());
|
||||
let view_id = self.tree.split(
|
||||
view,
|
||||
match action {
|
||||
@ -701,7 +725,7 @@ pub fn close_document(&mut self, doc_id: DocumentId, force: bool) -> anyhow::Res
|
||||
.map(|(&doc_id, _)| doc_id)
|
||||
.next()
|
||||
.unwrap_or_else(|| self.new_document(Document::default()));
|
||||
let view = View::new(doc_id);
|
||||
let view = View::new(doc_id, self.config().gutters.clone());
|
||||
let view_id = self.tree.insert(view);
|
||||
let doc = self.documents.get_mut(&doc_id).unwrap();
|
||||
doc.selections.insert(view_id, Selection::point(0));
|
||||
|
@ -39,7 +39,7 @@ pub fn diagnostic<'doc>(
|
||||
})
|
||||
}
|
||||
|
||||
pub fn line_number<'doc>(
|
||||
pub fn line_numbers<'doc>(
|
||||
editor: &'doc Editor,
|
||||
doc: &'doc Document,
|
||||
view: &View,
|
||||
|
@ -568,6 +568,7 @@ fn next(&mut self) -> Option<Self::Item> {
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::editor::GutterType;
|
||||
use crate::DocumentId;
|
||||
|
||||
#[test]
|
||||
@ -578,22 +579,34 @@ fn find_split_in_direction() {
|
||||
width: 180,
|
||||
height: 80,
|
||||
});
|
||||
let mut view = View::new(DocumentId::default());
|
||||
let mut view = View::new(
|
||||
DocumentId::default(),
|
||||
vec![GutterType::Diagnostics, GutterType::LineNumbers],
|
||||
);
|
||||
view.area = Rect::new(0, 0, 180, 80);
|
||||
tree.insert(view);
|
||||
|
||||
let l0 = tree.focus;
|
||||
let view = View::new(DocumentId::default());
|
||||
let view = View::new(
|
||||
DocumentId::default(),
|
||||
vec![GutterType::Diagnostics, GutterType::LineNumbers],
|
||||
);
|
||||
tree.split(view, Layout::Vertical);
|
||||
let r0 = tree.focus;
|
||||
|
||||
tree.focus = l0;
|
||||
let view = View::new(DocumentId::default());
|
||||
let view = View::new(
|
||||
DocumentId::default(),
|
||||
vec![GutterType::Diagnostics, GutterType::LineNumbers],
|
||||
);
|
||||
tree.split(view, Layout::Horizontal);
|
||||
let l1 = tree.focus;
|
||||
|
||||
tree.focus = l0;
|
||||
let view = View::new(DocumentId::default());
|
||||
let view = View::new(
|
||||
DocumentId::default(),
|
||||
vec![GutterType::Diagnostics, GutterType::LineNumbers],
|
||||
);
|
||||
tree.split(view, Layout::Vertical);
|
||||
let l2 = tree.focus;
|
||||
|
||||
|
@ -11,6 +11,8 @@
|
||||
visual_coords_at_pos, Position, RopeSlice, Selection,
|
||||
};
|
||||
|
||||
use std::fmt;
|
||||
|
||||
type Jump = (DocumentId, Selection);
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@ -64,17 +66,11 @@ pub fn remove(&mut self, doc_id: &DocumentId) {
|
||||
}
|
||||
}
|
||||
|
||||
const GUTTERS: &[(Gutter, usize)] = &[
|
||||
(gutter::diagnostics_or_breakpoints, 1),
|
||||
(gutter::line_number, 5),
|
||||
];
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct View {
|
||||
pub id: ViewId,
|
||||
pub doc: DocumentId,
|
||||
pub offset: Position,
|
||||
pub area: Rect,
|
||||
pub doc: DocumentId,
|
||||
pub jumps: JumpList,
|
||||
/// the last accessed file before the current one
|
||||
pub last_accessed_doc: Option<DocumentId>,
|
||||
@ -85,10 +81,29 @@ pub struct View {
|
||||
pub last_modified_docs: [Option<DocumentId>; 2],
|
||||
/// used to store previous selections of tree-sitter objecs
|
||||
pub object_selections: Vec<Selection>,
|
||||
pub gutters: Vec<(Gutter, usize)>,
|
||||
}
|
||||
|
||||
impl fmt::Debug for View {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("View")
|
||||
.field("id", &self.id)
|
||||
.field("area", &self.area)
|
||||
.field("doc", &self.doc)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl View {
|
||||
pub fn new(doc: DocumentId) -> Self {
|
||||
pub fn new(doc: DocumentId, gutter_types: Vec<crate::editor::GutterType>) -> Self {
|
||||
let mut gutters: Vec<(Gutter, usize)> = vec![];
|
||||
use crate::editor::GutterType;
|
||||
for gutter_type in &gutter_types {
|
||||
match gutter_type {
|
||||
GutterType::Diagnostics => gutters.push((gutter::diagnostics_or_breakpoints, 1)),
|
||||
GutterType::LineNumbers => gutters.push((gutter::line_numbers, 5)),
|
||||
}
|
||||
}
|
||||
Self {
|
||||
id: ViewId::default(),
|
||||
doc,
|
||||
@ -98,17 +113,14 @@ pub fn new(doc: DocumentId) -> Self {
|
||||
last_accessed_doc: None,
|
||||
last_modified_docs: [None, None],
|
||||
object_selections: Vec::new(),
|
||||
gutters,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn gutters(&self) -> &[(Gutter, usize)] {
|
||||
GUTTERS
|
||||
}
|
||||
|
||||
pub fn inner_area(&self) -> Rect {
|
||||
// TODO: cache this
|
||||
let offset = self
|
||||
.gutters()
|
||||
.gutters
|
||||
.iter()
|
||||
.map(|(_, width)| *width as u16)
|
||||
.sum::<u16>()
|
||||
@ -324,11 +336,16 @@ mod tests {
|
||||
use super::*;
|
||||
use helix_core::Rope;
|
||||
const OFFSET: u16 = 7; // 1 diagnostic + 5 linenr + 1 gutter
|
||||
// const OFFSET: u16 = GUTTERS.iter().map(|(_, width)| *width as u16).sum();
|
||||
const OFFSET_WITHOUT_LINE_NUMBERS: u16 = 2; // 1 diagnostic + 1 gutter
|
||||
// const OFFSET: u16 = GUTTERS.iter().map(|(_, width)| *width as u16).sum();
|
||||
use crate::editor::GutterType;
|
||||
|
||||
#[test]
|
||||
fn test_text_pos_at_screen_coords() {
|
||||
let mut view = View::new(DocumentId::default());
|
||||
let mut view = View::new(
|
||||
DocumentId::default(),
|
||||
vec![GutterType::Diagnostics, GutterType::LineNumbers],
|
||||
);
|
||||
view.area = Rect::new(40, 40, 40, 40);
|
||||
let rope = Rope::from_str("abc\n\tdef");
|
||||
let text = rope.slice(..);
|
||||
@ -372,9 +389,36 @@ fn test_text_pos_at_screen_coords() {
|
||||
assert_eq!(view.text_pos_at_screen_coords(&text, 41, 80, 4), Some(8));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_text_pos_at_screen_coords_without_line_numbers_gutter() {
|
||||
let mut view = View::new(DocumentId::default(), vec![GutterType::Diagnostics]);
|
||||
view.area = Rect::new(40, 40, 40, 40);
|
||||
let rope = Rope::from_str("abc\n\tdef");
|
||||
let text = rope.slice(..);
|
||||
assert_eq!(
|
||||
view.text_pos_at_screen_coords(&text, 41, 40 + OFFSET_WITHOUT_LINE_NUMBERS + 1, 4),
|
||||
Some(4)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_text_pos_at_screen_coords_without_any_gutters() {
|
||||
let mut view = View::new(DocumentId::default(), vec![]);
|
||||
view.area = Rect::new(40, 40, 40, 40);
|
||||
let rope = Rope::from_str("abc\n\tdef");
|
||||
let text = rope.slice(..);
|
||||
assert_eq!(
|
||||
view.text_pos_at_screen_coords(&text, 41, 40 + 1, 4),
|
||||
Some(4)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_text_pos_at_screen_coords_cjk() {
|
||||
let mut view = View::new(DocumentId::default());
|
||||
let mut view = View::new(
|
||||
DocumentId::default(),
|
||||
vec![GutterType::Diagnostics, GutterType::LineNumbers],
|
||||
);
|
||||
view.area = Rect::new(40, 40, 40, 40);
|
||||
let rope = Rope::from_str("Hi! こんにちは皆さん");
|
||||
let text = rope.slice(..);
|
||||
@ -411,7 +455,10 @@ fn test_text_pos_at_screen_coords_cjk() {
|
||||
|
||||
#[test]
|
||||
fn test_text_pos_at_screen_coords_graphemes() {
|
||||
let mut view = View::new(DocumentId::default());
|
||||
let mut view = View::new(
|
||||
DocumentId::default(),
|
||||
vec![GutterType::Diagnostics, GutterType::LineNumbers],
|
||||
);
|
||||
view.area = Rect::new(40, 40, 40, 40);
|
||||
let rope = Rope::from_str("Hèl̀l̀ò world!");
|
||||
let text = rope.slice(..);
|
||||
|
Loading…
Reference in New Issue
Block a user