mirror of
https://github.com/helix-editor/helix.git
synced 2024-11-22 17:36: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 serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::Selection;
|
||||||
|
|
||||||
/// Describes the severity level of a [`Diagnostic`].
|
/// Describes the severity level of a [`Diagnostic`].
|
||||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "lowercase")]
|
#[serde(rename_all = "lowercase")]
|
||||||
@ -62,6 +64,18 @@ pub struct Diagnostic {
|
|||||||
pub data: Option<serde_json::Value>,
|
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
|
// TODO turn this into an enum + feature flag when lsp becomes optional
|
||||||
pub type DiagnosticProvider = LanguageServerId;
|
pub type DiagnosticProvider = LanguageServerId;
|
||||||
|
|
||||||
|
@ -416,6 +416,12 @@ pub fn doc(&self) -> &str {
|
|||||||
goto_last_diag, "Goto last diagnostic",
|
goto_last_diag, "Goto last diagnostic",
|
||||||
goto_next_diag, "Goto next diagnostic",
|
goto_next_diag, "Goto next diagnostic",
|
||||||
goto_prev_diag, "Goto previous 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_next_change, "Goto next change",
|
||||||
goto_prev_change, "Goto previous change",
|
goto_prev_change, "Goto previous change",
|
||||||
goto_first_change, "Goto first change",
|
goto_first_change, "Goto first change",
|
||||||
@ -2846,13 +2852,7 @@ fn flip_selections(cx: &mut Context) {
|
|||||||
|
|
||||||
fn ensure_selections_forward(cx: &mut Context) {
|
fn ensure_selections_forward(cx: &mut Context) {
|
||||||
let (view, doc) = current!(cx.editor);
|
let (view, doc) = current!(cx.editor);
|
||||||
|
helix_view::ensure_selections_forward(view, doc);
|
||||||
let selection = doc
|
|
||||||
.selection(view.id)
|
|
||||||
.clone()
|
|
||||||
.transform(|r| r.with_direction(Direction::Forward));
|
|
||||||
|
|
||||||
doc.set_selection(view.id, selection);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn enter_insert_mode(cx: &mut Context) {
|
fn enter_insert_mode(cx: &mut Context) {
|
||||||
@ -3714,6 +3714,54 @@ fn goto_prev_diag(cx: &mut Context) {
|
|||||||
cx.editor.apply_motion(motion)
|
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) {
|
fn goto_first_change(cx: &mut Context) {
|
||||||
goto_first_change_impl(cx, false);
|
goto_first_change_impl(cx, false);
|
||||||
}
|
}
|
||||||
|
@ -127,7 +127,7 @@ fn jump_to_location(
|
|||||||
jump_to_position(editor, path, location.range, offset_encoding, action);
|
jump_to_position(editor, path, location.range, offset_encoding, action);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn jump_to_position(
|
pub fn jump_to_position(
|
||||||
editor: &mut Editor,
|
editor: &mut Editor,
|
||||||
path: &Path,
|
path: &Path,
|
||||||
range: lsp::Range,
|
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 {
|
fn display_symbol_kind(kind: lsp::SymbolKind) -> &'static str {
|
||||||
match kind {
|
match kind {
|
||||||
lsp::SymbolKind::FILE => "file",
|
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(
|
pub fn lsp_diagnostic_to_diagnostic(
|
||||||
text: &Rope,
|
text: &Rope,
|
||||||
language_config: Option<&LanguageConfiguration>,
|
language_config: Option<&LanguageConfiguration>,
|
||||||
@ -1902,7 +1918,7 @@ pub fn lsp_diagnostic_to_diagnostic(
|
|||||||
language_server_id: LanguageServerId,
|
language_server_id: LanguageServerId,
|
||||||
offset_encoding: helix_lsp::OffsetEncoding,
|
offset_encoding: helix_lsp::OffsetEncoding,
|
||||||
) -> Option<Diagnostic> {
|
) -> Option<Diagnostic> {
|
||||||
use helix_core::diagnostic::{Range, Severity::*};
|
use helix_core::diagnostic::Range;
|
||||||
|
|
||||||
// TODO: convert inside server
|
// TODO: convert inside server
|
||||||
let start =
|
let start =
|
||||||
@ -1920,16 +1936,7 @@ pub fn lsp_diagnostic_to_diagnostic(
|
|||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
|
|
||||||
let severity = diagnostic.severity.and_then(|severity| match severity {
|
let severity = diagnostic.severity.and_then(Self::lsp_severity_to_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
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if let Some(lang_conf) = language_config {
|
if let Some(lang_conf) = language_config {
|
||||||
if let Some(severity) = severity {
|
if let Some(severity) = severity {
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
pub mod tree;
|
pub mod tree;
|
||||||
pub mod view;
|
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
|
// uses NonZeroUsize so Option<DocumentId> use a byte rather than two
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
|
#[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);
|
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 document::Document;
|
||||||
pub use editor::Editor;
|
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 theme::Theme;
|
||||||
pub use view::View;
|
pub use view::View;
|
||||||
|
Loading…
Reference in New Issue
Block a user