mirror of
https://github.com/helix-editor/helix.git
synced 2024-11-25 02:46:17 +04:00
Add configuration for min width of line-numbers gutter (#4724)
This commit is contained in:
parent
8347139ff5
commit
2b58ff4d7c
@ -256,3 +256,55 @@ ### `[editor.indent-guides]` Section
|
|||||||
character = "╎" # Some characters that work well: "▏", "┆", "┊", "⸽"
|
character = "╎" # Some characters that work well: "▏", "┆", "┊", "⸽"
|
||||||
skip-levels = 1
|
skip-levels = 1
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### `[editor.gutters]` Section
|
||||||
|
|
||||||
|
For simplicity, `editor.gutters` accepts an array of gutter types, which will
|
||||||
|
use default settings for all gutter components.
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[editor]
|
||||||
|
gutters = ["diff", "diagnostics", "line-numbers", "spacer"]
|
||||||
|
```
|
||||||
|
|
||||||
|
To customize the behavior of gutters, the `[editor.gutters]` section must
|
||||||
|
be used. This section contains top level settings, as well as settings for
|
||||||
|
specific gutter components as sub-sections.
|
||||||
|
|
||||||
|
| Key | Description | Default |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `layout` | A vector of gutters to display | `["diagnostics", "spacer", "line-numbers", "spacer", "diff"]` |
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[editor.gutters]
|
||||||
|
layout = ["diff", "diagnostics", "line-numbers", "spacer"]
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `[editor.gutters.line-numbers]` Section
|
||||||
|
|
||||||
|
Options for the line number gutter
|
||||||
|
|
||||||
|
| Key | Description | Default |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `min-width` | The minimum number of characters to use | `3` |
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[editor.gutters.line-numbers]
|
||||||
|
min-width = 1
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `[editor.gutters.diagnotics]` Section
|
||||||
|
|
||||||
|
Currently unused
|
||||||
|
|
||||||
|
#### `[editor.gutters.diff]` Section
|
||||||
|
|
||||||
|
Currently unused
|
||||||
|
|
||||||
|
#### `[editor.gutters.spacer]` Section
|
||||||
|
|
||||||
|
Currently unused
|
@ -71,6 +71,96 @@ fn serialize_duration_millis<S>(duration: &Duration, serializer: S) -> Result<S:
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "kebab-case", default, deny_unknown_fields)]
|
||||||
|
pub struct GutterConfig {
|
||||||
|
/// Gutter Layout
|
||||||
|
pub layout: Vec<GutterType>,
|
||||||
|
/// Options specific to the "line-numbers" gutter
|
||||||
|
pub line_numbers: GutterLineNumbersConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for GutterConfig {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
layout: vec![
|
||||||
|
GutterType::Diagnostics,
|
||||||
|
GutterType::Spacer,
|
||||||
|
GutterType::LineNumbers,
|
||||||
|
GutterType::Spacer,
|
||||||
|
GutterType::Diff,
|
||||||
|
],
|
||||||
|
line_numbers: GutterLineNumbersConfig::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Vec<GutterType>> for GutterConfig {
|
||||||
|
fn from(x: Vec<GutterType>) -> Self {
|
||||||
|
GutterConfig {
|
||||||
|
layout: x,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deserialize_gutter_seq_or_struct<'de, D>(deserializer: D) -> Result<GutterConfig, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
struct GutterVisitor;
|
||||||
|
|
||||||
|
impl<'de> serde::de::Visitor<'de> for GutterVisitor {
|
||||||
|
type Value = GutterConfig;
|
||||||
|
|
||||||
|
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
write!(
|
||||||
|
formatter,
|
||||||
|
"an array of gutter names or a detailed gutter configuration"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_seq<S>(self, mut seq: S) -> Result<Self::Value, S::Error>
|
||||||
|
where
|
||||||
|
S: serde::de::SeqAccess<'de>,
|
||||||
|
{
|
||||||
|
let mut gutters = Vec::new();
|
||||||
|
while let Some(gutter) = seq.next_element::<&str>()? {
|
||||||
|
gutters.push(
|
||||||
|
gutter
|
||||||
|
.parse::<GutterType>()
|
||||||
|
.map_err(serde::de::Error::custom)?,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(gutters.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_map<M>(self, map: M) -> Result<Self::Value, M::Error>
|
||||||
|
where
|
||||||
|
M: serde::de::MapAccess<'de>,
|
||||||
|
{
|
||||||
|
let deserializer = serde::de::value::MapAccessDeserializer::new(map);
|
||||||
|
Deserialize::deserialize(deserializer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deserializer.deserialize_any(GutterVisitor)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "kebab-case", default, deny_unknown_fields)]
|
||||||
|
pub struct GutterLineNumbersConfig {
|
||||||
|
/// Minimum number of characters to use for line number gutter. Defaults to 3.
|
||||||
|
pub min_width: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for GutterLineNumbersConfig {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self { min_width: 3 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "kebab-case", default, deny_unknown_fields)]
|
#[serde(rename_all = "kebab-case", default, deny_unknown_fields)]
|
||||||
pub struct FilePickerConfig {
|
pub struct FilePickerConfig {
|
||||||
@ -132,8 +222,8 @@ pub struct Config {
|
|||||||
pub cursorline: bool,
|
pub cursorline: bool,
|
||||||
/// Highlight the columns cursors are currently on. Defaults to false.
|
/// Highlight the columns cursors are currently on. Defaults to false.
|
||||||
pub cursorcolumn: bool,
|
pub cursorcolumn: bool,
|
||||||
/// Gutters. Default ["diagnostics", "line-numbers"]
|
#[serde(deserialize_with = "deserialize_gutter_seq_or_struct")]
|
||||||
pub gutters: Vec<GutterType>,
|
pub gutters: GutterConfig,
|
||||||
/// Middle click paste support. Defaults to true.
|
/// Middle click paste support. Defaults to true.
|
||||||
pub middle_click_paste: bool,
|
pub middle_click_paste: bool,
|
||||||
/// Automatic insertion of pairs to parentheses, brackets,
|
/// Automatic insertion of pairs to parentheses, brackets,
|
||||||
@ -606,13 +696,7 @@ fn default() -> Self {
|
|||||||
line_number: LineNumber::Absolute,
|
line_number: LineNumber::Absolute,
|
||||||
cursorline: false,
|
cursorline: false,
|
||||||
cursorcolumn: false,
|
cursorcolumn: false,
|
||||||
gutters: vec![
|
gutters: GutterConfig::default(),
|
||||||
GutterType::Diagnostics,
|
|
||||||
GutterType::Spacer,
|
|
||||||
GutterType::LineNumbers,
|
|
||||||
GutterType::Spacer,
|
|
||||||
GutterType::Diff,
|
|
||||||
],
|
|
||||||
middle_click_paste: true,
|
middle_click_paste: true,
|
||||||
auto_pairs: AutoPairConfig::default(),
|
auto_pairs: AutoPairConfig::default(),
|
||||||
auto_completion: true,
|
auto_completion: true,
|
||||||
@ -844,6 +928,7 @@ pub fn refresh_config(&mut self) {
|
|||||||
let config = self.config();
|
let config = self.config();
|
||||||
self.auto_pairs = (&config.auto_pairs).into();
|
self.auto_pairs = (&config.auto_pairs).into();
|
||||||
self.reset_idle_timer();
|
self.reset_idle_timer();
|
||||||
|
self._refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clear_idle_timer(&mut self) {
|
pub fn clear_idle_timer(&mut self) {
|
||||||
@ -984,6 +1069,7 @@ fn _refresh(&mut self) {
|
|||||||
for (view, _) in self.tree.views_mut() {
|
for (view, _) in self.tree.views_mut() {
|
||||||
let doc = doc_mut!(self, &view.doc);
|
let doc = doc_mut!(self, &view.doc);
|
||||||
view.sync_changes(doc);
|
view.sync_changes(doc);
|
||||||
|
view.gutters = config.gutters.clone();
|
||||||
view.ensure_cursor_in_view(doc, config.scrolloff)
|
view.ensure_cursor_in_view(doc, config.scrolloff)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,10 +35,10 @@ pub fn style<'doc>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn width(self, _view: &View, doc: &Document) -> usize {
|
pub fn width(self, view: &View, doc: &Document) -> usize {
|
||||||
match self {
|
match self {
|
||||||
GutterType::Diagnostics => 1,
|
GutterType::Diagnostics => 1,
|
||||||
GutterType::LineNumbers => line_numbers_width(_view, doc),
|
GutterType::LineNumbers => line_numbers_width(view, doc),
|
||||||
GutterType::Spacer => 1,
|
GutterType::Spacer => 1,
|
||||||
GutterType::Diff => 1,
|
GutterType::Diff => 1,
|
||||||
}
|
}
|
||||||
@ -140,12 +140,13 @@ pub fn line_numbers<'doc>(
|
|||||||
is_focused: bool,
|
is_focused: bool,
|
||||||
) -> GutterFn<'doc> {
|
) -> GutterFn<'doc> {
|
||||||
let text = doc.text().slice(..);
|
let text = doc.text().slice(..);
|
||||||
let last_line = view.last_line(doc);
|
let width = line_numbers_width(view, doc);
|
||||||
let width = GutterType::LineNumbers.width(view, doc);
|
|
||||||
|
let last_line_in_view = view.last_line(doc);
|
||||||
|
|
||||||
// Whether to draw the line number for the last line of the
|
// 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.
|
// 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 draw_last = text.line_to_byte(last_line_in_view) < text.len_bytes();
|
||||||
|
|
||||||
let linenr = theme.get("ui.linenr");
|
let linenr = theme.get("ui.linenr");
|
||||||
let linenr_select = theme.get("ui.linenr.selected");
|
let linenr_select = theme.get("ui.linenr.selected");
|
||||||
@ -158,7 +159,7 @@ pub fn line_numbers<'doc>(
|
|||||||
let mode = editor.mode;
|
let mode = editor.mode;
|
||||||
|
|
||||||
Box::new(move |line: usize, selected: bool, out: &mut String| {
|
Box::new(move |line: usize, selected: bool, out: &mut String| {
|
||||||
if line == last_line && !draw_last {
|
if line == last_line_in_view && !draw_last {
|
||||||
write!(out, "{:>1$}", '~', width).unwrap();
|
write!(out, "{:>1$}", '~', width).unwrap();
|
||||||
Some(linenr)
|
Some(linenr)
|
||||||
} else {
|
} else {
|
||||||
@ -187,14 +188,19 @@ pub fn line_numbers<'doc>(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn line_numbers_width(_view: &View, doc: &Document) -> usize {
|
/// The width of a "line-numbers" gutter
|
||||||
|
///
|
||||||
|
/// The width of the gutter depends on the number of lines in the document,
|
||||||
|
/// whether there is content on the last line (the `~` line), and the
|
||||||
|
/// `editor.gutters.line-numbers.min-width` settings.
|
||||||
|
fn line_numbers_width(view: &View, doc: &Document) -> usize {
|
||||||
let text = doc.text();
|
let text = doc.text();
|
||||||
let last_line = text.len_lines().saturating_sub(1);
|
let last_line = text.len_lines().saturating_sub(1);
|
||||||
let draw_last = text.line_to_byte(last_line) < text.len_bytes();
|
let draw_last = text.line_to_byte(last_line) < text.len_bytes();
|
||||||
let last_drawn = if draw_last { last_line + 1 } else { last_line };
|
let last_drawn = if draw_last { last_line + 1 } else { last_line };
|
||||||
|
let digits = count_digits(last_drawn);
|
||||||
// set a lower bound to 2-chars to minimize ambiguous relative line numbers
|
let n_min = view.gutters.line_numbers.min_width;
|
||||||
std::cmp::max(count_digits(last_drawn), 2)
|
digits.max(n_min)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn padding<'doc>(
|
pub fn padding<'doc>(
|
||||||
@ -282,3 +288,82 @@ pub fn diagnostics_or_breakpoints<'doc>(
|
|||||||
breakpoints(line, selected, out).or_else(|| diagnostics(line, selected, out))
|
breakpoints(line, selected, out).or_else(|| diagnostics(line, selected, out))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::document::Document;
|
||||||
|
use crate::editor::{GutterConfig, GutterLineNumbersConfig};
|
||||||
|
use crate::graphics::Rect;
|
||||||
|
use crate::DocumentId;
|
||||||
|
use helix_core::Rope;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_default_gutter_widths() {
|
||||||
|
let mut view = View::new(DocumentId::default(), GutterConfig::default());
|
||||||
|
view.area = Rect::new(40, 40, 40, 40);
|
||||||
|
|
||||||
|
let rope = Rope::from_str("abc\n\tdef");
|
||||||
|
let doc = Document::from(rope, None);
|
||||||
|
|
||||||
|
assert_eq!(view.gutters.layout.len(), 5);
|
||||||
|
assert_eq!(view.gutters.layout[0].width(&view, &doc), 1);
|
||||||
|
assert_eq!(view.gutters.layout[1].width(&view, &doc), 1);
|
||||||
|
assert_eq!(view.gutters.layout[2].width(&view, &doc), 3);
|
||||||
|
assert_eq!(view.gutters.layout[3].width(&view, &doc), 1);
|
||||||
|
assert_eq!(view.gutters.layout[4].width(&view, &doc), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_configured_gutter_widths() {
|
||||||
|
let gutters = GutterConfig {
|
||||||
|
layout: vec![GutterType::Diagnostics],
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut view = View::new(DocumentId::default(), gutters);
|
||||||
|
view.area = Rect::new(40, 40, 40, 40);
|
||||||
|
|
||||||
|
let rope = Rope::from_str("abc\n\tdef");
|
||||||
|
let doc = Document::from(rope, None);
|
||||||
|
|
||||||
|
assert_eq!(view.gutters.layout.len(), 1);
|
||||||
|
assert_eq!(view.gutters.layout[0].width(&view, &doc), 1);
|
||||||
|
|
||||||
|
let gutters = GutterConfig {
|
||||||
|
layout: vec![GutterType::Diagnostics, GutterType::LineNumbers],
|
||||||
|
line_numbers: GutterLineNumbersConfig { min_width: 10 },
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut view = View::new(DocumentId::default(), gutters);
|
||||||
|
view.area = Rect::new(40, 40, 40, 40);
|
||||||
|
|
||||||
|
let rope = Rope::from_str("abc\n\tdef");
|
||||||
|
let doc = Document::from(rope, None);
|
||||||
|
|
||||||
|
assert_eq!(view.gutters.layout.len(), 2);
|
||||||
|
assert_eq!(view.gutters.layout[0].width(&view, &doc), 1);
|
||||||
|
assert_eq!(view.gutters.layout[1].width(&view, &doc), 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_line_numbers_gutter_width_resizes() {
|
||||||
|
let gutters = GutterConfig {
|
||||||
|
layout: vec![GutterType::Diagnostics, GutterType::LineNumbers],
|
||||||
|
line_numbers: GutterLineNumbersConfig { min_width: 1 },
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut view = View::new(DocumentId::default(), gutters);
|
||||||
|
view.area = Rect::new(40, 40, 40, 40);
|
||||||
|
|
||||||
|
let rope = Rope::from_str("a\nb");
|
||||||
|
let doc_short = Document::from(rope, None);
|
||||||
|
|
||||||
|
let rope = Rope::from_str("a\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk\nl\nm\nn\no\np");
|
||||||
|
let doc_long = Document::from(rope, None);
|
||||||
|
|
||||||
|
assert_eq!(view.gutters.layout.len(), 2);
|
||||||
|
assert_eq!(view.gutters.layout[1].width(&view, &doc_short), 1);
|
||||||
|
assert_eq!(view.gutters.layout[1].width(&view, &doc_long), 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -701,7 +701,7 @@ fn next_back(&mut self) -> Option<Self::Item> {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::editor::GutterType;
|
use crate::editor::GutterConfig;
|
||||||
use crate::DocumentId;
|
use crate::DocumentId;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -712,34 +712,22 @@ fn find_split_in_direction() {
|
|||||||
width: 180,
|
width: 180,
|
||||||
height: 80,
|
height: 80,
|
||||||
});
|
});
|
||||||
let mut view = View::new(
|
let mut view = View::new(DocumentId::default(), GutterConfig::default());
|
||||||
DocumentId::default(),
|
|
||||||
vec![GutterType::Diagnostics, GutterType::LineNumbers],
|
|
||||||
);
|
|
||||||
view.area = Rect::new(0, 0, 180, 80);
|
view.area = Rect::new(0, 0, 180, 80);
|
||||||
tree.insert(view);
|
tree.insert(view);
|
||||||
|
|
||||||
let l0 = tree.focus;
|
let l0 = tree.focus;
|
||||||
let view = View::new(
|
let view = View::new(DocumentId::default(), GutterConfig::default());
|
||||||
DocumentId::default(),
|
|
||||||
vec![GutterType::Diagnostics, GutterType::LineNumbers],
|
|
||||||
);
|
|
||||||
tree.split(view, Layout::Vertical);
|
tree.split(view, Layout::Vertical);
|
||||||
let r0 = tree.focus;
|
let r0 = tree.focus;
|
||||||
|
|
||||||
tree.focus = l0;
|
tree.focus = l0;
|
||||||
let view = View::new(
|
let view = View::new(DocumentId::default(), GutterConfig::default());
|
||||||
DocumentId::default(),
|
|
||||||
vec![GutterType::Diagnostics, GutterType::LineNumbers],
|
|
||||||
);
|
|
||||||
tree.split(view, Layout::Horizontal);
|
tree.split(view, Layout::Horizontal);
|
||||||
let l1 = tree.focus;
|
let l1 = tree.focus;
|
||||||
|
|
||||||
tree.focus = l0;
|
tree.focus = l0;
|
||||||
let view = View::new(
|
let view = View::new(DocumentId::default(), GutterConfig::default());
|
||||||
DocumentId::default(),
|
|
||||||
vec![GutterType::Diagnostics, GutterType::LineNumbers],
|
|
||||||
);
|
|
||||||
tree.split(view, Layout::Vertical);
|
tree.split(view, Layout::Vertical);
|
||||||
let l2 = tree.focus;
|
let l2 = tree.focus;
|
||||||
|
|
||||||
@ -781,40 +769,28 @@ fn swap_split_in_direction() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
let doc_l0 = DocumentId::default();
|
let doc_l0 = DocumentId::default();
|
||||||
let mut view = View::new(
|
let mut view = View::new(doc_l0, GutterConfig::default());
|
||||||
doc_l0,
|
|
||||||
vec![GutterType::Diagnostics, GutterType::LineNumbers],
|
|
||||||
);
|
|
||||||
view.area = Rect::new(0, 0, 180, 80);
|
view.area = Rect::new(0, 0, 180, 80);
|
||||||
tree.insert(view);
|
tree.insert(view);
|
||||||
|
|
||||||
let l0 = tree.focus;
|
let l0 = tree.focus;
|
||||||
|
|
||||||
let doc_r0 = DocumentId::default();
|
let doc_r0 = DocumentId::default();
|
||||||
let view = View::new(
|
let view = View::new(doc_r0, GutterConfig::default());
|
||||||
doc_r0,
|
|
||||||
vec![GutterType::Diagnostics, GutterType::LineNumbers],
|
|
||||||
);
|
|
||||||
tree.split(view, Layout::Vertical);
|
tree.split(view, Layout::Vertical);
|
||||||
let r0 = tree.focus;
|
let r0 = tree.focus;
|
||||||
|
|
||||||
tree.focus = l0;
|
tree.focus = l0;
|
||||||
|
|
||||||
let doc_l1 = DocumentId::default();
|
let doc_l1 = DocumentId::default();
|
||||||
let view = View::new(
|
let view = View::new(doc_l1, GutterConfig::default());
|
||||||
doc_l1,
|
|
||||||
vec![GutterType::Diagnostics, GutterType::LineNumbers],
|
|
||||||
);
|
|
||||||
tree.split(view, Layout::Horizontal);
|
tree.split(view, Layout::Horizontal);
|
||||||
let l1 = tree.focus;
|
let l1 = tree.focus;
|
||||||
|
|
||||||
tree.focus = l0;
|
tree.focus = l0;
|
||||||
|
|
||||||
let doc_l2 = DocumentId::default();
|
let doc_l2 = DocumentId::default();
|
||||||
let view = View::new(
|
let view = View::new(doc_l2, GutterConfig::default());
|
||||||
doc_l2,
|
|
||||||
vec![GutterType::Diagnostics, GutterType::LineNumbers],
|
|
||||||
);
|
|
||||||
tree.split(view, Layout::Vertical);
|
tree.split(view, Layout::Vertical);
|
||||||
let l2 = tree.focus;
|
let l2 = tree.focus;
|
||||||
|
|
||||||
|
@ -1,4 +1,10 @@
|
|||||||
use crate::{align_view, editor::GutterType, graphics::Rect, Align, Document, DocumentId, ViewId};
|
use crate::{
|
||||||
|
align_view,
|
||||||
|
editor::{GutterConfig, GutterType},
|
||||||
|
graphics::Rect,
|
||||||
|
Align, Document, DocumentId, ViewId,
|
||||||
|
};
|
||||||
|
|
||||||
use helix_core::{
|
use helix_core::{
|
||||||
pos_at_visual_coords, visual_coords_at_pos, Position, RopeSlice, Selection, Transaction,
|
pos_at_visual_coords, visual_coords_at_pos, Position, RopeSlice, Selection, Transaction,
|
||||||
};
|
};
|
||||||
@ -103,8 +109,8 @@ pub struct View {
|
|||||||
pub last_modified_docs: [Option<DocumentId>; 2],
|
pub last_modified_docs: [Option<DocumentId>; 2],
|
||||||
/// used to store previous selections of tree-sitter objects
|
/// used to store previous selections of tree-sitter objects
|
||||||
pub object_selections: Vec<Selection>,
|
pub object_selections: Vec<Selection>,
|
||||||
/// GutterTypes used to fetch Gutter (constructor) and width for rendering
|
/// all gutter-related configuration settings, used primarily for gutter rendering
|
||||||
gutters: Vec<GutterType>,
|
pub gutters: GutterConfig,
|
||||||
/// A mapping between documents and the last history revision the view was updated at.
|
/// 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
|
/// 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
|
/// mapping keeps track of the last applied history revision so that only new changes
|
||||||
@ -123,7 +129,7 @@ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl View {
|
impl View {
|
||||||
pub fn new(doc: DocumentId, gutter_types: Vec<crate::editor::GutterType>) -> Self {
|
pub fn new(doc: DocumentId, gutters: GutterConfig) -> Self {
|
||||||
Self {
|
Self {
|
||||||
id: ViewId::default(),
|
id: ViewId::default(),
|
||||||
doc,
|
doc,
|
||||||
@ -133,7 +139,7 @@ pub fn new(doc: DocumentId, gutter_types: Vec<crate::editor::GutterType>) -> Sel
|
|||||||
docs_access_history: Vec::new(),
|
docs_access_history: Vec::new(),
|
||||||
last_modified_docs: [None, None],
|
last_modified_docs: [None, None],
|
||||||
object_selections: Vec::new(),
|
object_selections: Vec::new(),
|
||||||
gutters: gutter_types,
|
gutters,
|
||||||
doc_revisions: HashMap::new(),
|
doc_revisions: HashMap::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -154,11 +160,12 @@ pub fn inner_height(&self) -> usize {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn gutters(&self) -> &[GutterType] {
|
pub fn gutters(&self) -> &[GutterType] {
|
||||||
&self.gutters
|
&self.gutters.layout
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn gutter_offset(&self, doc: &Document) -> u16 {
|
pub fn gutter_offset(&self, doc: &Document) -> u16 {
|
||||||
self.gutters
|
self.gutters
|
||||||
|
.layout
|
||||||
.iter()
|
.iter()
|
||||||
.map(|gutter| gutter.width(self, doc) as u16)
|
.map(|gutter| gutter.width(self, doc) as u16)
|
||||||
.sum()
|
.sum()
|
||||||
@ -414,18 +421,19 @@ pub fn sync_changes(&mut self, doc: &mut Document) {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use helix_core::Rope;
|
use helix_core::Rope;
|
||||||
const OFFSET: u16 = 3; // 1 diagnostic + 2 linenr (< 100 lines)
|
|
||||||
const OFFSET_WITHOUT_LINE_NUMBERS: u16 = 1; // 1 diagnostic
|
// 1 diagnostic + 1 spacer + 3 linenr (< 1000 lines) + 1 spacer + 1 diff
|
||||||
// const OFFSET: u16 = GUTTERS.iter().map(|(_, width)| *width as u16).sum();
|
const DEFAULT_GUTTER_OFFSET: u16 = 7;
|
||||||
|
|
||||||
|
// 1 diagnostics + 1 spacer + 1 gutter
|
||||||
|
const DEFAULT_GUTTER_OFFSET_ONLY_DIAGNOSTICS: u16 = 3;
|
||||||
|
|
||||||
use crate::document::Document;
|
use crate::document::Document;
|
||||||
use crate::editor::GutterType;
|
use crate::editor::{GutterConfig, GutterLineNumbersConfig, GutterType};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_text_pos_at_screen_coords() {
|
fn test_text_pos_at_screen_coords() {
|
||||||
let mut view = View::new(
|
let mut view = View::new(DocumentId::default(), GutterConfig::default());
|
||||||
DocumentId::default(),
|
|
||||||
vec![GutterType::Diagnostics, GutterType::LineNumbers],
|
|
||||||
);
|
|
||||||
view.area = Rect::new(40, 40, 40, 40);
|
view.area = Rect::new(40, 40, 40, 40);
|
||||||
let rope = Rope::from_str("abc\n\tdef");
|
let rope = Rope::from_str("abc\n\tdef");
|
||||||
let doc = Document::from(rope, None);
|
let doc = Document::from(rope, None);
|
||||||
@ -445,24 +453,24 @@ fn test_text_pos_at_screen_coords() {
|
|||||||
assert_eq!(view.text_pos_at_screen_coords(&doc, 78, 41, 4), None);
|
assert_eq!(view.text_pos_at_screen_coords(&doc, 78, 41, 4), None);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
view.text_pos_at_screen_coords(&doc, 40, 40 + OFFSET + 3, 4),
|
view.text_pos_at_screen_coords(&doc, 40, 40 + DEFAULT_GUTTER_OFFSET + 3, 4),
|
||||||
Some(3)
|
Some(3)
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(view.text_pos_at_screen_coords(&doc, 40, 80, 4), Some(3));
|
assert_eq!(view.text_pos_at_screen_coords(&doc, 40, 80, 4), Some(3));
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
view.text_pos_at_screen_coords(&doc, 41, 40 + OFFSET + 1, 4),
|
view.text_pos_at_screen_coords(&doc, 41, 40 + DEFAULT_GUTTER_OFFSET + 1, 4),
|
||||||
Some(4)
|
Some(4)
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
view.text_pos_at_screen_coords(&doc, 41, 40 + OFFSET + 4, 4),
|
view.text_pos_at_screen_coords(&doc, 41, 40 + DEFAULT_GUTTER_OFFSET + 4, 4),
|
||||||
Some(5)
|
Some(5)
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
view.text_pos_at_screen_coords(&doc, 41, 40 + OFFSET + 7, 4),
|
view.text_pos_at_screen_coords(&doc, 41, 40 + DEFAULT_GUTTER_OFFSET + 7, 4),
|
||||||
Some(8)
|
Some(8)
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -471,19 +479,36 @@ fn test_text_pos_at_screen_coords() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_text_pos_at_screen_coords_without_line_numbers_gutter() {
|
fn test_text_pos_at_screen_coords_without_line_numbers_gutter() {
|
||||||
let mut view = View::new(DocumentId::default(), vec![GutterType::Diagnostics]);
|
let mut view = View::new(
|
||||||
|
DocumentId::default(),
|
||||||
|
GutterConfig {
|
||||||
|
layout: vec![GutterType::Diagnostics],
|
||||||
|
line_numbers: GutterLineNumbersConfig::default(),
|
||||||
|
},
|
||||||
|
);
|
||||||
view.area = Rect::new(40, 40, 40, 40);
|
view.area = Rect::new(40, 40, 40, 40);
|
||||||
let rope = Rope::from_str("abc\n\tdef");
|
let rope = Rope::from_str("abc\n\tdef");
|
||||||
let doc = Document::from(rope, None);
|
let doc = Document::from(rope, None);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
view.text_pos_at_screen_coords(&doc, 41, 40 + OFFSET_WITHOUT_LINE_NUMBERS + 1, 4),
|
view.text_pos_at_screen_coords(
|
||||||
|
&doc,
|
||||||
|
41,
|
||||||
|
40 + DEFAULT_GUTTER_OFFSET_ONLY_DIAGNOSTICS + 1,
|
||||||
|
4
|
||||||
|
),
|
||||||
Some(4)
|
Some(4)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_text_pos_at_screen_coords_without_any_gutters() {
|
fn test_text_pos_at_screen_coords_without_any_gutters() {
|
||||||
let mut view = View::new(DocumentId::default(), vec![]);
|
let mut view = View::new(
|
||||||
|
DocumentId::default(),
|
||||||
|
GutterConfig {
|
||||||
|
layout: vec![],
|
||||||
|
line_numbers: GutterLineNumbersConfig::default(),
|
||||||
|
},
|
||||||
|
);
|
||||||
view.area = Rect::new(40, 40, 40, 40);
|
view.area = Rect::new(40, 40, 40, 40);
|
||||||
let rope = Rope::from_str("abc\n\tdef");
|
let rope = Rope::from_str("abc\n\tdef");
|
||||||
let doc = Document::from(rope, None);
|
let doc = Document::from(rope, None);
|
||||||
@ -492,76 +517,70 @@ fn test_text_pos_at_screen_coords_without_any_gutters() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_text_pos_at_screen_coords_cjk() {
|
fn test_text_pos_at_screen_coords_cjk() {
|
||||||
let mut view = View::new(
|
let mut view = View::new(DocumentId::default(), GutterConfig::default());
|
||||||
DocumentId::default(),
|
|
||||||
vec![GutterType::Diagnostics, GutterType::LineNumbers],
|
|
||||||
);
|
|
||||||
view.area = Rect::new(40, 40, 40, 40);
|
view.area = Rect::new(40, 40, 40, 40);
|
||||||
let rope = Rope::from_str("Hi! こんにちは皆さん");
|
let rope = Rope::from_str("Hi! こんにちは皆さん");
|
||||||
let doc = Document::from(rope, None);
|
let doc = Document::from(rope, None);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
view.text_pos_at_screen_coords(&doc, 40, 40 + OFFSET, 4),
|
view.text_pos_at_screen_coords(&doc, 40, 40 + DEFAULT_GUTTER_OFFSET, 4),
|
||||||
Some(0)
|
Some(0)
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
view.text_pos_at_screen_coords(&doc, 40, 40 + OFFSET + 4, 4),
|
view.text_pos_at_screen_coords(&doc, 40, 40 + DEFAULT_GUTTER_OFFSET + 4, 4),
|
||||||
Some(4)
|
Some(4)
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
view.text_pos_at_screen_coords(&doc, 40, 40 + OFFSET + 5, 4),
|
view.text_pos_at_screen_coords(&doc, 40, 40 + DEFAULT_GUTTER_OFFSET + 5, 4),
|
||||||
Some(4)
|
Some(4)
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
view.text_pos_at_screen_coords(&doc, 40, 40 + OFFSET + 6, 4),
|
view.text_pos_at_screen_coords(&doc, 40, 40 + DEFAULT_GUTTER_OFFSET + 6, 4),
|
||||||
Some(5)
|
Some(5)
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
view.text_pos_at_screen_coords(&doc, 40, 40 + OFFSET + 7, 4),
|
view.text_pos_at_screen_coords(&doc, 40, 40 + DEFAULT_GUTTER_OFFSET + 7, 4),
|
||||||
Some(5)
|
Some(5)
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
view.text_pos_at_screen_coords(&doc, 40, 40 + OFFSET + 8, 4),
|
view.text_pos_at_screen_coords(&doc, 40, 40 + DEFAULT_GUTTER_OFFSET + 8, 4),
|
||||||
Some(6)
|
Some(6)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_text_pos_at_screen_coords_graphemes() {
|
fn test_text_pos_at_screen_coords_graphemes() {
|
||||||
let mut view = View::new(
|
let mut view = View::new(DocumentId::default(), GutterConfig::default());
|
||||||
DocumentId::default(),
|
|
||||||
vec![GutterType::Diagnostics, GutterType::LineNumbers],
|
|
||||||
);
|
|
||||||
view.area = Rect::new(40, 40, 40, 40);
|
view.area = Rect::new(40, 40, 40, 40);
|
||||||
let rope = Rope::from_str("Hèl̀l̀ò world!");
|
let rope = Rope::from_str("Hèl̀l̀ò world!");
|
||||||
let doc = Document::from(rope, None);
|
let doc = Document::from(rope, None);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
view.text_pos_at_screen_coords(&doc, 40, 40 + OFFSET, 4),
|
view.text_pos_at_screen_coords(&doc, 40, 40 + DEFAULT_GUTTER_OFFSET, 4),
|
||||||
Some(0)
|
Some(0)
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
view.text_pos_at_screen_coords(&doc, 40, 40 + OFFSET + 1, 4),
|
view.text_pos_at_screen_coords(&doc, 40, 40 + DEFAULT_GUTTER_OFFSET + 1, 4),
|
||||||
Some(1)
|
Some(1)
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
view.text_pos_at_screen_coords(&doc, 40, 40 + OFFSET + 2, 4),
|
view.text_pos_at_screen_coords(&doc, 40, 40 + DEFAULT_GUTTER_OFFSET + 2, 4),
|
||||||
Some(3)
|
Some(3)
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
view.text_pos_at_screen_coords(&doc, 40, 40 + OFFSET + 3, 4),
|
view.text_pos_at_screen_coords(&doc, 40, 40 + DEFAULT_GUTTER_OFFSET + 3, 4),
|
||||||
Some(5)
|
Some(5)
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
view.text_pos_at_screen_coords(&doc, 40, 40 + OFFSET + 4, 4),
|
view.text_pos_at_screen_coords(&doc, 40, 40 + DEFAULT_GUTTER_OFFSET + 4, 4),
|
||||||
Some(7)
|
Some(7)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user