Skip to content
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

Add workspace and document diagnostics picker #2013

Merged
merged 41 commits into from
Jun 30, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
7245c7f
Add workspace and document diagnostics picker
hirschenberger Apr 7, 2022
e8a0198
Merge branch 'master' of https://github.com/helix-editor/helix
hirschenberger Apr 8, 2022
f3c6c40
Merge branch 'master' of https://github.com/helix-editor/helix
hirschenberger Apr 14, 2022
b4c5fdd
Fix some of @archseer's annotations
hirschenberger Apr 14, 2022
e164409
Add From<&Spans> impl for String
hirschenberger Apr 14, 2022
2920808
More descriptive parameter names.
hirschenberger Apr 14, 2022
f0e0890
Merge branch 'master' of https://github.com/helix-editor/helix
hirschenberger Apr 19, 2022
6f87bab
Adding From<Cow<str>> impls for Span and Spans
hirschenberger Apr 19, 2022
469c4cf
Merge remote-tracking branch 'upstream/master'
Apr 20, 2022
ce36e55
Merge branch 'master' of https://github.com/helix-editor/helix
Apr 20, 2022
7086952
Merge branch 'master' of https://github.com/helix-editor/helix
hirschenberger Apr 22, 2022
07854d9
Merge branch 'master' of https://github.com/helix-editor/helix
hirschenberger Apr 25, 2022
6ce102c
Merge remote-tracking branch 'upstream/master'
Apr 28, 2022
4819226
Add new keymap entries to docs
Apr 28, 2022
dbd2848
Merge branch 'master' of https://github.com/helix-editor/helix
hirschenberger Apr 28, 2022
60daf1c
Merge remote-tracking branch 'upstream/master'
May 3, 2022
2bfc8f9
Avoid some clones
May 3, 2022
4c5c9df
Merge branch 'master' of https://github.com/helix-editor/helix
hirschenberger May 7, 2022
e94bb27
Merge branch 'master' of https://github.com/helix-editor/helix
hirschenberger Jun 13, 2022
19aa615
Fix api change
hirschenberger Jun 13, 2022
4db6955
Merge branch 'master' of https://github.com/hirschenberger/helix
hirschenberger Jun 14, 2022
9d9aeaf
Update helix-term/src/application.rs
hirschenberger Jun 14, 2022
7740547
Fix a clippy hint
Jun 14, 2022
38ccf7e
Sort diagnostics first by URL and then by severity.
Jun 14, 2022
7301d05
Sort diagnostics first by URL and then by severity.
Jun 14, 2022
6014ec8
Merge branch 'master' of https://github.com/hirschenberger/helix
Jun 14, 2022
154e985
Ignore missing lsp severity entries
Jun 15, 2022
3fdc7d2
Merge branch 'master' of https://github.com/helix-editor/helix
hirschenberger Jun 15, 2022
5a3378f
Merge branch 'master' of https://github.com/hirschenberger/helix
hirschenberger Jun 15, 2022
48f1d4f
Merge branch 'master' of https://github.com/helix-editor/helix
hirschenberger Jun 17, 2022
6779697
Merge branch 'master' of https://github.com/helix-editor/helix
hirschenberger Jun 21, 2022
627bb09
Add truncated filepath
Jun 21, 2022
45ff7fd
Typo
hirschenberger Jun 21, 2022
9477627
Merge branch 'master' of https://github.com/helix-editor/helix
hirschenberger Jun 21, 2022
3bb9498
Merge branch 'master' of https://github.com/helix-editor/helix
Jun 21, 2022
8c5e289
Merge branch 'master' of https://github.com/helix-editor/helix
hirschenberger Jun 22, 2022
973f4dd
Merge branch 'master' of https://github.com/hirschenberger/helix
hirschenberger Jun 22, 2022
50e67c9
Strip cwd from paths and use url-path without schema
hirschenberger Jun 22, 2022
00be70b
Make tests a doctest
hirschenberger Jun 22, 2022
1b94cb7
Merge branch 'master' of https://github.com/helix-editor/helix
hirschenberger Jun 26, 2022
21607e0
Better variable names
hirschenberger Jun 26, 2022
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
2 changes: 2 additions & 0 deletions book/src/keymap.md
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,8 @@ This layer is a kludge of mappings, mostly pickers.
| `k` | Show documentation for item under cursor in a [popup](#popup) (**LSP**) | `hover` |
| `s` | Open document symbol picker (**LSP**) | `symbol_picker` |
| `S` | Open workspace symbol picker (**LSP**) | `workspace_symbol_picker` |
| `g` | Open document diagnosics picker (**LSP**) | `diagnostics_picker` |
| `G` | Open workspace diagnostics picker (**LSP**) | `workspace_diagnosics_picker`
| `r` | Rename symbol (**LSP**) | `rename_symbol` |
| `a` | Apply code action (**LSP**) | `code_action` |
| `'` | Open last fuzzy picker | `last_picker` |
Expand Down
51 changes: 51 additions & 0 deletions helix-core/src/path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,3 +90,54 @@ pub fn get_relative_path(path: &Path) -> PathBuf {
};
fold_home_dir(path)
}

