mirror of
https://github.com/helix-editor/helix.git
synced 2024-11-22 09:26:19 +04:00
add "goto first/next workspace diagnostic" commands
Adds - goto_first_diag_workspace - goto_first_error_workspace - goto_first_warning_workspace - goto_next_diag_workspace - goto_next_error_workspace - goto_next_warning_workspace
This commit is contained in:
parent
a7651f5bf0
commit
aab0239fbe
@ -3,6 +3,8 @@
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::Selection;
|
||||
|
||||
/// Describes the severity level of a [`Diagnostic`].
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
@ -62,6 +64,18 @@ pub struct Diagnostic {
|
||||
pub data: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
impl Diagnostic {
|
||||
/// Returns a single selection spanning the range of the diagnostic.
|
||||
pub fn single_selection(&self) -> Selection {
|
||||
Selection::single(self.range.start, self.range.end)
|
||||
}
|
||||
|
||||
/// Returns a single reversed selection spanning the range of the diagnostic.
|
||||
pub fn single_selection_rev(&self) -> Selection {
|
||||
Selection::single(self.range.end, self.range.start)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO turn this into an enum + feature flag when lsp becomes optional
|
||||
pub type DiagnosticProvider = LanguageServerId;
|
||||
|
||||
|
@ -416,6 +416,12 @@ pub fn doc(&self) -> &str {
|
||||
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_first_error_workspace, "Goto first Error diagnostic in workspace",
|
||||
goto_first_warning_workspace, "Goto first Warning diagnostic in workspace",
|
||||
goto_next_diag_workspace, "Goto next diagnostic in workspace",
|
||||
goto_next_error_workspace, "Goto next Error diagnostic in workspace",
|
||||
goto_next_warning_workspace, "Goto next Warning diagnostic in workspace",
|
||||
goto_next_change, "Goto next change",
|
||||
goto_prev_change, "Goto previous change",
|
||||
goto_first_change, "Goto first change",
|
||||
@ -2846,13 +2852,7 @@ fn flip_selections(cx: &mut Context) {
|
||||
|
||||
fn ensure_selections_forward(cx: &mut Context) {
|
||||
let (view, doc) = current!(cx.editor);
|
||||
|
||||
let selection = doc
|
||||
.selection(view.id)
|
||||
.clone()
|
||||
.transform(|r| r.with_direction(Direction::Forward));
|
||||
|
||||
doc.set_selection(view.id, selection);
|
||||
helix_view::ensure_selections_forward(view, doc);
|
||||
}
|
||||
|
||||
fn enter_insert_mode(cx: &mut Context) {
|
||||
@ -3714,6 +3714,54 @@ fn goto_prev_diag(cx: &mut Context) {
|
||||
cx.editor.apply_motion(motion)
|
||||
}
|
||||
|
||||
fn goto_next_diag_workspace(cx: &mut Context) {
|
||||
goto_next_diag_workspace_impl(cx, None)
|
||||
}
|
||||
|
||||
fn goto_next_error_workspace(cx: &mut Context) {
|
||||
goto_next_diag_workspace_impl(cx, Some(helix_core::diagnostic::Severity::Error))
|
||||
}
|
||||
|
||||
fn goto_next_warning_workspace(cx: &mut Context) {
|
||||
goto_next_diag_workspace_impl(cx, Some(helix_core::diagnostic::Severity::Warning))
|
||||
}
|
||||
|
||||
fn goto_next_diag_workspace_impl(
|
||||
cx: &mut Context,
|
||||
severity_filter: Option<helix_core::diagnostic::Severity>,
|
||||
) {
|
||||
let diag = helix_view::next_diagnostic_in_workspace(&cx.editor, severity_filter);
|
||||
|
||||
// wrap around
|
||||
let diag =
|
||||
diag.or_else(|| helix_view::first_diagnostic_in_workspace(&cx.editor, severity_filter));
|
||||
|
||||
if let Some(diag) = diag {
|
||||
lsp::jump_to_diagnostic(cx, diag.into_owned());
|
||||
}
|
||||
}
|
||||
|
||||
fn goto_first_diag_workspace(cx: &mut Context) {
|
||||
goto_first_diag_workspace_impl(cx, None)
|
||||
}
|
||||
|
||||
fn goto_first_error_workspace(cx: &mut Context) {
|
||||
goto_first_diag_workspace_impl(cx, Some(helix_core::diagnostic::Severity::Error))
|
||||
}
|
||||
|
||||
fn goto_first_warning_workspace(cx: &mut Context) {
|
||||
goto_first_diag_workspace_impl(cx, Some(helix_core::diagnostic::Severity::Warning))
|
||||
}
|
||||
|
||||
fn goto_first_diag_workspace_impl(
|
||||
cx: &mut Context,
|
||||
severity_filter: Option<helix_core::diagnostic::Severity>,
|
||||
) {
|
||||
if let Some(diag) = helix_view::first_diagnostic_in_workspace(&cx.editor, severity_filter) {
|
||||
lsp::jump_to_diagnostic(cx, diag.into_owned());
|
||||
}
|
||||
}
|
||||
|
||||
fn goto_first_change(cx: &mut Context) {
|
||||
goto_first_change_impl(cx, false);
|
||||
}
|
||||
|
@ -127,7 +127,7 @@ fn jump_to_location(
|
||||
jump_to_position(editor, path, location.range, offset_encoding, action);
|
||||
}
|
||||
|
||||
fn jump_to_position(
|
||||
pub fn jump_to_position(
|
||||
editor: &mut Editor,
|
||||
path: &Path,
|
||||
range: lsp::Range,
|
||||
@ -159,6 +159,19 @@ fn jump_to_position(
|
||||
}
|
||||
}
|
||||
|
||||
pub fn jump_to_diagnostic(cx: &mut Context, diagnostic: helix_view::WorkspaceDiagnostic<'static>) {
|
||||
let path = diagnostic.path;
|
||||
let range = diagnostic.diagnostic.range;
|
||||
let offset_encoding = diagnostic.offset_encoding;
|
||||
|
||||
let motion = move |editor: &mut Editor| {
|
||||
jump_to_position(editor, &path, range, offset_encoding, Action::Replace);
|
||||
let (view, doc) = current!(editor);
|
||||
helix_view::ensure_selections_forward(view, doc);
|
||||
};
|
||||
cx.editor.apply_motion(motion);
|
||||
}
|
||||
|
||||
fn display_symbol_kind(kind: lsp::SymbolKind) -> &'static str {
|
||||
match kind {
|
||||
lsp::SymbolKind::FILE => "file",
|
||||
|
@ -1895,6 +1895,22 @@ pub fn position(
|
||||
)
|
||||
}
|
||||
|
||||
pub fn lsp_severity_to_severity(
|
||||
severity: lsp::DiagnosticSeverity,
|
||||
) -> Option<helix_core::diagnostic::Severity> {
|
||||
use helix_core::diagnostic::Severity::*;
|
||||
match severity {
|
||||
lsp::DiagnosticSeverity::ERROR => Some(Error),
|
||||
lsp::DiagnosticSeverity::WARNING => Some(Warning),
|
||||
lsp::DiagnosticSeverity::INFORMATION => Some(Info),
|
||||
lsp::DiagnosticSeverity::HINT => Some(Hint),
|
||||
severity => {
|
||||
log::error!("unrecognized diagnostic severity: {:?}", severity);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn lsp_diagnostic_to_diagnostic(
|
||||
text: &Rope,
|
||||
language_config: Option<&LanguageConfiguration>,
|
||||
@ -1902,7 +1918,7 @@ pub fn lsp_diagnostic_to_diagnostic(
|
||||
language_server_id: LanguageServerId,
|
||||
offset_encoding: helix_lsp::OffsetEncoding,
|
||||
) -> Option<Diagnostic> {
|
||||
use helix_core::diagnostic::{Range, Severity::*};
|
||||
use helix_core::diagnostic::Range;
|
||||
|
||||
// TODO: convert inside server
|
||||
let start =
|
||||
@ -1920,16 +1936,7 @@ pub fn lsp_diagnostic_to_diagnostic(
|
||||
return None;
|
||||
};
|
||||
|
||||
let severity = diagnostic.severity.and_then(|severity| match severity {
|
||||
lsp::DiagnosticSeverity::ERROR => Some(Error),
|
||||
lsp::DiagnosticSeverity::WARNING => Some(Warning),
|
||||
lsp::DiagnosticSeverity::INFORMATION => Some(Info),
|
||||
lsp::DiagnosticSeverity::HINT => Some(Hint),
|
||||
severity => {
|
||||
log::error!("unrecognized diagnostic severity: {:?}", severity);
|
||||
None
|
||||
}
|
||||
});
|
||||
let severity = diagnostic.severity.and_then(Self::lsp_severity_to_severity);
|
||||
|
||||
if let Some(lang_conf) = language_config {
|
||||
if let Some(severity) = severity {
|
||||
|
@ -18,7 +18,7 @@
|
||||
pub mod tree;
|
||||
pub mod view;
|
||||
|
||||
use std::num::NonZeroUsize;
|
||||
use std::{borrow::Cow, num::NonZeroUsize, path::Path};
|
||||
|
||||
// uses NonZeroUsize so Option<DocumentId> use a byte rather than two
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
|
||||
@ -72,8 +72,152 @@ pub fn align_view(doc: &mut Document, view: &View, align: Align) {
|
||||
doc.set_view_offset(view.id, view_offset);
|
||||
}
|
||||
|
||||
/// Returns the left-side position of the primary selection.
|
||||
pub fn primary_cursor(view: &View, doc: &Document) -> usize {
|
||||
doc.selection(view.id)
|
||||
.primary()
|
||||
.cursor(doc.text().slice(..))
|
||||
}
|
||||
|
||||
/// Returns the next diagnostic in the document if any.
|
||||
///
|
||||
/// This does not wrap-around.
|
||||
pub fn next_diagnostic_in_doc<'d>(
|
||||
view: &View,
|
||||
doc: &'d Document,
|
||||
severity_filter: Option<helix_core::diagnostic::Severity>,
|
||||
) -> Option<&'d Diagnostic> {
|
||||
let cursor = primary_cursor(view, doc);
|
||||
doc.diagnostics()
|
||||
.iter()
|
||||
.filter(|diagnostic| diagnostic.severity >= severity_filter)
|
||||
.find(|diag| diag.range.start > cursor)
|
||||
}
|
||||
|
||||
/// Returns the previous diagnostic in the document if any.
|
||||
///
|
||||
/// This does not wrap-around.
|
||||
pub fn prev_diagnostic_in_doc<'d>(
|
||||
view: &View,
|
||||
doc: &'d Document,
|
||||
severity_filter: Option<helix_core::diagnostic::Severity>,
|
||||
) -> Option<&'d Diagnostic> {
|
||||
let cursor = primary_cursor(view, doc);
|
||||
doc.diagnostics()
|
||||
.iter()
|
||||
.rev()
|
||||
.filter(|diagnostic| diagnostic.severity >= severity_filter)
|
||||
.find(|diag| diag.range.start < cursor)
|
||||
}
|
||||
|
||||
pub struct WorkspaceDiagnostic<'e> {
|
||||
pub path: Cow<'e, Path>,
|
||||
pub diagnostic: Cow<'e, helix_lsp::lsp::Diagnostic>,
|
||||
pub offset_encoding: OffsetEncoding,
|
||||
}
|
||||
impl<'e> WorkspaceDiagnostic<'e> {
|
||||
pub fn into_owned(self) -> WorkspaceDiagnostic<'static> {
|
||||
WorkspaceDiagnostic {
|
||||
path: Cow::Owned(self.path.into_owned()),
|
||||
diagnostic: Cow::Owned(self.diagnostic.into_owned()),
|
||||
offset_encoding: self.offset_encoding,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn workspace_diagnostics<'e>(
|
||||
editor: &'e Editor,
|
||||
severity_filter: Option<helix_core::diagnostic::Severity>,
|
||||
) -> impl Iterator<Item = WorkspaceDiagnostic<'e>> {
|
||||
editor
|
||||
.diagnostics
|
||||
.iter()
|
||||
.filter_map(|(uri, diagnostics)| {
|
||||
// Extract Path from diagnostic Uri, skipping diagnostics that don't have a path.
|
||||
uri.as_path().map(|p| (p, diagnostics))
|
||||
})
|
||||
.flat_map(|(path, diagnostics)| {
|
||||
diagnostics
|
||||
.iter()
|
||||
.map(move |(diagnostic, language_server_id)| (path, diagnostic, language_server_id))
|
||||
})
|
||||
.filter(move |(_, diagnostic, _)| {
|
||||
// Filter by severity
|
||||
let severity = diagnostic
|
||||
.severity
|
||||
.and_then(Document::lsp_severity_to_severity);
|
||||
severity >= severity_filter
|
||||
})
|
||||
.map(|(path, diag, language_server_id)| {
|
||||
// Map language server ID to offset encoding
|
||||
let offset_encoding = editor
|
||||
.language_server_by_id(*language_server_id)
|
||||
.map(|client| client.offset_encoding())
|
||||
.unwrap_or_default();
|
||||
(path, diag, offset_encoding)
|
||||
})
|
||||
.map(|(path, diagnostic, offset_encoding)| WorkspaceDiagnostic {
|
||||
path: Cow::Borrowed(path),
|
||||
diagnostic: Cow::Borrowed(diagnostic),
|
||||
offset_encoding,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn first_diagnostic_in_workspace(
|
||||
editor: &Editor,
|
||||
severity_filter: Option<helix_core::diagnostic::Severity>,
|
||||
) -> Option<WorkspaceDiagnostic> {
|
||||
workspace_diagnostics(editor, severity_filter).next()
|
||||
}
|
||||
|
||||
pub fn next_diagnostic_in_workspace(
|
||||
editor: &Editor,
|
||||
severity_filter: Option<helix_core::diagnostic::Severity>,
|
||||
) -> Option<WorkspaceDiagnostic> {
|
||||
let (view, doc) = current_ref!(editor);
|
||||
|
||||
let Some(current_doc_path) = doc.path() else {
|
||||
return first_diagnostic_in_workspace(editor, severity_filter);
|
||||
};
|
||||
|
||||
let cursor = primary_cursor(view, doc);
|
||||
|
||||
workspace_diagnostics(editor, severity_filter)
|
||||
.filter(|d| {
|
||||
// Skip diagnostics before the current document
|
||||
d.path >= current_doc_path.as_path()
|
||||
})
|
||||
.filter(|d| {
|
||||
// Skip diagnostics before the primary cursor in the current document
|
||||
if d.path == current_doc_path.as_path() {
|
||||
let Some(start) = helix_lsp::util::lsp_pos_to_pos(
|
||||
doc.text(),
|
||||
d.diagnostic.range.start,
|
||||
d.offset_encoding,
|
||||
) else {
|
||||
return false;
|
||||
};
|
||||
if start <= cursor {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
})
|
||||
.next()
|
||||
}
|
||||
|
||||
pub fn ensure_selections_forward(view: &View, doc: &mut Document) {
|
||||
let selection = doc
|
||||
.selection(view.id)
|
||||
.clone()
|
||||
.transform(|r| r.with_direction(Direction::Forward));
|
||||
|
||||
doc.set_selection(view.id, selection);
|
||||
}
|
||||
|
||||
pub use document::Document;
|
||||
pub use editor::Editor;
|
||||
use helix_core::char_idx_at_visual_offset;
|
||||
use helix_core::{char_idx_at_visual_offset, movement::Direction, Diagnostic};
|
||||
use helix_lsp::OffsetEncoding;
|
||||
pub use theme::Theme;
|
||||
pub use view::View;
|
||||
|
Loading…
Reference in New Issue
Block a user