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/editor.rs203
-rw-r--r--helix-term/src/ui/mod.rs7
-rw-r--r--helix-term/src/ui/picker.rs13
-rw-r--r--helix-term/src/ui/prompt.rs3
4 files changed, 211 insertions, 15 deletions
diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs
index 03cd0474..01554c64 100644
--- a/helix-term/src/ui/editor.rs
+++ b/helix-term/src/ui/editor.rs
@@ -15,16 +15,17 @@ use helix_core::{
unicode::width::UnicodeWidthStr,
LineEnding, Position, Range, Selection,
};
+use helix_dap::{Breakpoint, SourceBreakpoint, StackFrame};
use helix_view::{
document::{Mode, SCRATCH_BUFFER_NAME},
editor::LineNumber,
- graphics::{CursorKind, Modifier, Rect, Style},
+ graphics::{Color, CursorKind, Modifier, Rect, Style},
info::Info,
input::KeyEvent,
keyboard::{KeyCode, KeyModifiers},
Document, Editor, Theme, View,
};
-use std::borrow::Cow;
+use std::{borrow::Cow, collections::HashMap, path::PathBuf};
use crossterm::event::{Event, MouseButton, MouseEvent, MouseEventKind};
use tui::buffer::Buffer as Surface;
@@ -71,6 +72,9 @@ impl EditorView {
is_focused: bool,
loader: &syntax::Loader,
config: &helix_view::editor::Config,
+ debugger: &Option<helix_dap::Client>,
+ all_breakpoints: &HashMap<PathBuf, Vec<SourceBreakpoint>>,
+ dbg_breakpoints: &Option<Vec<Breakpoint>>,
) {
let inner = view.inner_area();
let area = view.area;
@@ -87,7 +91,18 @@ impl EditorView {
};
Self::render_text_highlights(doc, view.offset, inner, surface, theme, highlights);
- Self::render_gutter(doc, view, view.area, surface, theme, is_focused, config);
+ Self::render_gutter(
+ doc,
+ view,
+ view.area,
+ surface,
+ theme,
+ is_focused,
+ config,
+ debugger,
+ all_breakpoints,
+ dbg_breakpoints,
+ );
if is_focused {
Self::render_focused_view_elements(view, doc, inner, theme, surface);
@@ -106,7 +121,7 @@ impl EditorView {
}
}
- self.render_diagnostics(doc, view, inner, surface, theme);
+ self.render_diagnostics(doc, view, inner, surface, theme, all_breakpoints);
let statusline_area = view
.area
@@ -408,6 +423,9 @@ impl EditorView {
theme: &Theme,
is_focused: bool,
config: &helix_view::editor::Config,
+ debugger: &Option<helix_dap::Client>,
+ all_breakpoints: &HashMap<PathBuf, Vec<SourceBreakpoint>>,
+ dbg_breakpoints: &Option<Vec<Breakpoint>>,
) {
let text = doc.text().slice(..);
let last_line = view.last_line(doc);
@@ -437,6 +455,31 @@ impl EditorView {
.map(|range| range.cursor_line(text))
.collect();
+ let mut breakpoints: Option<&Vec<SourceBreakpoint>> = None;
+ let mut stack_frame: Option<&StackFrame> = None;
+ if let Some(path) = doc.path() {
+ breakpoints = all_breakpoints.get(path);
+ if let Some(debugger) = debugger {
+ // if we have a frame, and the frame path matches document
+ if let (Some(frame), Some(thread_id)) = (debugger.active_frame, debugger.thread_id)
+ {
+ let frame = debugger
+ .stack_frames
+ .get(&thread_id)
+ .and_then(|bt| bt.get(frame)); // TODO: drop the clone..
+ if let Some(StackFrame {
+ source: Some(source),
+ ..
+ }) = &frame
+ {
+ if source.path.as_ref() == Some(path) {
+ stack_frame = frame;
+ }
+ };
+ };
+ }
+ }
+
for (i, line) in (view.offset.row..(last_line + 1)).enumerate() {
use helix_core::diagnostic::Severity;
if let Some(diagnostic) = doc.diagnostics().iter().find(|d| d.line == line) {
@@ -456,6 +499,75 @@ impl EditorView {
let selected = cursors.contains(&line);
+ // TODO: debugger should translate received breakpoints to 0-indexing
+
+ if let Some(user) = breakpoints.as_ref() {
+ let debugger_breakpoint = if let Some(debugger) = dbg_breakpoints.as_ref() {
+ debugger.iter().find(|breakpoint| {
+ if breakpoint.source.is_some()
+ && doc.path().is_some()
+ && breakpoint.source.as_ref().unwrap().path == doc.path().cloned()
+ {
+ match (breakpoint.line, breakpoint.end_line) {
+ #[allow(clippy::int_plus_one)]
+ (Some(l), Some(el)) => l - 1 <= line && line <= el - 1,
+ (Some(l), None) => l - 1 == line,
+ _ => false,
+ }
+ } else {
+ false
+ }
+ })
+ } else {
+ None
+ };
+
+ if let Some(breakpoint) = user.iter().find(|breakpoint| breakpoint.line - 1 == line)
+ {
+ let verified = debugger_breakpoint.map(|b| b.verified).unwrap_or(false);
+ let mut style =
+ if breakpoint.condition.is_some() && breakpoint.log_message.is_some() {
+ error.add_modifier(Modifier::UNDERLINED)
+ } else if breakpoint.condition.is_some() {
+ error
+ } else if breakpoint.log_message.is_some() {
+ info
+ } else {
+ warning
+ };
+ if !verified {
+ // Faded colors
+ style = if let Some(Color::Rgb(r, g, b)) = style.fg {
+ style.fg(Color::Rgb(
+ ((r as f32) * 0.4).floor() as u8,
+ ((g as f32) * 0.4).floor() as u8,
+ ((b as f32) * 0.4).floor() as u8,
+ ))
+ } else {
+ style.fg(Color::Gray)
+ }
+ };
+ surface.set_stringn(viewport.x, viewport.y + i as u16, "▲", 1, style);
+ } else if let Some(breakpoint) = debugger_breakpoint {
+ let style = if breakpoint.verified {
+ info
+ } else {
+ info.fg(Color::Gray)
+ };
+ surface.set_stringn(viewport.x, viewport.y + i as u16, "⊚", 1, style);
+ }
+ }
+
+ if let Some(frame) = stack_frame {
+ if frame.line - 1 == line {
+ surface.set_style(
+ Rect::new(viewport.x, viewport.y + i as u16, 6, 1),
+ helix_view::graphics::Style::default()
+ .bg(helix_view::graphics::Color::LightYellow),
+ );
+ }
+ }
+
let text = if line == last_line && !draw_last {
" ~".into()
} else {
@@ -492,6 +604,7 @@ impl EditorView {
viewport: Rect,
surface: &mut Surface,
theme: &Theme,
+ all_breakpoints: &HashMap<PathBuf, Vec<SourceBreakpoint>>,
) {
use helix_core::diagnostic::Severity;
use tui::{
@@ -529,6 +642,29 @@ impl EditorView {
lines.extend(text.lines);
}
+ if let Some(path) = doc.path() {
+ let line = doc.text().char_to_line(cursor);
+ if let Some(breakpoints) = all_breakpoints.get(path) {
+ if let Some(breakpoint) = breakpoints
+ .iter()
+ .find(|breakpoint| breakpoint.line - 1 == line)
+ {
+ if let Some(condition) = &breakpoint.condition {
+ lines.extend(
+ Text::styled(condition, warning.add_modifier(Modifier::UNDERLINED))
+ .lines,
+ );
+ }
+ if let Some(log_message) = &breakpoint.log_message {
+ lines.extend(
+ Text::styled(log_message, info.add_modifier(Modifier::UNDERLINED))
+ .lines,
+ );
+ }
+ }
+ }
+ }
+
let paragraph = Paragraph::new(lines)
.alignment(Alignment::Right)
.wrap(Wrap { trim: true });
@@ -692,7 +828,6 @@ impl EditorView {
cxt: &mut commands::Context,
event: KeyEvent,
) -> Option<KeymapResult> {
- self.autoinfo = None;
let key_result = self.keymaps.get_mut(&mode).unwrap().get(event);
self.autoinfo = key_result.sticky.map(|node| node.infobox());
@@ -841,6 +976,26 @@ impl EditorView {
return EventResult::Consumed(None);
}
+ let result = editor.tree.views().find_map(|(view, _focus)| {
+ view.gutter_coords_at_screen_coords(row, column)
+ .map(|coords| (coords.0, coords.1, view.id))
+ });
+
+ if let Some((line, _, view_id)) = result {
+ editor.tree.focus = view_id;
+
+ let doc = editor
+ .documents
+ .get_mut(&editor.tree.get(view_id).doc)
+ .unwrap();
+ if let Ok(pos) = doc.text().try_line_to_char(line) {
+ doc.set_selection(view_id, Selection::point(pos));
+ commands::dap_toggle_breakpoint(cxt);
+
+ return EventResult::Consumed(None);
+ }
+ }
+
EventResult::Ignored
}
@@ -917,6 +1072,40 @@ impl EditorView {
}
MouseEvent {
+ kind: MouseEventKind::Up(MouseButton::Right),
+ row,
+ column,
+ modifiers,
+ ..
+ } => {
+ let result = cxt.editor.tree.views().find_map(|(view, _focus)| {
+ view.gutter_coords_at_screen_coords(row, column)
+ .map(|coords| (coords.0, coords.1, view.id))
+ });
+
+ if let Some((line, _, view_id)) = result {
+ cxt.editor.tree.focus = view_id;
+
+ let doc = cxt
+ .editor
+ .documents
+ .get_mut(&cxt.editor.tree.get(view_id).doc)
+ .unwrap();
+ if let Ok(pos) = doc.text().try_line_to_char(line) {
+ doc.set_selection(view_id, Selection::point(pos));
+ if modifiers == crossterm::event::KeyModifiers::ALT {
+ commands::Command::dap_edit_log.execute(cxt);
+ } else {
+ commands::Command::dap_edit_condition.execute(cxt);
+ }
+
+ return EventResult::Consumed(None);
+ }
+ }
+ EventResult::Ignored
+ }
+
+ MouseEvent {
kind: MouseEventKind::Up(MouseButton::Middle),
row,
column,
@@ -1083,6 +1272,7 @@ impl Component for EditorView {
for (view, is_focused) in cx.editor.tree.views() {
let doc = cx.editor.document(view.doc).unwrap();
let loader = &cx.editor.syn_loader;
+ let dbg_breakpoints = cx.editor.debugger.as_ref().map(|d| d.breakpoints.clone());
self.render_view(
doc,
view,
@@ -1092,6 +1282,9 @@ impl Component for EditorView {
is_focused,
loader,
&cx.editor.config,
+ &cx.editor.debugger,
+ &cx.editor.breakpoints,
+ &dbg_breakpoints,
);
}
diff --git a/helix-term/src/ui/mod.rs b/helix-term/src/ui/mod.rs
index 62da0dce..0915937d 100644
--- a/helix-term/src/ui/mod.rs
+++ b/helix-term/src/ui/mod.rs
@@ -21,7 +21,7 @@ pub use text::Text;
use helix_core::regex::Regex;
use helix_core::regex::RegexBuilder;
-use helix_view::{Document, Editor, View};
+use helix_view::{Document, View};
use std::path::PathBuf;
@@ -90,6 +90,7 @@ pub fn regex_prompt(
}
}
},
+ None,
)
}
@@ -152,8 +153,8 @@ pub fn file_picker(root: PathBuf) -> FilePicker<PathBuf> {
.unwrap()
.into()
},
- move |editor: &mut Editor, path: &PathBuf, action| {
- editor
+ move |cx, path: &PathBuf, action| {
+ cx.editor
.open(path.into(), action)
.expect("editor.open failed");
},
diff --git a/helix-term/src/ui/picker.rs b/helix-term/src/ui/picker.rs
index 6b1c5832..2b2e47a4 100644
--- a/helix-term/src/ui/picker.rs
+++ b/helix-term/src/ui/picker.rs
@@ -86,7 +86,7 @@ impl<T> FilePicker<T> {
pub fn new(
options: Vec<T>,
format_fn: impl Fn(&T) -> Cow<str> + 'static,
- callback_fn: impl Fn(&mut Editor, &T, Action) + 'static,
+ callback_fn: impl Fn(&mut Context, &T, Action) + 'static,
preview_fn: impl Fn(&Editor, &T) -> Option<FileLocation> + 'static,
) -> Self {
Self {
@@ -284,7 +284,7 @@ pub struct Picker<T> {
pub truncate_start: bool,
format_fn: Box<dyn Fn(&T) -> Cow<str>>,
- callback_fn: Box<dyn Fn(&mut Editor, &T, Action)>,
+ callback_fn: Box<dyn Fn(&mut Context, &T, Action)>,
}
impl<T> Picker<T> {
@@ -292,7 +292,7 @@ impl<T> Picker<T> {
render_centered: bool,
options: Vec<T>,
format_fn: impl Fn(&T) -> Cow<str> + 'static,
- callback_fn: impl Fn(&mut Editor, &T, Action) + 'static,
+ callback_fn: impl Fn(&mut Context, &T, Action) + 'static,
) -> Self {
let prompt = Prompt::new(
"".into(),
@@ -301,6 +301,7 @@ impl<T> Picker<T> {
|_editor: &mut Context, _pattern: &str, _event: PromptEvent| {
//
},
+ None,
);
let mut picker = Self {
@@ -421,19 +422,19 @@ impl<T: 'static> Component for Picker<T> {
}
key!(Enter) => {
if let Some(option) = self.selection() {
- (self.callback_fn)(&mut cx.editor, option, Action::Replace);
+ (self.callback_fn)(cx, option, Action::Replace);
}
return close_fn;
}
ctrl!('s') => {
if let Some(option) = self.selection() {
- (self.callback_fn)(&mut cx.editor, option, Action::HorizontalSplit);
+ (self.callback_fn)(cx, option, Action::HorizontalSplit);
}
return close_fn;
}
ctrl!('v') => {
if let Some(option) = self.selection() {
- (self.callback_fn)(&mut cx.editor, option, Action::VerticalSplit);
+ (self.callback_fn)(cx, option, Action::VerticalSplit);
}
return close_fn;
}
diff --git a/helix-term/src/ui/prompt.rs b/helix-term/src/ui/prompt.rs
index e90b0772..00ffdccf 100644
--- a/helix-term/src/ui/prompt.rs
+++ b/helix-term/src/ui/prompt.rs
@@ -61,10 +61,11 @@ impl Prompt {
history_register: Option<char>,
mut completion_fn: impl FnMut(&str) -> Vec<Completion> + 'static,
callback_fn: impl FnMut(&mut Context, &str, PromptEvent) + 'static,
+ line: Option<String>,
) -> Self {
Self {
prompt,
- line: String::new(),
+ line: line.unwrap_or_default(),
cursor: 0,
completion: completion_fn(""),
selection: None,