@@ -19,7 +19,8 @@ use crate::{
19 ui::{self, overlay::overlayed, FileLocation, FilePicker, Popup, PromptEvent},
19 ui::{self, overlay::overlayed, FileLocation, FilePicker, Popup, PromptEvent},
22−use std::{borrow::Cow, collections::BTreeMap};
22+use std::collections::BTreeMap;
23+use std::{borrow::Cow, path::PathBuf};
24/// Gets the language server that is attached to a document, and
25/// Gets the language server that is attached to a document, and
25/// if it's not active displays a status message. Using this macro
26/// if it's not active displays a status message. Using this macro
@@ -39,6 +40,112 @@ macro_rules! language_server {
43+impl ui::menu::Item for lsp::Location {
44+ /// Current working directory.
47+ fn label(&self, cwdir: &Self::Data) -> Spans {
48+ let file: Cow<'_, str> = (self.uri.scheme() == "file")
54+ path.strip_prefix(&cwdir)
55+ .map(|path| path.to_path_buf())
58+ .map(|path| Cow::from(path.to_string_lossy().into_owned()))
62+ .unwrap_or_else(|| self.uri.as_str().into());
63+ let line = self.range.start.line;
64+ format!("{}:{}", file, line).into()
68+impl ui::menu::Item for lsp::SymbolInformation {
69+ /// Path to currently focussed document
70+ type Data = Option<lsp::Url>;
72+ fn label(&self, current_doc_path: &Self::Data) -> Spans {
73+ if current_doc_path.as_ref() == Some(&self.location.uri) {
74+ self.name.as_str().into()
76+ match self.location.uri.to_file_path() {
78+ let relative_path = helix_core::path::get_relative_path(path.as_path())
81+ format!("{} ({})", &self.name, relative_path).into()
83+ Err(_) => format!("{} ({})", &self.name, &self.location.uri).into(),
89+struct DiagnosticStyles {
96+struct PickerDiagnostic {
98+ diag: lsp::Diagnostic,
101+impl ui::menu::Item for PickerDiagnostic {
102+ type Data = DiagnosticStyles;
104+ fn label(&self, styles: &Self::Data) -> Spans {
105+ let mut style = self
109+ DiagnosticSeverity::HINT => styles.hint,
110+ DiagnosticSeverity::INFORMATION => styles.info,
111+ DiagnosticSeverity::WARNING => styles.warning,
112+ DiagnosticSeverity::ERROR => styles.error,
113+ _ => Style::default(),
115+ .unwrap_or_default();
117+ // remove background as it is distracting in the picker list
125+ NumberOrString::Number(n) => n.to_string(),
126+ NumberOrString::String(s) => s.to_string(),
128+ .unwrap_or_default();
130+ let truncated_path = path::get_truncated_path(self.url.path())
136+ self.diag.source.clone().unwrap_or_default(),
137+ style.add_modifier(Modifier::BOLD),
140+ Span::styled(truncated_path, style),
142+ Span::styled(code, style.add_modifier(Modifier::BOLD)),
144+ Span::styled(&self.diag.message, style),
42fn location_to_file_location(location: &lsp::Location) -> FileLocation {
149fn location_to_file_location(location: &lsp::Location) -> FileLocation {
43 let path = location.uri.to_file_path().unwrap();
150 let path = location.uri.to_file_path().unwrap();
44 let line = Some((
151 let line = Some((
@@ -93,29 +200,14 @@ fn sym_picker(
93 offset_encoding: OffsetEncoding,
200 offset_encoding: OffsetEncoding,
94) -> FilePicker<lsp::SymbolInformation> {
201) -> FilePicker<lsp::SymbolInformation> {
95 // TODO: drop current_path comparison and instead use workspace: bool flag?
202 // TODO: drop current_path comparison and instead use workspace: bool flag?
96− let current_path2 = current_path.clone();
97 FilePicker::new(
203 FilePicker::new(
100− if current_path.as_ref() == Some(&symbol.location.uri) {
205+ current_path.clone(),
101− symbol.name.as_str().into()
103− match symbol.location.uri.to_file_path() {
105− let relative_path = helix_core::path::get_relative_path(path.as_path())
108− format!("{} ({})", &symbol.name, relative_path).into()
110− Err(_) => format!("{} ({})", &symbol.name, &symbol.location.uri).into(),
114 move |cx, symbol, action| {
206 move |cx, symbol, action| {
115 let (view, doc) = current!(cx.editor);
207 let (view, doc) = current!(cx.editor);
116 push_jump(view, doc);
208 push_jump(view, doc);
118− if current_path2.as_ref() != Some(&symbol.location.uri) {
210+ if current_path.as_ref() != Some(&symbol.location.uri) {
119 let uri = &symbol.location.uri;
211 let uri = &symbol.location.uri;
120 let path = match uri.to_file_path() {
212 let path = match uri.to_file_path() {
121 Ok(path) => path,
213 Ok(path) => path,
@@ -155,7 +247,7 @@ fn diag_picker(
155 diagnostics: BTreeMap<lsp::Url, Vec<lsp::Diagnostic>>,
247 diagnostics: BTreeMap<lsp::Url, Vec<lsp::Diagnostic>>,
156 current_path: Option<lsp::Url>,
248 current_path: Option<lsp::Url>,
157 offset_encoding: OffsetEncoding,
249 offset_encoding: OffsetEncoding,
158−) -> FilePicker<(lsp::Url, lsp::Diagnostic)> {
250+) -> FilePicker<PickerDiagnostic> {
159 // TODO: drop current_path comparison and instead use workspace: bool flag?
251 // TODO: drop current_path comparison and instead use workspace: bool flag?
161 // flatten the map to a vec of (url, diag) pairs
253 // flatten the map to a vec of (url, diag) pairs
@@ -163,59 +255,24 @@ fn diag_picker(
163 for (url, diags) in diagnostics {
255 for (url, diags) in diagnostics {
164 flat_diag.reserve(diags.len());
256 flat_diag.reserve(diags.len());
165 for diag in diags {
257 for diag in diags {
166− flat_diag.push((url.clone(), diag));
258+ flat_diag.push(PickerDiagnostic {
265+ let styles = DiagnosticStyles {
170− let hint = cx.editor.theme.get("hint");
266+ hint: cx.editor.theme.get("hint"),
171− let info = cx.editor.theme.get("info");
267+ info: cx.editor.theme.get("info"),
172− let warning = cx.editor.theme.get("warning");
268+ warning: cx.editor.theme.get("warning"),
173− let error = cx.editor.theme.get("error");
269+ error: cx.editor.theme.get("error"),
175 FilePicker::new(
272 FilePicker::new(
176 flat_diag,
273 flat_diag,
177− move |(url, diag)| {
274+ styles,
178− let mut style = diag
181− DiagnosticSeverity::HINT => hint,
182− DiagnosticSeverity::INFORMATION => info,
183− DiagnosticSeverity::WARNING => warning,
184− DiagnosticSeverity::ERROR => error,
185− _ => Style::default(),
187− .unwrap_or_default();
189− // remove background as it is distracting in the picker list
196− NumberOrString::Number(n) => n.to_string(),
197− NumberOrString::String(s) => s.to_string(),
199− .unwrap_or_default();
201− let truncated_path = path::get_truncated_path(url.path())
207− diag.source.clone().unwrap_or_default(),
208− style.add_modifier(Modifier::BOLD),
211− Span::styled(truncated_path, style),
213− Span::styled(code, style.add_modifier(Modifier::BOLD)),
215− Span::styled(&diag.message, style),
218− move |cx, (url, diag), action| {
275+ move |cx, PickerDiagnostic { url, diag }, action| {
219 if current_path.as_ref() == Some(url) {
276 if current_path.as_ref() == Some(url) {
220 let (view, doc) = current!(cx.editor);
277 let (view, doc) = current!(cx.editor);
221 push_jump(view, doc);
278 push_jump(view, doc);
@@ -233,7 +290,7 @@ fn diag_picker(
233 align_view(doc, view, Align::Center);
290 align_view(doc, view, Align::Center);
236− move |_editor, (url, diag)| {
293+ move |_editor, PickerDiagnostic { url, diag }| {
237 let location = lsp::Location::new(url.clone(), diag.range);
294 let location = lsp::Location::new(url.clone(), diag.range);
238 Some(location_to_file_location(&location))
295 Some(location_to_file_location(&location))
@@ -343,10 +400,11 @@ pub fn workspace_diagnostics_picker(cx: &mut Context) {
345impl ui::menu::Item for lsp::CodeActionOrCommand {
402impl ui::menu::Item for lsp::CodeActionOrCommand {
346− fn label(&self) -> &str {
404+ fn label(&self, _data: &Self::Data) -> Spans {
347 match self {
405 match self {
348− lsp::CodeActionOrCommand::CodeAction(action) => action.title.as_str(),
406+ lsp::CodeActionOrCommand::CodeAction(action) => action.title.as_str().into(),
349− lsp::CodeActionOrCommand::Command(command) => command.title.as_str(),
407+ lsp::CodeActionOrCommand::Command(command) => command.title.as_str().into(),
@@ -391,7 +449,7 @@ pub fn code_action(cx: &mut Context) {
394− let mut picker = ui::Menu::new(actions, move |editor, code_action, event| {
452+ let mut picker = ui::Menu::new(actions, (), move |editor, code_action, event| {
395 if event != PromptEvent::Validate {
453 if event != PromptEvent::Validate {
@@ -619,6 +677,7 @@ pub fn apply_workspace_edit(
622fn goto_impl(
681fn goto_impl(
623 editor: &mut Editor,
682 editor: &mut Editor,
624 compositor: &mut Compositor,
683 compositor: &mut Compositor,
@@ -637,26 +696,7 @@ fn goto_impl(
637 _locations => {
696 _locations => {
638 let picker = FilePicker::new(
697 let picker = FilePicker::new(
639 locations,
698 locations,
640− move |location| {
699+ cwdir,
641− let file: Cow<'_, str> = (location.uri.scheme() == "file")
647− // strip root prefix
648− path.strip_prefix(&cwdir)
649− .map(|path| path.to_path_buf())
652− .map(|path| Cow::from(path.to_string_lossy().into_owned()))
656− .unwrap_or_else(|| location.uri.as_str().into());
657− let line = location.range.start.line;
658− format!("{}:{}", file, line).into()
660 move |cx, location, action| {
700 move |cx, location, action| {
661 jump_to_location(cx.editor, location, offset_encoding, action)
701 jump_to_location(cx.editor, location, offset_encoding, action)