diff options
Diffstat (limited to 'helix-term/src/commands')
-rw-r--r-- | helix-term/src/commands/dap.rs | 54 | ||||
-rw-r--r-- | helix-term/src/commands/lsp.rs | 222 |
2 files changed, 170 insertions, 106 deletions
diff --git a/helix-term/src/commands/dap.rs b/helix-term/src/commands/dap.rs index b897b2d5..9f6f4c15 100644 --- a/helix-term/src/commands/dap.rs +++ b/helix-term/src/commands/dap.rs @@ -4,13 +4,15 @@ use crate::{ job::{Callback, Jobs}, ui::{self, overlay::overlayed, FilePicker, Picker, Popup, Prompt, PromptEvent, Text}, }; -use helix_core::syntax::{DebugArgumentValue, DebugConfigCompletion}; +use dap::{StackFrame, Thread, ThreadStates}; +use helix_core::syntax::{DebugArgumentValue, DebugConfigCompletion, DebugTemplate}; use helix_dap::{self as dap, Client}; use helix_lsp::block_on; use helix_view::editor::Breakpoint; use serde_json::{to_value, Value}; use tokio_stream::wrappers::UnboundedReceiverStream; +use tui::text::Spans; use std::collections::HashMap; use std::future::Future; @@ -20,6 +22,38 @@ use anyhow::{anyhow, bail}; use helix_view::handlers::dap::{breakpoints_changed, jump_to_stack_frame, select_thread_id}; +impl ui::menu::Item for StackFrame { + type Data = (); + + fn label(&self, _data: &Self::Data) -> Spans { + self.name.as_str().into() // TODO: include thread_states in the label + } +} + +impl ui::menu::Item for DebugTemplate { + type Data = (); + + fn label(&self, _data: &Self::Data) -> Spans { + self.name.as_str().into() + } +} + +impl ui::menu::Item for Thread { + type Data = ThreadStates; + + fn label(&self, thread_states: &Self::Data) -> Spans { + format!( + "{} ({})", + self.name, + thread_states + .get(&self.id) + .map(|state| state.as_str()) + .unwrap_or("unknown") + ) + .into() + } +} + fn thread_picker( cx: &mut Context, callback_fn: impl Fn(&mut Editor, &dap::Thread) + Send + 'static, @@ -41,17 +75,7 @@ fn thread_picker( let thread_states = debugger.thread_states.clone(); let picker = FilePicker::new( threads, - move |thread| { - format!( - "{} ({})", - thread.name, - thread_states - .get(&thread.id) - .map(|state| state.as_str()) - .unwrap_or("unknown") - ) - .into() - }, + thread_states, move |cx, thread, _action| callback_fn(cx.editor, thread), move |editor, thread| { let frames = editor.debugger.as_ref()?.stack_frames.get(&thread.id)?; @@ -243,7 +267,7 @@ pub fn dap_launch(cx: &mut Context) { cx.push_layer(Box::new(overlayed(Picker::new( templates, - |template| template.name.as_str().into(), + (), |cx, template, _action| { let completions = template.completion.clone(); let name = template.name.clone(); @@ -475,7 +499,7 @@ pub fn dap_variables(cx: &mut Context) { for scope in scopes.iter() { // use helix_view::graphics::Style; - use tui::text::{Span, Spans}; + use tui::text::Span; let response = block_on(debugger.variables(scope.variables_reference)); variables.push(Spans::from(Span::styled( @@ -652,7 +676,7 @@ pub fn dap_switch_stack_frame(cx: &mut Context) { let picker = FilePicker::new( frames, - |frame| frame.name.as_str().into(), // TODO: include thread_states in the label + (), move |cx, frame, _action| { let debugger = debugger!(cx.editor); // TODO: this should be simpler to find diff --git a/helix-term/src/commands/lsp.rs b/helix-term/src/commands/lsp.rs index d11c44cd..7f82394a 100644 --- a/helix-term/src/commands/lsp.rs +++ b/helix-term/src/commands/lsp.rs @@ -19,7 +19,8 @@ use crate::{ ui::{self, overlay::overlayed, FileLocation, FilePicker, Popup, PromptEvent}, }; -use std::{borrow::Cow, collections::BTreeMap}; +use std::collections::BTreeMap; +use std::{borrow::Cow, path::PathBuf}; /// Gets the language server that is attached to a document, and /// if it's not active displays a status message. Using this macro @@ -39,6 +40,112 @@ macro_rules! language_server { }; } +impl ui::menu::Item for lsp::Location { + /// Current working directory. + type Data = PathBuf; + + fn label(&self, cwdir: &Self::Data) -> Spans { + let file: Cow<'_, str> = (self.uri.scheme() == "file") + .then(|| { + self.uri + .to_file_path() + .map(|path| { + // strip root prefix + path.strip_prefix(&cwdir) + .map(|path| path.to_path_buf()) + .unwrap_or(path) + }) + .map(|path| Cow::from(path.to_string_lossy().into_owned())) + .ok() + }) + .flatten() + .unwrap_or_else(|| self.uri.as_str().into()); + let line = self.range.start.line; + format!("{}:{}", file, line).into() + } +} + +impl ui::menu::Item for lsp::SymbolInformation { + /// Path to currently focussed document + type Data = Option<lsp::Url>; + + fn label(&self, current_doc_path: &Self::Data) -> Spans { + if current_doc_path.as_ref() == Some(&self.location.uri) { + self.name.as_str().into() + } else { + match self.location.uri.to_file_path() { + Ok(path) => { + let relative_path = helix_core::path::get_relative_path(path.as_path()) + .to_string_lossy() + .into_owned(); + format!("{} ({})", &self.name, relative_path).into() + } + Err(_) => format!("{} ({})", &self.name, &self.location.uri).into(), + } + } + } +} + +struct DiagnosticStyles { + hint: Style, + info: Style, + warning: Style, + error: Style, +} + +struct PickerDiagnostic { + url: lsp::Url, + diag: lsp::Diagnostic, +} + +impl ui::menu::Item for PickerDiagnostic { + type Data = DiagnosticStyles; + + fn label(&self, styles: &Self::Data) -> Spans { + let mut style = self + .diag + .severity + .map(|s| match s { + DiagnosticSeverity::HINT => styles.hint, + DiagnosticSeverity::INFORMATION => styles.info, + DiagnosticSeverity::WARNING => styles.warning, + DiagnosticSeverity::ERROR => styles.error, + _ => Style::default(), + }) + .unwrap_or_default(); + + // remove background as it is distracting in the picker list + style.bg = None; + + let code = self + .diag + .code + .as_ref() + .map(|c| match c { + NumberOrString::Number(n) => n.to_string(), + NumberOrString::String(s) => s.to_string(), + }) + .unwrap_or_default(); + + let truncated_path = path::get_truncated_path(self.url.path()) + .to_string_lossy() + .into_owned(); + + Spans::from(vec![ + Span::styled( + self.diag.source.clone().unwrap_or_default(), + style.add_modifier(Modifier::BOLD), + ), + Span::raw(": "), + Span::styled(truncated_path, style), + Span::raw(" - "), + Span::styled(code, style.add_modifier(Modifier::BOLD)), + Span::raw(": "), + Span::styled(&self.diag.message, style), + ]) + } +} + fn location_to_file_location(location: &lsp::Location) -> FileLocation { let path = location.uri.to_file_path().unwrap(); let line = Some(( @@ -93,29 +200,14 @@ fn sym_picker( offset_encoding: OffsetEncoding, ) -> FilePicker<lsp::SymbolInformation> { // TODO: drop current_path comparison and instead use workspace: bool flag? - let current_path2 = current_path.clone(); FilePicker::new( symbols, - move |symbol| { - if current_path.as_ref() == Some(&symbol.location.uri) { - symbol.name.as_str().into() - } else { - match symbol.location.uri.to_file_path() { - Ok(path) => { - let relative_path = helix_core::path::get_relative_path(path.as_path()) - .to_string_lossy() - .into_owned(); - format!("{} ({})", &symbol.name, relative_path).into() - } - Err(_) => format!("{} ({})", &symbol.name, &symbol.location.uri).into(), - } - } - }, + current_path.clone(), move |cx, symbol, action| { let (view, doc) = current!(cx.editor); push_jump(view, doc); - if current_path2.as_ref() != Some(&symbol.location.uri) { + if current_path.as_ref() != Some(&symbol.location.uri) { let uri = &symbol.location.uri; let path = match uri.to_file_path() { Ok(path) => path, @@ -155,7 +247,7 @@ fn diag_picker( diagnostics: BTreeMap<lsp::Url, Vec<lsp::Diagnostic>>, current_path: Option<lsp::Url>, offset_encoding: OffsetEncoding, -) -> FilePicker<(lsp::Url, lsp::Diagnostic)> { +) -> FilePicker<PickerDiagnostic> { // TODO: drop current_path comparison and instead use workspace: bool flag? // flatten the map to a vec of (url, diag) pairs @@ -163,59 +255,24 @@ fn diag_picker( for (url, diags) in diagnostics { flat_diag.reserve(diags.len()); for diag in diags { - flat_diag.push((url.clone(), diag)); + flat_diag.push(PickerDiagnostic { + url: url.clone(), + diag, + }); } } - let hint = cx.editor.theme.get("hint"); - let info = cx.editor.theme.get("info"); - let warning = cx.editor.theme.get("warning"); - let error = cx.editor.theme.get("error"); + let styles = DiagnosticStyles { + hint: cx.editor.theme.get("hint"), + info: cx.editor.theme.get("info"), + warning: cx.editor.theme.get("warning"), + error: cx.editor.theme.get("error"), + }; FilePicker::new( flat_diag, - move |(url, diag)| { - let mut style = diag - .severity - .map(|s| match s { - DiagnosticSeverity::HINT => hint, - DiagnosticSeverity::INFORMATION => info, - DiagnosticSeverity::WARNING => warning, - DiagnosticSeverity::ERROR => error, - _ => Style::default(), - }) - .unwrap_or_default(); - - // remove background as it is distracting in the picker list - style.bg = None; - - let code = diag - .code - .as_ref() - .map(|c| match c { - NumberOrString::Number(n) => n.to_string(), - NumberOrString::String(s) => s.to_string(), - }) - .unwrap_or_default(); - - let truncated_path = path::get_truncated_path(url.path()) - .to_string_lossy() - .into_owned(); - - Spans::from(vec![ - Span::styled( - diag.source.clone().unwrap_or_default(), - style.add_modifier(Modifier::BOLD), - ), - Span::raw(": "), - Span::styled(truncated_path, style), - Span::raw(" - "), - Span::styled(code, style.add_modifier(Modifier::BOLD)), - Span::raw(": "), - Span::styled(&diag.message, style), - ]) - }, - move |cx, (url, diag), action| { + styles, + move |cx, PickerDiagnostic { url, diag }, action| { if current_path.as_ref() == Some(url) { let (view, doc) = current!(cx.editor); push_jump(view, doc); @@ -233,7 +290,7 @@ fn diag_picker( align_view(doc, view, Align::Center); } }, - move |_editor, (url, diag)| { + move |_editor, PickerDiagnostic { url, diag }| { let location = lsp::Location::new(url.clone(), diag.range); Some(location_to_file_location(&location)) }, @@ -343,10 +400,11 @@ pub fn workspace_diagnostics_picker(cx: &mut Context) { } impl ui::menu::Item for lsp::CodeActionOrCommand { - fn label(&self) -> &str { + type Data = (); + fn label(&self, _data: &Self::Data) -> Spans { match self { - lsp::CodeActionOrCommand::CodeAction(action) => action.title.as_str(), - lsp::CodeActionOrCommand::Command(command) => command.title.as_str(), + lsp::CodeActionOrCommand::CodeAction(action) => action.title.as_str().into(), + lsp::CodeActionOrCommand::Command(command) => command.title.as_str().into(), } } } @@ -391,7 +449,7 @@ pub fn code_action(cx: &mut Context) { return; } - let mut picker = ui::Menu::new(actions, move |editor, code_action, event| { + let mut picker = ui::Menu::new(actions, (), move |editor, code_action, event| { if event != PromptEvent::Validate { return; } @@ -619,6 +677,7 @@ pub fn apply_workspace_edit( } } } + fn goto_impl( editor: &mut Editor, compositor: &mut Compositor, @@ -637,26 +696,7 @@ fn goto_impl( _locations => { let picker = FilePicker::new( locations, - move |location| { - let file: Cow<'_, str> = (location.uri.scheme() == "file") - .then(|| { - location - .uri - .to_file_path() - .map(|path| { - // strip root prefix - path.strip_prefix(&cwdir) - .map(|path| path.to_path_buf()) - .unwrap_or(path) - }) - .map(|path| Cow::from(path.to_string_lossy().into_owned())) - .ok() - }) - .flatten() - .unwrap_or_else(|| location.uri.as_str().into()); - let line = location.range.start.line; - format!("{}:{}", file, line).into() - }, + cwdir, move |cx, location, action| { jump_to_location(cx.editor, location, offset_encoding, action) }, |