mirror of
https://github.com/helix-editor/helix.git
synced 2025-01-19 13:37:06 +04:00
Allow last line in file to lack a line break character.
This commit is contained in:
parent
230248bbc3
commit
22dca3b111
@ -65,7 +65,7 @@ pub fn move_vertically(
|
|||||||
Direction::Backward => row.saturating_sub(count),
|
Direction::Backward => row.saturating_sub(count),
|
||||||
Direction::Forward => std::cmp::min(
|
Direction::Forward => std::cmp::min(
|
||||||
row.saturating_add(count),
|
row.saturating_add(count),
|
||||||
slice.len_lines().saturating_sub(2),
|
slice.len_lines().saturating_sub(1),
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -402,12 +402,13 @@ fn vertical_moves_in_single_column() {
|
|||||||
let moves_and_expected_coordinates = IntoIter::new([
|
let moves_and_expected_coordinates = IntoIter::new([
|
||||||
((Direction::Forward, 1usize), (1, 0)),
|
((Direction::Forward, 1usize), (1, 0)),
|
||||||
((Direction::Forward, 2usize), (3, 0)),
|
((Direction::Forward, 2usize), (3, 0)),
|
||||||
|
((Direction::Forward, 1usize), (4, 0)),
|
||||||
((Direction::Backward, 999usize), (0, 0)),
|
((Direction::Backward, 999usize), (0, 0)),
|
||||||
((Direction::Forward, 3usize), (3, 0)),
|
((Direction::Forward, 4usize), (4, 0)),
|
||||||
((Direction::Forward, 0usize), (3, 0)),
|
((Direction::Forward, 0usize), (4, 0)),
|
||||||
((Direction::Backward, 0usize), (3, 0)),
|
((Direction::Backward, 0usize), (4, 0)),
|
||||||
((Direction::Forward, 5), (4, 0)),
|
((Direction::Forward, 5), (5, 0)),
|
||||||
((Direction::Forward, 999usize), (4, 0)),
|
((Direction::Forward, 999usize), (5, 0)),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
for ((direction, amount), coordinates) in moves_and_expected_coordinates {
|
for ((direction, amount), coordinates) in moves_and_expected_coordinates {
|
||||||
@ -439,7 +440,8 @@ enum Axis {
|
|||||||
((Axis::V, Direction::Forward, 1usize), (3, 8)),
|
((Axis::V, Direction::Forward, 1usize), (3, 8)),
|
||||||
// Behaviour is preserved even through long jumps
|
// Behaviour is preserved even through long jumps
|
||||||
((Axis::V, Direction::Backward, 999usize), (0, 8)),
|
((Axis::V, Direction::Backward, 999usize), (0, 8)),
|
||||||
((Axis::V, Direction::Forward, 999usize), (4, 8)),
|
((Axis::V, Direction::Forward, 4usize), (4, 8)),
|
||||||
|
((Axis::V, Direction::Forward, 999usize), (5, 0)),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
for ((axis, direction, amount), coordinates) in moves_and_expected_coordinates {
|
for ((axis, direction, amount), coordinates) in moves_and_expected_coordinates {
|
||||||
|
@ -1758,10 +1758,20 @@ fn next(&mut self) -> Option<Self::Item> {
|
|||||||
self.next_event = self.iter.next();
|
self.next_event = self.iter.next();
|
||||||
Some(event)
|
Some(event)
|
||||||
}
|
}
|
||||||
// can happen if deleting and cursor at EOF, and diagnostic reaches past the end
|
// Can happen if cursor at EOF and/or diagnostic reaches past the end.
|
||||||
(None, Some((_, _))) => {
|
// We need to actually emit events for the cursor-at-EOF situation,
|
||||||
self.next_span = None;
|
// even though the range is past the end of the text. This needs to be
|
||||||
None
|
// handled appropriately by the drawing code by not assuming that
|
||||||
|
// all `Source` events point to valid indices in the rope.
|
||||||
|
(None, Some((span, range))) => {
|
||||||
|
let event = HighlightStart(Highlight(*span));
|
||||||
|
self.queue.push(HighlightEnd);
|
||||||
|
self.queue.push(Source {
|
||||||
|
start: range.start,
|
||||||
|
end: range.end,
|
||||||
|
});
|
||||||
|
self.next_span = self.spans.next();
|
||||||
|
Some(event)
|
||||||
}
|
}
|
||||||
(None, None) => None,
|
(None, None) => None,
|
||||||
e => unreachable!("{:?}", e),
|
e => unreachable!("{:?}", e),
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
use helix_core::{
|
use helix_core::{
|
||||||
coords_at_pos,
|
coords_at_pos,
|
||||||
graphemes::ensure_grapheme_boundary_next,
|
graphemes::{ensure_grapheme_boundary_next, next_grapheme_boundary, prev_grapheme_boundary},
|
||||||
syntax::{self, HighlightEvent},
|
syntax::{self, HighlightEvent},
|
||||||
LineEnding, Position, Range,
|
LineEnding, Position, Range,
|
||||||
};
|
};
|
||||||
@ -165,21 +165,18 @@ pub fn render_buffer(
|
|||||||
}
|
}
|
||||||
.unwrap_or(base_cursor_scope);
|
.unwrap_or(base_cursor_scope);
|
||||||
|
|
||||||
let primary_selection_scope = theme
|
|
||||||
.find_scope_index("ui.selection.primary")
|
|
||||||
.unwrap_or(selection_scope);
|
|
||||||
|
|
||||||
let highlights: Box<dyn Iterator<Item = HighlightEvent>> = if is_focused {
|
let highlights: Box<dyn Iterator<Item = HighlightEvent>> = if is_focused {
|
||||||
// inject selections as highlight scopes
|
|
||||||
let mut spans: Vec<(usize, std::ops::Range<usize>)> = Vec::new();
|
|
||||||
|
|
||||||
// TODO: primary + insert mode patching:
|
// TODO: primary + insert mode patching:
|
||||||
// (ui.cursor.primary).patch(mode).unwrap_or(cursor)
|
// (ui.cursor.primary).patch(mode).unwrap_or(cursor)
|
||||||
|
|
||||||
let primary_cursor_scope = theme
|
let primary_cursor_scope = theme
|
||||||
.find_scope_index("ui.cursor.primary")
|
.find_scope_index("ui.cursor.primary")
|
||||||
.unwrap_or(cursor_scope);
|
.unwrap_or(cursor_scope);
|
||||||
|
let primary_selection_scope = theme
|
||||||
|
.find_scope_index("ui.selection.primary")
|
||||||
|
.unwrap_or(selection_scope);
|
||||||
|
|
||||||
|
// inject selections as highlight scopes
|
||||||
|
let mut spans: Vec<(usize, std::ops::Range<usize>)> = Vec::new();
|
||||||
for (i, range) in selections.iter().enumerate() {
|
for (i, range) in selections.iter().enumerate() {
|
||||||
let (cursor_scope, selection_scope) = if i == primary_idx {
|
let (cursor_scope, selection_scope) = if i == primary_idx {
|
||||||
(primary_cursor_scope, primary_selection_scope)
|
(primary_cursor_scope, primary_selection_scope)
|
||||||
@ -187,19 +184,23 @@ pub fn render_buffer(
|
|||||||
(cursor_scope, selection_scope)
|
(cursor_scope, selection_scope)
|
||||||
};
|
};
|
||||||
|
|
||||||
if range.head == range.anchor {
|
// Special-case: cursor at end of the rope.
|
||||||
|
if range.head == range.anchor && range.head == text.len_chars() {
|
||||||
spans.push((cursor_scope, range.head..range.head + 1));
|
spans.push((cursor_scope, range.head..range.head + 1));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let reverse = range.head < range.anchor;
|
let range = range.min_width_1(text);
|
||||||
|
if range.head > range.anchor {
|
||||||
if reverse {
|
// Standard case.
|
||||||
spans.push((cursor_scope, range.head..range.head + 1));
|
let cursor_start = prev_grapheme_boundary(text, range.head);
|
||||||
spans.push((selection_scope, range.head + 1..range.anchor + 1));
|
spans.push((selection_scope, range.anchor..cursor_start));
|
||||||
|
spans.push((cursor_scope, cursor_start..range.head));
|
||||||
} else {
|
} else {
|
||||||
spans.push((selection_scope, range.anchor..range.head));
|
// Reverse case.
|
||||||
spans.push((cursor_scope, range.head..range.head + 1));
|
let cursor_end = next_grapheme_boundary(text, range.head);
|
||||||
|
spans.push((cursor_scope, range.head..cursor_end));
|
||||||
|
spans.push((selection_scope, cursor_end..range.anchor));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -232,7 +233,10 @@ pub fn render_buffer(
|
|||||||
spans.pop();
|
spans.pop();
|
||||||
}
|
}
|
||||||
HighlightEvent::Source { start, end } => {
|
HighlightEvent::Source { start, end } => {
|
||||||
let text = text.slice(start..end);
|
// `unwrap_or_else` part is for off-the-end indices of
|
||||||
|
// the rope, to allow cursor highlighting at the end
|
||||||
|
// of the rope.
|
||||||
|
let text = text.get_slice(start..end).unwrap_or_else(|| " ".into());
|
||||||
|
|
||||||
use helix_core::graphemes::{grapheme_width, RopeGraphemes};
|
use helix_core::graphemes::{grapheme_width, RopeGraphemes};
|
||||||
|
|
||||||
@ -301,7 +305,11 @@ pub fn render_buffer(
|
|||||||
let info: Style = theme.get("info");
|
let info: Style = theme.get("info");
|
||||||
let hint: Style = theme.get("hint");
|
let hint: Style = theme.get("hint");
|
||||||
|
|
||||||
for (i, line) in (view.first_line..last_line).enumerate() {
|
// 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();
|
||||||
|
|
||||||
|
for (i, line) in (view.first_line..=last_line).enumerate() {
|
||||||
use helix_core::diagnostic::Severity;
|
use helix_core::diagnostic::Severity;
|
||||||
if let Some(diagnostic) = doc.diagnostics().iter().find(|d| d.line == line) {
|
if let Some(diagnostic) = doc.diagnostics().iter().find(|d| d.line == line) {
|
||||||
surface.set_stringn(
|
surface.set_stringn(
|
||||||
@ -318,11 +326,17 @@ pub fn render_buffer(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// line numbers having selections are rendered differently
|
// Line numbers having selections are rendered
|
||||||
|
// differently, further below.
|
||||||
|
let line_number_text = if line == last_line && !draw_last {
|
||||||
|
" ~".into()
|
||||||
|
} else {
|
||||||
|
format!("{:>5}", line + 1)
|
||||||
|
};
|
||||||
surface.set_stringn(
|
surface.set_stringn(
|
||||||
viewport.x + 1 - OFFSET,
|
viewport.x + 1 - OFFSET,
|
||||||
viewport.y + i as u16,
|
viewport.y + i as u16,
|
||||||
format!("{:>5}", line + 1),
|
line_number_text,
|
||||||
5,
|
5,
|
||||||
linenr,
|
linenr,
|
||||||
);
|
);
|
||||||
@ -336,7 +350,7 @@ pub fn render_buffer(
|
|||||||
if is_focused {
|
if is_focused {
|
||||||
let screen = {
|
let screen = {
|
||||||
let start = text.line_to_char(view.first_line);
|
let start = text.line_to_char(view.first_line);
|
||||||
let end = text.line_to_char(last_line + 1);
|
let end = text.line_to_char(last_line + 1) + 1; // +1 for cursor at end of text.
|
||||||
Range::new(start, end)
|
Range::new(start, end)
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -345,10 +359,17 @@ pub fn render_buffer(
|
|||||||
for selection in selection.iter().filter(|range| range.overlaps(&screen)) {
|
for selection in selection.iter().filter(|range| range.overlaps(&screen)) {
|
||||||
let head = view.screen_coords_at_pos(doc, text, selection.head);
|
let head = view.screen_coords_at_pos(doc, text, selection.head);
|
||||||
if let Some(head) = head {
|
if let Some(head) = head {
|
||||||
|
// Draw line number for selected lines.
|
||||||
|
let line_number = view.first_line + head.row;
|
||||||
|
let line_number_text = if line_number == last_line && !draw_last {
|
||||||
|
" ~".into()
|
||||||
|
} else {
|
||||||
|
format!("{:>5}", line_number + 1)
|
||||||
|
};
|
||||||
surface.set_stringn(
|
surface.set_stringn(
|
||||||
viewport.x + 1 - OFFSET,
|
viewport.x + 1 - OFFSET,
|
||||||
viewport.y + head.row as u16,
|
viewport.y + head.row as u16,
|
||||||
format!("{:>5}", view.first_line + head.row + 1),
|
line_number_text,
|
||||||
5,
|
5,
|
||||||
linenr_select,
|
linenr_select,
|
||||||
);
|
);
|
||||||
|
@ -448,15 +448,7 @@ pub fn open(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut file = std::fs::File::open(&path).context(format!("unable to open {:?}", path))?;
|
let mut file = std::fs::File::open(&path).context(format!("unable to open {:?}", path))?;
|
||||||
let (mut rope, encoding) = from_reader(&mut file, encoding)?;
|
let (rope, encoding) = from_reader(&mut file, encoding)?;
|
||||||
|
|
||||||
// search for line endings
|
|
||||||
let line_ending = auto_detect_line_ending(&rope).unwrap_or(DEFAULT_LINE_ENDING);
|
|
||||||
|
|
||||||
// add missing newline at the end of file
|
|
||||||
if rope.len_bytes() == 0 || !char_is_line_ending(rope.char(rope.len_chars() - 1)) {
|
|
||||||
rope.insert(rope.len_chars(), line_ending.as_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut doc = Self::from(rope, Some(encoding));
|
let mut doc = Self::from(rope, Some(encoding));
|
||||||
|
|
||||||
@ -466,9 +458,9 @@ pub fn open(
|
|||||||
doc.detect_language(theme, loader);
|
doc.detect_language(theme, loader);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Detect indentation style and set line ending.
|
// Detect indentation style and line ending.
|
||||||
doc.detect_indent_style();
|
doc.detect_indent_style();
|
||||||
doc.line_ending = line_ending;
|
doc.line_ending = auto_detect_line_ending(&doc.text).unwrap_or(DEFAULT_LINE_ENDING);
|
||||||
|
|
||||||
Ok(doc)
|
Ok(doc)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user