@@ -1,20 +1,25 @@
1use helix_lsp::{
1use helix_lsp::{
2− block_on, lsp,
2+ block_on,
3+ lsp::{self, DiagnosticSeverity, NumberOrString},
3 util::{diagnostic_to_lsp_diagnostic, lsp_pos_to_pos, lsp_range_to_range, range_to_lsp_range},
4 util::{diagnostic_to_lsp_diagnostic, lsp_pos_to_pos, lsp_range_to_range, range_to_lsp_range},
4 OffsetEncoding,
5 OffsetEncoding,
7+use tui::text::{Span, Spans};
7use super::{align_view, push_jump, Align, Context, Editor};
9use super::{align_view, push_jump, Align, Context, Editor};
9−use helix_core::Selection;
11+use helix_core::{path, Selection};
10−use helix_view::editor::Action;
12+use helix_view::{
14+ theme::{Modifier, Style},
12use crate::{
17use crate::{
13 compositor::{self, Compositor},
18 compositor::{self, Compositor},
14 ui::{self, overlay::overlayed, FileLocation, FilePicker, Popup, PromptEvent},
19 ui::{self, overlay::overlayed, FileLocation, FilePicker, Popup, PromptEvent},
17−use std::borrow::Cow;
22+use std::{borrow::Cow, collections::BTreeMap};
19/// Gets the language server that is attached to a document, and
24/// Gets the language server that is attached to a document, and
20/// if it's not active displays a status message. Using this macro
25/// if it's not active displays a status message. Using this macro
@@ -145,6 +150,97 @@ fn sym_picker(
145 .truncate_start(false)
150 .truncate_start(false)
155+ diagnostics: BTreeMap<lsp::Url, Vec<lsp::Diagnostic>>,
156+ current_path: Option<lsp::Url>,
157+ offset_encoding: OffsetEncoding,
158+) -> FilePicker<(lsp::Url, lsp::Diagnostic)> {
159+ // TODO: drop current_path comparison and instead use workspace: bool flag?
161+ // flatten the map to a vec of (url, diag) pairs
162+ let mut flat_diag = Vec::new();
163+ for (url, diags) in diagnostics {
164+ flat_diag.reserve(diags.len());
166+ flat_diag.push((url.clone(), diag));
170+ let hint = cx.editor.theme.get("hint");
171+ let info = cx.editor.theme.get("info");
172+ let warning = cx.editor.theme.get("warning");
173+ let error = cx.editor.theme.get("error");
177+ move |(url, diag)| {
178+ let mut style = diag
181+ DiagnosticSeverity::HINT => hint,
182+ DiagnosticSeverity::INFORMATION => info,
183+ DiagnosticSeverity::WARNING => warning,
184+ DiagnosticSeverity::ERROR => error,
185+ _ => Style::default(),
187+ .unwrap_or_default();
189+ // remove background as it is distracting in the picker list
196+ NumberOrString::Number(n) => n.to_string(),
197+ NumberOrString::String(s) => s.to_string(),
199+ .unwrap_or_default();
201+ let truncated_path = path::get_truncated_path(url.path())
207+ diag.source.clone().unwrap_or_default(),
208+ style.add_modifier(Modifier::BOLD),
211+ Span::styled(truncated_path, style),
213+ Span::styled(code, style.add_modifier(Modifier::BOLD)),
215+ Span::styled(&diag.message, style),
218+ move |cx, (url, diag), action| {
219+ if current_path.as_ref() == Some(url) {
220+ let (view, doc) = current!(cx.editor);
221+ push_jump(view, doc);
223+ let path = url.to_file_path().unwrap();
224+ cx.editor.open(&path, action).expect("editor.open failed");
227+ let (view, doc) = current!(cx.editor);
229+ if let Some(range) = lsp_range_to_range(doc.text(), diag.range, offset_encoding) {
230+ // we flip the range so that the cursor sits on the start of the symbol
231+ // (for example start of the function).
232+ doc.set_selection(view.id, Selection::single(range.head, range.anchor));
233+ align_view(doc, view, Align::Center);
236+ move |_editor, (url, diag)| {
237+ let location = lsp::Location::new(url.clone(), diag.range);
238+ Some(location_to_file_location(&location))
241+ .truncate_start(false)
148pub fn symbol_picker(cx: &mut Context) {
244pub fn symbol_picker(cx: &mut Context) {
149 fn nested_to_flat(
245 fn nested_to_flat(
150 list: &mut Vec<lsp::SymbolInformation>,
246 list: &mut Vec<lsp::SymbolInformation>,
@@ -215,6 +311,37 @@ pub fn workspace_symbol_picker(cx: &mut Context) {
314+pub fn diagnostics_picker(cx: &mut Context) {
315+ let doc = doc!(cx.editor);
316+ let language_server = language_server!(cx.editor, doc);
317+ if let Some(current_url) = doc.url() {
318+ let offset_encoding = language_server.offset_encoding();
319+ let diagnostics = cx
324+ .unwrap_or_default();
325+ let picker = diag_picker(
327+ [(current_url.clone(), diagnostics)].into(),
331+ cx.push_layer(Box::new(overlayed(picker)));
335+pub fn workspace_diagnostics_picker(cx: &mut Context) {
336+ let doc = doc!(cx.editor);
337+ let language_server = language_server!(cx.editor, doc);
338+ let current_url = doc.url();
339+ let offset_encoding = language_server.offset_encoding();
340+ let diagnostics = cx.editor.diagnostics.clone();
341+ let picker = diag_picker(cx, diagnostics, current_url, offset_encoding);
342+ cx.push_layer(Box::new(overlayed(picker)));
218impl ui::menu::Item for lsp::CodeActionOrCommand {
345impl ui::menu::Item for lsp::CodeActionOrCommand {
219 fn label(&self) -> &str {
346 fn label(&self) -> &str {
220 match self {
347 match self {