Skip to content

Jump to next/prev diagnostic in workspace (#3116) #4201

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 83 additions & 27 deletions helix-term/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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);
Expand All @@ -2826,23 +2834,80 @@ 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<usize> {
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,
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<helix_lsp::lsp::Url, Vec<helix_lsp::lsp::Diagnostic>>,
direction: Direction,
) -> Option<PathBuf> {
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()
.find(|(_, diags)| !diags.is_empty())
})
}
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,
};

Expand All @@ -2853,20 +2918,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,
};

Expand Down
119 changes: 118 additions & 1 deletion helix-term/src/commands/lsp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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 => (),
}
}

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 => (),
}
}

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 => (),
}
}
None => (),
}
}
}
}

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 => (),
}
}
None => (),
}
}
}
}

impl ui::menu::Item for lsp::CodeActionOrCommand {
type Data = ();
fn label(&self, _data: &Self::Data) -> Spans {
Expand Down
8 changes: 8 additions & 0 deletions helix-term/src/keymap/default.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,10 @@ pub fn default() -> HashMap<Mode, Keymap> {
"t" => goto_prev_test,
"p" => goto_prev_paragraph,
"space" => add_newline_above,
"[" => { "Workspace"
"d" => goto_prev_diag_workspace,
"D" => goto_first_diag_workspace,
},
},
"]" => { "Right bracket"
"d" => goto_next_diag,
Expand All @@ -118,6 +122,10 @@ pub fn default() -> HashMap<Mode, Keymap> {
"t" => goto_next_test,
"p" => goto_next_paragraph,
"space" => add_newline_below,
"]" => { "Workspace"
"d" => goto_next_diag_workspace,
"D" => goto_last_diag_workspace,
},
},

"/" => search,
Expand Down