From 262ab16c994f721ccb2d1ee21222f7cec556ecad Mon Sep 17 00:00:00 2001 From: Dario Oddenino Date: Tue, 11 Oct 2022 14:00:30 +0200 Subject: [PATCH] Jump to next/prev diagnostic in workspace (#3116) Implements functions and keybindings to move to next/previous/first/last diagnostic in the workspace --- helix-term/src/commands.rs | 111 +++++++++++++++++++++------- helix-term/src/commands/lsp.rs | 119 ++++++++++++++++++++++++++++++- helix-term/src/keymap/default.rs | 4 ++ 3 files changed, 206 insertions(+), 28 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index ad84c85c88d44..2548d10bec4cf 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -53,8 +53,12 @@ use crate::{ use crate::job::{self, Job, Jobs}; use futures_util::{FutureExt, StreamExt}; -use std::{collections::HashMap, fmt, future::Future}; -use std::{collections::HashSet, num::NonZeroUsize}; +use std::{ + collections::{BTreeMap, HashMap, HashSet}, + fmt, + future::Future, + num::NonZeroUsize, +}; use std::{ borrow::Cow, @@ -304,6 +308,10 @@ impl MappableCommand { goto_last_diag, "Goto last diagnostic", goto_next_diag, "Goto next diagnostic", goto_prev_diag, "Goto previous diagnostic", + goto_first_diag_workspace, "Goto first diagnostic in workspace", + goto_last_diag_workspace, "Goto last diagnostic in workspace", + goto_next_diag_workspace, "Goto next diagnostic in workspace", + goto_prev_diag_workspace, "Goto previous diagnostic in workspace", goto_line_start, "Goto line start", goto_line_end, "Goto line end", goto_next_buffer, "Goto next buffer", @@ -2800,7 +2808,7 @@ fn exit_select_mode(cx: &mut Context) { } } -fn goto_pos(editor: &mut Editor, pos: usize) { +pub fn goto_pos(editor: &mut Editor, pos: usize) { let (view, doc) = current!(editor); push_jump(view, doc); @@ -2826,23 +2834,81 @@ fn goto_last_diag(cx: &mut Context) { goto_pos(cx.editor, pos); } -fn goto_next_diag(cx: &mut Context) { - let editor = &mut cx.editor; - let (view, doc) = current!(editor); - +/// Finds the position of the next/previous diagnostic. +pub fn get_next_diag_pos( + view: &mut View, + doc: &mut Document, + direction: Direction, +) -> Option { let cursor_pos = doc .selection(view.id) .primary() .cursor(doc.text().slice(..)); - let diag = doc - .diagnostics() - .iter() - .find(|diag| diag.range.start > cursor_pos) - .or_else(|| doc.diagnostics().first()); + let diag = match direction { + Direction::Forward => doc + .diagnostics() + .iter() + .find(|diag| diag.range.start > cursor_pos), + Direction::Backward => doc + .diagnostics() + .iter() + .find(|diag| diag.range.start < cursor_pos), + }; - let pos = match diag { - Some(diag) => diag.range.start, + return diag.map(|d| d.range.start); +} + +/// Finds the next/previous document with diagnostics in it. +pub fn get_next_diag_doc( + doc: &mut Document, + editor_diagnostics: BTreeMap>, + direction: Direction, +) -> Option { + let current_url = doc.url(); + let diagnostics = editor_diagnostics.iter(); + let next_diags = match direction { + Direction::Forward => { + let mut iter = diagnostics + .filter(|(_, diags)| !diags.is_empty()) + .skip_while(|(url, _)| Some(*url) != current_url.as_ref()); + iter.next(); + iter.next().or_else(|| { + editor_diagnostics + .iter() + .filter(|(_, diags)| !diags.is_empty()) + .next() + }) + } + Direction::Backward => { + let mut iter = diagnostics + .rev() + .filter(|(_, diags)| !diags.is_empty()) + .skip_while(|(url, _)| Some(*url) != current_url.as_ref()); + iter.next(); // skip current + iter.next().or_else(|| { + editor_diagnostics + .iter() + .filter(|(_, diags)| !diags.is_empty()) + .last() + }) + } + }; + match next_diags { + Some((url, _)) => url.to_file_path().ok(), + None => None, + } +} + +fn goto_next_diag(cx: &mut Context) { + let editor = &mut cx.editor; + let (view, doc) = current!(editor); + + let diag_pos = get_next_diag_pos(view, doc, Direction::Forward) + .or_else(|| doc.diagnostics().first().map(|d| d.range.start)); + + let pos = match diag_pos { + Some(pos) => pos, None => return, }; @@ -2853,20 +2919,11 @@ fn goto_prev_diag(cx: &mut Context) { let editor = &mut cx.editor; let (view, doc) = current!(editor); - let cursor_pos = doc - .selection(view.id) - .primary() - .cursor(doc.text().slice(..)); - - let diag = doc - .diagnostics() - .iter() - .rev() - .find(|diag| diag.range.start < cursor_pos) - .or_else(|| doc.diagnostics().last()); + let diag_pos = get_next_diag_pos(view, doc, Direction::Backward) + .or_else(|| doc.diagnostics().last().map(|d| d.range.start)); - let pos = match diag { - Some(diag) => diag.range.start, + let pos = match diag_pos { + Some(pos) => pos, None => return, }; diff --git a/helix-term/src/commands/lsp.rs b/helix-term/src/commands/lsp.rs index 3fa5c96fff83b..eba5189b04a21 100644 --- a/helix-term/src/commands/lsp.rs +++ b/helix-term/src/commands/lsp.rs @@ -8,10 +8,11 @@ use tui::text::{Span, Spans}; use super::{align_view, push_jump, Align, Context, Editor, Open}; -use helix_core::{path, Selection}; +use helix_core::{movement::Direction, path, Selection}; use helix_view::{apply_transaction, editor::Action, theme::Style}; use crate::{ + commands::{get_next_diag_doc, get_next_diag_pos, goto_pos}, compositor::{self, Compositor}, ui::{ self, lsp::SignatureHelp, overlay::overlayed, FileLocation, FilePicker, Popup, PromptEvent, @@ -411,6 +412,122 @@ pub fn workspace_diagnostics_picker(cx: &mut Context) { cx.push_layer(Box::new(overlayed(picker))); } +pub fn goto_first_diag_workspace(cx: &mut Context) { + let editor = &mut cx.editor; + + let mut diagnostics = editor + .diagnostics + .iter() + .filter(|(_, diags)| !diags.is_empty()) + .map(|(url, _)| url); + + let diag = diagnostics.next(); + match diag { + Some(url) => { + let path = url.to_file_path().unwrap(); + editor + .open(&path, Action::Replace) + .expect("editor.open failed"); + let doc = doc!(editor); + let pos = match doc.diagnostics().first() { + Some(diag) => diag.range.start, + None => return, + }; + goto_pos(editor, pos); + } + None => return, + } +} + +pub fn goto_last_diag_workspace(cx: &mut Context) { + let editor = &mut cx.editor; + + let diagnostics = editor + .diagnostics + .iter() + .filter(|(_, diags)| !diags.is_empty()) + .map(|(url, _)| url); + + let diag = diagnostics.last(); + match diag { + Some(url) => { + let path = url.to_file_path().unwrap(); + editor + .open(&path, Action::Replace) + .expect("editor.open failed"); + let doc = doc!(editor); + let pos = match doc.diagnostics().last() { + Some(diag) => diag.range.start, + None => return, + }; + goto_pos(editor, pos); + } + None => return, + } +} + +pub fn goto_next_diag_workspace(cx: &mut Context) { + let editor = &mut cx.editor; + let (view, doc) = current!(editor); + + let doc_next_diag_pos = get_next_diag_pos(view, doc, Direction::Forward); + + match doc_next_diag_pos { + Some(pos) => goto_pos(editor, pos), + None => { + let diagnostics = editor.diagnostics.clone(); + let next_doc = get_next_diag_doc(doc, diagnostics, Direction::Forward); + match next_doc { + Some(path) => { + editor + .open(&path, Action::Replace) + .expect("editor.open failed"); + let doc = doc!(editor); + match doc.diagnostics().get(0) { + Some(diag) => { + let pos = diag.range.start; + goto_pos(editor, pos) + } + None => return, + } + } + None => return, + } + } + } +} + +pub fn goto_prev_diag_workspace(cx: &mut Context) { + let editor = &mut cx.editor; + let (view, doc) = current!(editor); + + let doc_prev_diag_pos = get_next_diag_pos(view, doc, Direction::Backward); + + match doc_prev_diag_pos { + Some(pos) => goto_pos(editor, pos), + None => { + let diagnostics = editor.diagnostics.clone(); + let next_doc = get_next_diag_doc(doc, diagnostics, Direction::Backward); + match next_doc { + Some(path) => { + editor + .open(&path, Action::Replace) + .expect("editor.open failed"); + let doc = doc!(editor); + match doc.diagnostics().last() { + Some(diag) => { + let pos = diag.range.start; + goto_pos(editor, pos) + } + None => return, + } + } + None => return, + } + } + } +} + impl ui::menu::Item for lsp::CodeActionOrCommand { type Data = (); fn label(&self, _data: &Self::Data) -> Spans { diff --git a/helix-term/src/keymap/default.rs b/helix-term/src/keymap/default.rs index 6c327ee6bc47e..4010ce953bd5e 100644 --- a/helix-term/src/keymap/default.rs +++ b/helix-term/src/keymap/default.rs @@ -100,6 +100,8 @@ pub fn default() -> HashMap { "[" => { "Left bracket" "d" => goto_prev_diag, "D" => goto_first_diag, + "w" => goto_prev_diag_workspace, + "W" => goto_first_diag_workspace, "f" => goto_prev_function, "c" => goto_prev_class, "a" => goto_prev_parameter, @@ -111,6 +113,8 @@ pub fn default() -> HashMap { "]" => { "Right bracket" "d" => goto_next_diag, "D" => goto_last_diag, + "w" => goto_next_diag_workspace, + "W" => goto_last_diag_workspace, "f" => goto_next_function, "c" => goto_next_class, "a" => goto_next_parameter,