/// Returns a truncated filepath where the basepart of the path is reduced to the first
/// char of the folder and the whole filename appended.
///
/// Also strip the current working directory from the beginning of the path.
/// Note that this function does not check if the truncated path is unambiguous.
///
/// ```
/// use helix_core::path::get_truncated_path;
/// use std::path::Path;
///
/// assert_eq!(
/// get_truncated_path("/home/cnorris/documents/jokes.txt").as_path(),
/// Path::new("/h/c/d/jokes.txt")
/// );
/// assert_eq!(
/// get_truncated_path("jokes.txt").as_path(),
/// Path::new("jokes.txt")
/// );
/// assert_eq!(
/// get_truncated_path("/jokes.txt").as_path(),
/// Path::new("/jokes.txt")
/// );
/// assert_eq!(
/// get_truncated_path("/h/c/d/jokes.txt").as_path(),
/// Path::new("/h/c/d/jokes.txt")
/// );
/// assert_eq!(get_truncated_path("").as_path(), Path::new(""));
/// ```
///
pub fn get_truncated_path<P: AsRef<Path>>(path: P) -> PathBuf {
let cwd = std::env::current_dir().unwrap_or_default();
let path = path
.as_ref()
.strip_prefix(cwd)
.unwrap_or_else(|_| path.as_ref());
let file = path.file_name().unwrap_or_default();
let base = path.parent().unwrap_or_else(|| Path::new(""));
let mut ret = PathBuf::new();
for d in base {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this loop necessary? d is an Option. I'd use if let Some()

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems to me you actually want to iterate over ancestors()

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

d is an OsStr and base is a &Path

Path implements Iterator for component traversal.

ret.push(
d.to_string_lossy()
.chars()
.next()
.unwrap_or_default()
.to_string(),
);
}
ret.push(file);
ret
}
28 changes: 21 additions & 7 deletions helix-term/src/application.rs
Original file line number Diff line number Diff line change
Expand Up @@ -495,7 +495,7 @@ impl Application {
));
}
}
Notification::PublishDiagnostics(params) => {
Notification::PublishDiagnostics(mut params) => {
let path = params.uri.to_file_path().unwrap();
hirschenberger marked this conversation as resolved.
Show resolved Hide resolved
let doc = self.editor.document_by_path_mut(&path);

Expand All @@ -505,12 +505,9 @@ impl Application {

let diagnostics = params
.diagnostics
.into_iter()
.iter()
.filter_map(|diagnostic| {
use helix_core::{
diagnostic::{Range, Severity::*},
Diagnostic,
};
use helix_core::diagnostic::{Diagnostic, Range, Severity::*};
use lsp::DiagnosticSeverity;

let language_server = doc.language_server().unwrap();
Expand Down Expand Up @@ -561,7 +558,7 @@ impl Application {
Some(Diagnostic {
range: Range { start, end },
line: diagnostic.range.start.line as usize,
message: diagnostic.message,
message: diagnostic.message.clone(),
severity,
// code
// source
Expand All @@ -571,6 +568,23 @@ impl Application {

doc.set_diagnostics(diagnostics);
}

// Sort diagnostics first by URL and then by severity.
// Note: The `lsp::DiagnosticSeverity` enum is already defined in decreasing order
params.diagnostics.sort_unstable_by(|a, b| {
if let (Some(a), Some(b)) = (a.severity, b.severity) {
a.partial_cmp(&b).unwrap()
} else {
std::cmp::Ordering::Equal
}
});

// Insert the original lsp::Diagnostics here because we may have no open document
// for diagnosic message and so we can't calculate the exact position.
// When using them later in the diagnostics picker, we calculate them on-demand.
self.editor
.diagnostics
.insert(params.uri, params.diagnostics);
}
Notification::ShowMessage(params) => {
log::warn!("unhandled window/showMessage: {:?}", params);
Expand Down
12 changes: 7 additions & 5 deletions helix-term/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ pub(crate) mod typed;

pub use dap::*;
pub use lsp::*;
use tui::text::Spans;
pub use typed::*;

use helix_core::{
Expand Down Expand Up @@ -264,6 +265,8 @@ impl MappableCommand {
buffer_picker, "Open buffer picker",
symbol_picker, "Open symbol picker",
workspace_symbol_picker, "Open workspace symbol picker",
diagnostics_picker, "Open diagnostic picker",
workspace_diagnostics_picker, "Open workspace diagnostic picker",
last_picker, "Open last picker",
prepend_to_line, "Insert at start of line",
append_to_line, "Insert at end of line",
Expand Down Expand Up @@ -2169,7 +2172,7 @@ fn buffer_picker(cx: &mut Context) {
}

impl BufferMeta {
fn format(&self) -> Cow<str> {
fn format(&self) -> Spans {
let path = self
.path
.as_deref()
Expand All @@ -2192,7 +2195,7 @@ fn buffer_picker(cx: &mut Context) {
} else {
format!(" ({})", flags.join(""))
};
Cow::Owned(format!("{} {}{}", self.id, path, flag))
format!("{} {}{}", self.id, path, flag).into()
}
}

Expand Down Expand Up @@ -2259,10 +2262,9 @@ pub fn command_palette(cx: &mut Context) {
let picker = Picker::new(
commands,
move |command| match command {
MappableCommand::Typable { doc, name, .. } => match keymap.get(name as &String)
{
MappableCommand::Typable { doc, name, .. } => match keymap.get(name) {
Some(bindings) => format!("{} ({})", doc, fmt_binding(bindings)).into(),
None => doc.into(),
None => doc.as_str().into(),
},
MappableCommand::Static { doc, name, .. } => match keymap.get(*name) {
Some(bindings) => format!("{} ({})", doc, fmt_binding(bindings)).into(),
Expand Down
135 changes: 131 additions & 4 deletions helix-term/src/commands/lsp.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,25 @@
use helix_lsp::{
block_on, lsp,
block_on,
lsp::{self, DiagnosticSeverity, NumberOrString},
util::{diagnostic_to_lsp_diagnostic, lsp_pos_to_pos, lsp_range_to_range, range_to_lsp_range},
OffsetEncoding,
};
use tui::text::{Span, Spans};

use super::{align_view, push_jump, Align, Context, Editor};

use helix_core::Selection;
use helix_view::editor::Action;
use helix_core::{path, Selection};
use helix_view::{
editor::Action,
theme::{Modifier, Style},
};

use crate::{
compositor::{self, Compositor},
ui::{self, overlay::overlayed, FileLocation, FilePicker, Popup, PromptEvent},
};

use std::borrow::Cow;
use std::{borrow::Cow, collections::BTreeMap};

/// Gets the language server that is attached to a document, and
/// if it's not active displays a status message. Using this macro
Expand Down Expand Up @@ -145,6 +150,97 @@ fn sym_picker(
.truncate_start(false)
}

fn diag_picker(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

diagnostics_picker

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It follows the same naming schema as the sym_picker:

There's a function symbol_picker that calls a function sym_picker, the same as the function diagnostics_picker is calling diag_picker

cx: &Context,
diagnostics: BTreeMap<lsp::Url, Vec<lsp::Diagnostic>>,
current_path: Option<lsp::Url>,
offset_encoding: OffsetEncoding,
) -> FilePicker<(lsp::Url, lsp::Diagnostic)> {
// TODO: drop current_path comparison and instead use workspace: bool flag?

// flatten the map to a vec of (url, diag) pairs
let mut flat_diag = Vec::new();
for (url, diags) in diagnostics {
flat_diag.reserve(diags.len());
for diag in diags {
flat_diag.push((url.clone(), diag));
}
}

let hint = cx.editor.theme.get("hint");
let info = cx.editor.theme.get("info");
let warning = cx.editor.theme.get("warning");
let error = cx.editor.theme.get("error");

FilePicker::new(
flat_diag,
move |(url, diag)| {
let mut style = diag
.severity
.map(|s| match s {
DiagnosticSeverity::HINT => hint,
DiagnosticSeverity::INFORMATION => info,
DiagnosticSeverity::WARNING => warning,
DiagnosticSeverity::ERROR => error,
_ => Style::default(),
})
.unwrap_or_default();

// remove background as it is distracting in the picker list
style.bg = None;
hirschenberger marked this conversation as resolved.
Show resolved Hide resolved

let code = diag
.code
.as_ref()
.map(|c| match c {
NumberOrString::Number(n) => n.to_string(),
NumberOrString::String(s) => s.to_string(),
})
.unwrap_or_default();

let truncated_path = path::get_truncated_path(url.path())
.to_string_lossy()
.into_owned();

Spans::from(vec![
Span::styled(
diag.source.clone().unwrap_or_default(),
style.add_modifier(Modifier::BOLD),
),
Span::raw(": "),
Span::styled(truncated_path, style),
Span::raw(" - "),
Span::styled(code, style.add_modifier(Modifier::BOLD)),
Span::raw(": "),
Span::styled(&diag.message, style),
])
},
move |cx, (url, diag), action| {
if current_path.as_ref() == Some(url) {
let (view, doc) = current!(cx.editor);
push_jump(view, doc);
} else {
let path = url.to_file_path().unwrap();
cx.editor.open(&path, action).expect("editor.open failed");
}

let (view, doc) = current!(cx.editor);

if let Some(range) = lsp_range_to_range(doc.text(), diag.range, offset_encoding) {
// we flip the range so that the cursor sits on the start of the symbol
// (for example start of the function).
doc.set_selection(view.id, Selection::single(range.head, range.anchor));
align_view(doc, view, Align::Center);
}
},
move |_editor, (url, diag)| {
let location = lsp::Location::new(url.clone(), diag.range);
Some(location_to_file_location(&location))
},
)
.truncate_start(false)
}

pub fn symbol_picker(cx: &mut Context) {
fn nested_to_flat(
list: &mut Vec<lsp::SymbolInformation>,
Expand Down Expand Up @@ -215,6 +311,37 @@ pub fn workspace_symbol_picker(cx: &mut Context) {
)
}

pub fn diagnostics_picker(cx: &mut Context) {
let doc = doc!(cx.editor);
let language_server = language_server!(cx.editor, doc);
if let Some(current_url) = doc.url() {
let offset_encoding = language_server.offset_encoding();
let diagnostics = cx
.editor
.diagnostics
.get(&current_url)
.cloned()
.unwrap_or_default();
let picker = diag_picker(
cx,
[(current_url.clone(), diagnostics)].into(),
Some(current_url),
offset_encoding,
);
cx.push_layer(Box::new(overlayed(picker)));
}
}

pub fn workspace_diagnostics_picker(cx: &mut Context) {
let doc = doc!(cx.editor);
let language_server = language_server!(cx.editor, doc);
let current_url = doc.url();
let offset_encoding = language_server.offset_encoding();
let diagnostics = cx.editor.diagnostics.clone();
let picker = diag_picker(cx, diagnostics, current_url, offset_encoding);
cx.push_layer(Box::new(overlayed(picker)));
}

impl ui::menu::Item for lsp::CodeActionOrCommand {
fn label(&self) -> &str {
match self {
Expand Down
2 changes: 2 additions & 0 deletions helix-term/src/keymap/default.rs
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,8 @@ pub fn default() -> HashMap<Mode, Keymap> {
"b" => buffer_picker,
"s" => symbol_picker,
"S" => workspace_symbol_picker,
"g" => diagnostics_picker,
"G" => workspace_diagnostics_picker,
hirschenberger marked this conversation as resolved.
Show resolved Hide resolved
"a" => code_action,
"'" => last_picker,
"d" => { "Debug (experimental)" sticky=true
Expand Down
4 changes: 3 additions & 1 deletion helix-term/src/ui/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ pub use text::Text;
use helix_core::regex::Regex;
use helix_core::regex::RegexBuilder;
use helix_view::{Document, Editor, View};
use tui;
use tui::text::Spans;

use std::path::PathBuf;

Expand Down Expand Up @@ -172,7 +174,7 @@ pub fn file_picker(root: PathBuf, config: &helix_view::editor::Config) -> FilePi
files,
move |path: &PathBuf| {
// format_fn
path.strip_prefix(&root).unwrap_or(path).to_string_lossy()
Spans::from(path.strip_prefix(&root).unwrap_or(path).to_string_lossy())
},
move |cx, path: &PathBuf, action| {
if let Err(e) = cx.editor.open(path, action) {
Expand Down
Loading