@@ -19,7 +19,8 @@ use crate::{
1919 ui::{self, overlay::overlayed, FileLocation, FilePicker, Popup, PromptEvent},
2020};
2121
22−use std::{borrow::Cow, collections::BTreeMap};
22+use std::collections::BTreeMap;
23+use std::{borrow::Cow, path::PathBuf};
2324
2425/// Gets the language server that is attached to a document, and
2526/// if it's not active displays a status message. Using this macro
@@ -39,6 +40,112 @@ macro_rules! language_server {
3940 };
4041}
4142
43+impl ui::menu::Item for lsp::Location {
44+ /// Current working directory.
45+ type Data = PathBuf;
46+
47+ fn label(&self, cwdir: &Self::Data) -> Spans {
48+ let file: Cow<'_, str> = (self.uri.scheme() == "file")
49+ .then(|| {
50+ self.uri
51+ .to_file_path()
52+ .map(|path| {
53+ // strip root prefix
54+ path.strip_prefix(&cwdir)
55+ .map(|path| path.to_path_buf())
56+ .unwrap_or(path)
57+ })
58+ .map(|path| Cow::from(path.to_string_lossy().into_owned()))
59+ .ok()
60+ })
61+ .flatten()
62+ .unwrap_or_else(|| self.uri.as_str().into());
63+ let line = self.range.start.line;
64+ format!("{}:{}", file, line).into()
65+ }
66+}
67+
68+impl ui::menu::Item for lsp::SymbolInformation {
69+ /// Path to currently focussed document
70+ type Data = Option<lsp::Url>;
71+
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()
75+ } else {
76+ match self.location.uri.to_file_path() {
77+ Ok(path) => {
78+ let relative_path = helix_core::path::get_relative_path(path.as_path())
79+ .to_string_lossy()
80+ .into_owned();
81+ format!("{} ({})", &self.name, relative_path).into()
82+ }
83+ Err(_) => format!("{} ({})", &self.name, &self.location.uri).into(),
84+ }
85+ }
86+ }
87+}
88+
89+struct DiagnosticStyles {
90+ hint: Style,
91+ info: Style,
92+ warning: Style,
93+ error: Style,
94+}
95+
96+struct PickerDiagnostic {
97+ url: lsp::Url,
98+ diag: lsp::Diagnostic,
99+}
100+
101+impl ui::menu::Item for PickerDiagnostic {
102+ type Data = DiagnosticStyles;
103+
104+ fn label(&self, styles: &Self::Data) -> Spans {
105+ let mut style = self
106+ .diag
107+ .severity
108+ .map(|s| match s {
109+ DiagnosticSeverity::HINT => styles.hint,
110+ DiagnosticSeverity::INFORMATION => styles.info,
111+ DiagnosticSeverity::WARNING => styles.warning,
112+ DiagnosticSeverity::ERROR => styles.error,
113+ _ => Style::default(),
114+ })
115+ .unwrap_or_default();
116+
117+ // remove background as it is distracting in the picker list
118+ style.bg = None;
119+
120+ let code = self
121+ .diag
122+ .code
123+ .as_ref()
124+ .map(|c| match c {
125+ NumberOrString::Number(n) => n.to_string(),
126+ NumberOrString::String(s) => s.to_string(),
127+ })
128+ .unwrap_or_default();
129+
130+ let truncated_path = path::get_truncated_path(self.url.path())
131+ .to_string_lossy()
132+ .into_owned();
133+
134+ Spans::from(vec![
135+ Span::styled(
136+ self.diag.source.clone().unwrap_or_default(),
137+ style.add_modifier(Modifier::BOLD),
138+ ),
139+ Span::raw(": "),
140+ Span::styled(truncated_path, style),
141+ Span::raw(" - "),
142+ Span::styled(code, style.add_modifier(Modifier::BOLD)),
143+ Span::raw(": "),
144+ Span::styled(&self.diag.message, style),
145+ ])
146+ }
147+}
148+
42149fn location_to_file_location(location: &lsp::Location) -> FileLocation {
43150 let path = location.uri.to_file_path().unwrap();
44151 let line = Some((
@@ -93,29 +200,14 @@ fn sym_picker(
93200 offset_encoding: OffsetEncoding,
94201) -> FilePicker<lsp::SymbolInformation> {
95202 // TODO: drop current_path comparison and instead use workspace: bool flag?
96− let current_path2 = current_path.clone();
97203 FilePicker::new(
98204 symbols,
99− move |symbol| {
100− if current_path.as_ref() == Some(&symbol.location.uri) {
101− symbol.name.as_str().into()
102− } else {
103− match symbol.location.uri.to_file_path() {
104− Ok(path) => {
105− let relative_path = helix_core::path::get_relative_path(path.as_path())
106− .to_string_lossy()
107− .into_owned();
108− format!("{} ({})", &symbol.name, relative_path).into()
109− }
110− Err(_) => format!("{} ({})", &symbol.name, &symbol.location.uri).into(),
111− }
112− }
113− },
205+ current_path.clone(),
114206 move |cx, symbol, action| {
115207 let (view, doc) = current!(cx.editor);
116208 push_jump(view, doc);
117209
118− if current_path2.as_ref() != Some(&symbol.location.uri) {
210+ if current_path.as_ref() != Some(&symbol.location.uri) {
119211 let uri = &symbol.location.uri;
120212 let path = match uri.to_file_path() {
121213 Ok(path) => path,
@@ -155,7 +247,7 @@ fn diag_picker(
155247 diagnostics: BTreeMap<lsp::Url, Vec<lsp::Diagnostic>>,
156248 current_path: Option<lsp::Url>,
157249 offset_encoding: OffsetEncoding,
158−) -> FilePicker<(lsp::Url, lsp::Diagnostic)> {
250+) -> FilePicker<PickerDiagnostic> {
159251 // TODO: drop current_path comparison and instead use workspace: bool flag?
160252
161253 // flatten the map to a vec of (url, diag) pairs
@@ -163,59 +255,24 @@ fn diag_picker(
163255 for (url, diags) in diagnostics {
164256 flat_diag.reserve(diags.len());
165257 for diag in diags {
166− flat_diag.push((url.clone(), diag));
258+ flat_diag.push(PickerDiagnostic {
259+ url: url.clone(),
260+ diag,
261+ });
167262 }
168263 }
169264
170− let hint = cx.editor.theme.get("hint");
171− let info = cx.editor.theme.get("info");
172− let warning = cx.editor.theme.get("warning");
173− let error = cx.editor.theme.get("error");
265+ let styles = DiagnosticStyles {
266+ hint: cx.editor.theme.get("hint"),
267+ info: cx.editor.theme.get("info"),
268+ warning: cx.editor.theme.get("warning"),
269+ error: cx.editor.theme.get("error"),
270+ };
174271
175272 FilePicker::new(
176273 flat_diag,
177− move |(url, diag)| {
178− let mut style = diag
179− .severity
180− .map(|s| match s {
181− DiagnosticSeverity::HINT => hint,
182− DiagnosticSeverity::INFORMATION => info,
183− DiagnosticSeverity::WARNING => warning,
184− DiagnosticSeverity::ERROR => error,
185− _ => Style::default(),
186− })
187− .unwrap_or_default();
188−
189− // remove background as it is distracting in the picker list
190− style.bg = None;
191−
192− let code = diag
193− .code
194− .as_ref()
195− .map(|c| match c {
196− NumberOrString::Number(n) => n.to_string(),
197− NumberOrString::String(s) => s.to_string(),
198− })
199− .unwrap_or_default();
200−
201− let truncated_path = path::get_truncated_path(url.path())
202− .to_string_lossy()
203− .into_owned();
204−
205− Spans::from(vec![
206− Span::styled(
207− diag.source.clone().unwrap_or_default(),
208− style.add_modifier(Modifier::BOLD),
209− ),
210− Span::raw(": "),
211− Span::styled(truncated_path, style),
212− Span::raw(" - "),
213− Span::styled(code, style.add_modifier(Modifier::BOLD)),
214− Span::raw(": "),
215− Span::styled(&diag.message, style),
216− ])
217− },
218− move |cx, (url, diag), action| {
274+ styles,
275+ move |cx, PickerDiagnostic { url, diag }, action| {
219276 if current_path.as_ref() == Some(url) {
220277 let (view, doc) = current!(cx.editor);
221278 push_jump(view, doc);
@@ -233,7 +290,7 @@ fn diag_picker(
233290 align_view(doc, view, Align::Center);
234291 }
235292 },
236− move |_editor, (url, diag)| {
293+ move |_editor, PickerDiagnostic { url, diag }| {
237294 let location = lsp::Location::new(url.clone(), diag.range);
238295 Some(location_to_file_location(&location))
239296 },
@@ -343,10 +400,11 @@ pub fn workspace_diagnostics_picker(cx: &mut Context) {
343400}
344401
345402impl ui::menu::Item for lsp::CodeActionOrCommand {
346− fn label(&self) -> &str {
403+ type Data = ();
404+ fn label(&self, _data: &Self::Data) -> Spans {
347405 match self {
348− lsp::CodeActionOrCommand::CodeAction(action) => action.title.as_str(),
349− lsp::CodeActionOrCommand::Command(command) => command.title.as_str(),
406+ lsp::CodeActionOrCommand::CodeAction(action) => action.title.as_str().into(),
407+ lsp::CodeActionOrCommand::Command(command) => command.title.as_str().into(),
350408 }
351409 }
352410}
@@ -391,7 +449,7 @@ pub fn code_action(cx: &mut Context) {
391449 return;
392450 }
393451
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| {
395453 if event != PromptEvent::Validate {
396454 return;
397455 }
@@ -619,6 +677,7 @@ pub fn apply_workspace_edit(
619677 }
620678 }
621679}
680+
622681fn goto_impl(
623682 editor: &mut Editor,
624683 compositor: &mut Compositor,
@@ -637,26 +696,7 @@ fn goto_impl(
637696 _locations => {
638697 let picker = FilePicker::new(
639698 locations,
640− move |location| {
641− let file: Cow<'_, str> = (location.uri.scheme() == "file")
642− .then(|| {
643− location
644− .uri
645− .to_file_path()
646− .map(|path| {
647− // strip root prefix
648− path.strip_prefix(&cwdir)
649− .map(|path| path.to_path_buf())
650− .unwrap_or(path)
651− })
652− .map(|path| Cow::from(path.to_string_lossy().into_owned()))
653− .ok()
654− })
655− .flatten()
656− .unwrap_or_else(|| location.uri.as_str().into());
657− let line = location.range.start.line;
658− format!("{}:{}", file, line).into()
659− },
699+ cwdir,
660700 move |cx, location, action| {
661701 jump_to_location(cx.editor, location, offset_encoding, action)
662702 },