aboutsummaryrefslogtreecommitdiff
path: root/helix-term/src/ui
diff options
context:
space:
mode:
Diffstat (limited to 'helix-term/src/ui')
-rw-r--r--helix-term/src/ui/completion.rs8
-rw-r--r--helix-term/src/ui/editor.rs95
2 files changed, 65 insertions, 38 deletions
diff --git a/helix-term/src/ui/completion.rs b/helix-term/src/ui/completion.rs
index be6db42c..942a2483 100644
--- a/helix-term/src/ui/completion.rs
+++ b/helix-term/src/ui/completion.rs
@@ -86,7 +86,7 @@ impl Completion {
let item = item.unwrap();
// if more text was entered, remove it
- let cursor = doc.selection(view.id).cursor();
+ let cursor = doc.selection(view.id).cursor(doc.text().slice(..));
if trigger_offset < cursor {
let remove = Transaction::change(
doc.text(),
@@ -109,7 +109,7 @@ impl Completion {
)
} else {
let text = item.insert_text.as_ref().unwrap_or(&item.label);
- let cursor = doc.selection(view.id).cursor();
+ let cursor = doc.selection(view.id).cursor(doc.text().slice(..));
Transaction::change(
doc.text(),
vec![(cursor, cursor, Some(text.as_str().into()))].into_iter(),
@@ -155,7 +155,7 @@ impl Completion {
// TODO: hooks should get processed immediately so maybe do it after select!(), before
// looping?
- let cursor = doc.selection(view.id).cursor();
+ let cursor = doc.selection(view.id).cursor(doc.text().slice(..));
if self.trigger_offset <= cursor {
let fragment = doc.text().slice(self.trigger_offset..cursor);
let text = Cow::from(fragment);
@@ -212,7 +212,7 @@ impl Component for Completion {
.language()
.and_then(|scope| scope.strip_prefix("source."))
.unwrap_or("");
- let cursor_pos = doc.selection(view.id).cursor();
+ let cursor_pos = doc.selection(view.id).cursor(doc.text().slice(..));
let cursor_pos = (helix_core::coords_at_pos(doc.text().slice(..), cursor_pos).row
- view.first_line) as u16;
diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs
index 9a2fbf57..8e29be6c 100644
--- a/helix-term/src/ui/editor.rs
+++ b/helix-term/src/ui/editor.rs
@@ -8,7 +8,7 @@ use crate::{
use helix_core::{
coords_at_pos,
- graphemes::{ensure_grapheme_boundary, next_grapheme_boundary},
+ graphemes::{ensure_grapheme_boundary_next, next_grapheme_boundary, prev_grapheme_boundary},
syntax::{self, HighlightEvent},
LineEnding, Position, Range,
};
@@ -161,8 +161,8 @@ impl EditorView {
let highlights = highlights.into_iter().map(|event| match event.unwrap() {
// convert byte offsets to char offset
HighlightEvent::Source { start, end } => {
- let start = ensure_grapheme_boundary(text, text.byte_to_char(start));
- let end = ensure_grapheme_boundary(text, text.byte_to_char(end));
+ let start = ensure_grapheme_boundary_next(text, text.byte_to_char(start));
+ let end = ensure_grapheme_boundary_next(text, text.byte_to_char(end));
HighlightEvent::Source { start, end }
}
event => event,
@@ -186,21 +186,18 @@ impl EditorView {
}
.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 {
- // inject selections as highlight scopes
- let mut spans: Vec<(usize, std::ops::Range<usize>)> = Vec::new();
-
// TODO: primary + insert mode patching:
// (ui.cursor.primary).patch(mode).unwrap_or(cursor)
-
let primary_cursor_scope = theme
.find_scope_index("ui.cursor.primary")
.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() {
let (cursor_scope, selection_scope) = if i == primary_idx {
(primary_cursor_scope, primary_selection_scope)
@@ -208,24 +205,23 @@ impl EditorView {
(cursor_scope, selection_scope)
};
- let cursor_end = next_grapheme_boundary(text, range.head); // Used in every case below.
-
- if range.head == range.anchor {
- spans.push((cursor_scope, range.head..cursor_end));
+ // 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));
continue;
}
- let reverse = range.head < range.anchor;
-
- if reverse {
- spans.push((cursor_scope, range.head..cursor_end));
- spans.push((
- selection_scope,
- cursor_end..next_grapheme_boundary(text, range.anchor),
- ));
+ let range = range.min_width_1(text);
+ if range.head > range.anchor {
+ // Standard case.
+ let cursor_start = prev_grapheme_boundary(text, range.head);
+ spans.push((selection_scope, range.anchor..cursor_start));
+ spans.push((cursor_scope, cursor_start..range.head));
} else {
- spans.push((selection_scope, range.anchor..range.head));
+ // Reverse case.
+ 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));
}
}
@@ -258,7 +254,10 @@ impl EditorView {
spans.pop();
}
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};
@@ -327,7 +326,11 @@ impl EditorView {
let info: Style = theme.get("info");
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 + 1)).enumerate() {
use helix_core::diagnostic::Severity;
if let Some(diagnostic) = doc.diagnostics().iter().find(|d| d.line == line) {
surface.set_stringn(
@@ -344,11 +347,17 @@ impl EditorView {
);
}
- // 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(
viewport.x + 1 - OFFSET,
viewport.y + i as u16,
- format!("{:>5}", line + 1),
+ line_number_text,
5,
linenr,
);
@@ -362,19 +371,34 @@ impl EditorView {
if is_focused {
let screen = {
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)
};
let selection = doc.selection(view.id);
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,
+ if selection.head > selection.anchor {
+ selection.head - 1
+ } else {
+ selection.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(
viewport.x + 1 - OFFSET,
viewport.y + head.row as u16,
- format!("{:>5}", view.first_line + head.row + 1),
+ line_number_text,
5,
linenr_select,
);
@@ -382,7 +406,7 @@ impl EditorView {
// TODO: set cursor position for IME
if let Some(syntax) = doc.syntax() {
use helix_core::match_brackets;
- let pos = doc.selection(view.id).cursor();
+ let pos = doc.selection(view.id).cursor(doc.text().slice(..));
let pos = match_brackets::find(syntax, doc.text(), pos)
.and_then(|pos| view.screen_coords_at_pos(doc, text, pos));
@@ -427,7 +451,7 @@ impl EditorView {
widgets::{Paragraph, Widget},
};
- let cursor = doc.selection(view.id).cursor();
+ let cursor = doc.selection(view.id).cursor(doc.text().slice(..));
let diagnostics = doc.diagnostics().iter().filter(|diagnostic| {
diagnostic.range.start <= cursor && diagnostic.range.end >= cursor
@@ -539,7 +563,10 @@ impl EditorView {
// _ => "indent:ERROR",
// };
let position_info = {
- let pos = coords_at_pos(doc.text().slice(..), doc.selection(view.id).cursor());
+ let pos = coords_at_pos(
+ doc.text().slice(..),
+ doc.selection(view.id).cursor(doc.text().slice(..)),
+ );
format!("{}:{}", pos.row + 1, pos.col + 1) // convert to 1-indexing
};