tlc: editor visual polish — current-line highlight, bracket match, modified gutter mark
Current-line highlight (bg lightened by 12 per channel on cursor line),
bracket-match tracking (forward/backward search for ()[]{}<> pairs),
modified-line gutter mark (▌ in warning color when buffer is dirty).
bracket_flash field + find_matching_forward/backward methods on Editor.
This commit is contained in:
@@ -131,6 +131,10 @@ pub struct Editor {
|
||||
/// Track scroll position to reset highlighter state on scroll.
|
||||
#[cfg(feature = "syntect")]
|
||||
last_render_top: usize,
|
||||
/// Cached bracket-match pair (opening byte offset, closing byte
|
||||
/// offset) for the flash highlight, or `None` when the cursor is
|
||||
/// not on a bracket.
|
||||
bracket_flash: Option<(usize, usize)>,
|
||||
}
|
||||
|
||||
impl Editor {
|
||||
@@ -165,6 +169,7 @@ impl Editor {
|
||||
highlighter: hl,
|
||||
#[cfg(feature = "syntect")]
|
||||
last_render_top: 0,
|
||||
bracket_flash: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,6 +196,7 @@ impl Editor {
|
||||
highlighter: None,
|
||||
#[cfg(feature = "syntect")]
|
||||
last_render_top: 0,
|
||||
bracket_flash: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -241,6 +247,63 @@ impl Editor {
|
||||
self.modified
|
||||
}
|
||||
|
||||
fn update_bracket_flash(&mut self) {
|
||||
let pos = self.cursor.position();
|
||||
let text = self.buffer.as_string();
|
||||
if pos >= text.len() {
|
||||
self.bracket_flash = None;
|
||||
return;
|
||||
}
|
||||
let ch = text[pos..].chars().next().unwrap();
|
||||
let open = "([{<";
|
||||
let close = ")]}>";
|
||||
let result = if let Some(idx) = open.find(ch) {
|
||||
let target = close.as_bytes()[idx] as char;
|
||||
Self::find_matching_forward(&text, pos, ch, target)
|
||||
.map(|end| (pos, end))
|
||||
} else if let Some(idx) = close.find(ch) {
|
||||
let target = open.as_bytes()[idx] as char;
|
||||
Self::find_matching_backward(&text, pos, ch, target)
|
||||
.map(|start| (start, pos))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
self.bracket_flash = result;
|
||||
}
|
||||
|
||||
fn find_matching_forward(text: &str, start: usize, open: char, close: char) -> Option<usize> {
|
||||
let mut depth = 1i32;
|
||||
let mut offset = start + open.len_utf8();
|
||||
for (i, ch) in text[offset..].char_indices() {
|
||||
let abs = offset + i;
|
||||
if ch == open {
|
||||
depth += 1;
|
||||
} else if ch == close {
|
||||
depth -= 1;
|
||||
if depth == 0 {
|
||||
return Some(abs);
|
||||
}
|
||||
}
|
||||
offset = abs + ch.len_utf8();
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn find_matching_backward(text: &str, start: usize, close: char, open: char) -> Option<usize> {
|
||||
let mut depth = 1i32;
|
||||
for (i, ch) in text[..start].char_indices().rev() {
|
||||
if ch == close {
|
||||
depth += 1;
|
||||
} else if ch == open {
|
||||
depth -= 1;
|
||||
if depth == 0 {
|
||||
return Some(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Set a named bookmark at the current cursor location.
|
||||
pub fn set_bookmark(&mut self, name: char) -> Result<(), String> {
|
||||
let line = self.buffer_line_of(self.cursor.position()) as u32;
|
||||
|
||||
@@ -28,6 +28,7 @@ impl Editor {
|
||||
// (e.g. commit_prompt's GotoLine/GotoCol) changed the
|
||||
// buffer cursor without calling cursor.set_position().
|
||||
self.cursor.set_position(self.buffer.cursor(), &self.buffer);
|
||||
self.update_bracket_flash();
|
||||
// Syntect is stateful: when the user scrolls the viewport
|
||||
// we have to rebuild the parser state by replaying every
|
||||
// line from the top of the file down to the new first
|
||||
@@ -67,6 +68,10 @@ impl Editor {
|
||||
let editor_bookmarkfound = mc_skin::color_pair(theme.name, "editor", "bookmarkfound");
|
||||
let body_fg = editor_default.map(|p| p.fg).unwrap_or(theme.foreground);
|
||||
let body_bg = editor_default.map(|p| p.bg).unwrap_or(theme.background);
|
||||
let cur_line_bg = match body_bg {
|
||||
Color::Rgb(r, g, b) => Color::Rgb(r.saturating_add(12), g.saturating_add(12), b.saturating_add(12)),
|
||||
_ => theme.current_line_bg(),
|
||||
};
|
||||
let marked_fg = editor_marked.map(|p| p.fg).unwrap_or(theme.marked_fg);
|
||||
let marked_bg = editor_marked.map(|p| p.bg).unwrap_or(theme.marked_bg);
|
||||
let linestate_fg = editor_linestate.map(|p| p.fg).unwrap_or(theme.cursor_fg);
|
||||
@@ -121,6 +126,7 @@ impl Editor {
|
||||
.map(|row| {
|
||||
let line_idx = top + row;
|
||||
let n = line_idx + 1;
|
||||
let is_mod_cursor = self.modified && cursor_line == line_idx;
|
||||
let gutter_style = if self.has_bookmark_on_line(line_idx) {
|
||||
if cursor_line == line_idx {
|
||||
Style::default()
|
||||
@@ -130,13 +136,17 @@ impl Editor {
|
||||
} else {
|
||||
Style::default().fg(bookmark_fg).bg(bookmark_bg)
|
||||
}
|
||||
} else if is_mod_cursor {
|
||||
Style::default().fg(theme.warning).bg(body_bg)
|
||||
} else {
|
||||
Style::default().fg(frame_fg).bg(body_bg)
|
||||
};
|
||||
Line::from(Span::styled(
|
||||
format!("{:>w$} ", n, w = (gutter_w - 1) as usize),
|
||||
gutter_style,
|
||||
))
|
||||
let text = if is_mod_cursor {
|
||||
format!("{:>w$}\u{258c}", n, w = (gutter_w - 1) as usize)
|
||||
} else {
|
||||
format!("{:>w$} ", n, w = (gutter_w - 1) as usize)
|
||||
};
|
||||
Line::from(Span::styled(text, gutter_style))
|
||||
})
|
||||
.collect();
|
||||
frame.render_widget(Paragraph::new(gutter_lines), chunks[0]);
|
||||
@@ -159,7 +169,7 @@ impl Editor {
|
||||
let line_end = (off + len).min(full_text.len());
|
||||
let line_text = full_text.get(off..line_end).unwrap_or("");
|
||||
let base_style = if cursor_line == line_idx {
|
||||
Style::default().fg(linestate_fg).bg(body_bg)
|
||||
Style::default().fg(linestate_fg).bg(cur_line_bg)
|
||||
} else {
|
||||
Style::default().fg(body_fg).bg(body_bg)
|
||||
};
|
||||
@@ -170,6 +180,7 @@ impl Editor {
|
||||
let sel_style = Style::default()
|
||||
.fg(marked_fg)
|
||||
.bg(marked_bg);
|
||||
let line_bg = if cursor_line == line_idx { cur_line_bg } else { body_bg };
|
||||
let mut spans: Vec<Span> = Vec::new();
|
||||
#[cfg(feature = "syntect")]
|
||||
{
|
||||
@@ -179,7 +190,7 @@ impl Editor {
|
||||
highlighted,
|
||||
rs,
|
||||
re,
|
||||
body_bg,
|
||||
line_bg,
|
||||
marked_bg,
|
||||
);
|
||||
body_lines.push(Line::from(spans));
|
||||
|
||||
Reference in New Issue
Block a user