diff options
Diffstat (limited to 'helix-term/src/ui')
-rw-r--r-- | helix-term/src/ui/editor.rs | 203 | ||||
-rw-r--r-- | helix-term/src/ui/mod.rs | 7 | ||||
-rw-r--r-- | helix-term/src/ui/picker.rs | 13 | ||||
-rw-r--r-- | helix-term/src/ui/prompt.rs | 3 |
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, |