on master help sign in
Align view for background buffer opened with `alt-ret` (#7691)
aa4d84awoojiq
helix-term/src/commands.rs+11−8
@@ -2199,8 +2199,8 @@ fn global_search(cx: &mut Context) {
2199 all_matches,
2199 all_matches,
2200 current_path,
2200 current_path,
2201 move |cx, FileResult { path, line_num }, action| {
2201 move |cx, FileResult { path, line_num }, action| {
2202− match cx.editor.open(path, action) {
2202+ let doc = match cx.editor.open(path, action) {
2203− Ok(_) => {}
2203+ Ok(id) => doc_mut!(cx.editor, &id),
2204 Err(e) => {
2204 Err(e) => {
2205 cx.editor.set_error(format!(
2205 cx.editor.set_error(format!(
2206 "Failed to open file '{}': {}",
2206 "Failed to open file '{}': {}",
@@ -2209,10 +2209,9 @@ fn global_search(cx: &mut Context) {
2209 ));
2209 ));
2210 return;
2210 return;
2211 }
2211 }
2212− }
2212+ };
2213−
2214 let line_num = *line_num;
2213 let line_num = *line_num;
2215− let (view, doc) = current!(cx.editor);
2214+ let view = view_mut!(cx.editor);
2216 let text = doc.text();
2215 let text = doc.text();
2217 if line_num >= text.len_lines() {
2216 if line_num >= text.len_lines() {
2218 cx.editor.set_error("The line you jumped to does not exist anymore because the file has changed.");
2217 cx.editor.set_error("The line you jumped to does not exist anymore because the file has changed.");
@@ -2222,7 +2221,9 @@ fn global_search(cx: &mut Context) {
2222 let end = text.line_to_char((line_num + 1).min(text.len_lines()));
2221 let end = text.line_to_char((line_num + 1).min(text.len_lines()));
2223
2222
2224 doc.set_selection(view.id, Selection::single(start, end));
2223 doc.set_selection(view.id, Selection::single(start, end));
2224+ if action.align_view(view, doc.id()){
2225− align_view(doc, view, Align::Center);
2225+ align_view(doc, view, Align::Center);
2226+ }
2226 }).with_preview(|_editor, FileResult { path, line_num }| {
2227 }).with_preview(|_editor, FileResult { path, line_num }| {
2227 Some((path.clone().into(), Some((*line_num, *line_num))))
2228 Some((path.clone().into(), Some((*line_num, *line_num))))
2228 });
2229 });
@@ -2742,9 +2743,11 @@ fn jumplist_picker(cx: &mut Context) {
2742 |cx, meta, action| {
2743 |cx, meta, action| {
2743 cx.editor.switch(meta.id, action);
2744 cx.editor.switch(meta.id, action);
2744 let config = cx.editor.config();
2745 let config = cx.editor.config();
2745− let (view, doc) = current!(cx.editor);
2746+ let (view, doc) = (view_mut!(cx.editor), doc_mut!(cx.editor, &meta.id));
2746 doc.set_selection(view.id, meta.selection.clone());
2747 doc.set_selection(view.id, meta.selection.clone());
2748+ if action.align_view(view, doc.id()) {
2747− view.ensure_cursor_in_view_center(doc, config.scrolloff);
2749+ view.ensure_cursor_in_view_center(doc, config.scrolloff);
2750+ }
2748 },
2751 },
2749 )
2752 )
2750 .with_preview(|editor, meta| {
2753 .with_preview(|editor, meta| {
helix-term/src/commands/lsp.rs+22−55
@@ -195,7 +195,6 @@ fn location_to_file_location(location: &lsp::Location) -> FileLocation {
195 (path.into(), line)
195 (path.into(), line)
196}
196}
197
197
198−// TODO: share with symbol picker(symbol.location)
199fn jump_to_location(
198fn jump_to_location(
200 editor: &mut Editor,
199 editor: &mut Editor,
201 location: &lsp::Location,
200 location: &lsp::Location,
@@ -213,15 +212,16 @@ fn jump_to_location(
213 return;
212 return;
214 }
213 }
215 };
214 };
215+
216− match editor.open(&path, action) {
216+ let doc = match editor.open(&path, action) {
217− Ok(_) => (),
217+ Ok(id) => doc_mut!(editor, &id),
218 Err(err) => {
218 Err(err) => {
219 let err = format!("failed to open path: {:?}: {:?}", location.uri, err);
219 let err = format!("failed to open path: {:?}: {:?}", location.uri, err);
220 editor.set_error(err);
220 editor.set_error(err);
221 return;
221 return;
222 }
222 }
223− }
223+ };
224− let (view, doc) = current!(editor);
224+ let view = view_mut!(editor);
225 // TODO: convert inside server
225 // TODO: convert inside server
226 let new_range =
226 let new_range =
227 if let Some(new_range) = lsp_range_to_range(doc.text(), location.range, offset_encoding) {
227 if let Some(new_range) = lsp_range_to_range(doc.text(), location.range, offset_encoding) {
@@ -233,45 +233,22 @@ fn jump_to_location(
233 // we flip the range so that the cursor sits on the start of the symbol
233 // we flip the range so that the cursor sits on the start of the symbol
234 // (for example start of the function).
234 // (for example start of the function).
235 doc.set_selection(view.id, Selection::single(new_range.head, new_range.anchor));
235 doc.set_selection(view.id, Selection::single(new_range.head, new_range.anchor));
236+ if action.align_view(view, doc.id()) {
236− align_view(doc, view, Align::Center);
237+ align_view(doc, view, Align::Center);
238+ }
237}
239}
238
240
239type SymbolPicker = Picker<SymbolInformationItem>;
241type SymbolPicker = Picker<SymbolInformationItem>;
240
242
241fn sym_picker(symbols: Vec<SymbolInformationItem>, current_path: Option<lsp::Url>) -> SymbolPicker {
243fn sym_picker(symbols: Vec<SymbolInformationItem>, current_path: Option<lsp::Url>) -> SymbolPicker {
242 // TODO: drop current_path comparison and instead use workspace: bool flag?
244 // TODO: drop current_path comparison and instead use workspace: bool flag?
243− Picker::new(symbols, current_path.clone(), move |cx, item, action| {
245+ Picker::new(symbols, current_path, move |cx, item, action| {
246+ jump_to_location(
244− let (view, doc) = current!(cx.editor);
247+ cx.editor,
245− push_jump(view, doc);
246−
247− if current_path.as_ref() != Some(&item.symbol.location.uri) {
248− let uri = &item.symbol.location.uri;
248+ &item.symbol.location,
249− let path = match uri.to_file_path() {
250− Ok(path) => path,
251− Err(_) => {
252− let err = format!("unable to convert URI to filepath: {}", uri);
253− cx.editor.set_error(err);
254− return;
255− }
256− };
257− if let Err(err) = cx.editor.open(&path, action) {
258− let err = format!("failed to open document: {}: {}", uri, err);
259− log::error!("{}", err);
260− cx.editor.set_error(err);
261− return;
262− }
263− }
264−
265− let (view, doc) = current!(cx.editor);
266−
267− if let Some(range) =
268− lsp_range_to_range(doc.text(), item.symbol.location.range, item.offset_encoding)
249+ item.offset_encoding,
269− {
250+ action,
270− // we flip the range so that the cursor sits on the start of the symbol
251+ );
271− // (for example start of the function).
272− doc.set_selection(view.id, Selection::single(range.head, range.anchor));
273− align_view(doc, view, Align::Center);
274− }
275 })
252 })
276 .with_preview(move |_editor, item| Some(location_to_file_location(&item.symbol.location)))
253 .with_preview(move |_editor, item| Some(location_to_file_location(&item.symbol.location)))
277 .truncate_start(false)
254 .truncate_start(false)
@@ -286,7 +263,7 @@ enum DiagnosticsFormat {
286fn diag_picker(
263fn diag_picker(
287 cx: &Context,
264 cx: &Context,
288 diagnostics: BTreeMap<lsp::Url, Vec<(lsp::Diagnostic, usize)>>,
265 diagnostics: BTreeMap<lsp::Url, Vec<(lsp::Diagnostic, usize)>>,
289− current_path: Option<lsp::Url>,
266+ _current_path: Option<lsp::Url>,
290 format: DiagnosticsFormat,
267 format: DiagnosticsFormat,
291) -> Picker<PickerDiagnostic> {
268) -> Picker<PickerDiagnostic> {
292 // TODO: drop current_path comparison and instead use workspace: bool flag?
269 // TODO: drop current_path comparison and instead use workspace: bool flag?
@@ -324,22 +301,12 @@ fn diag_picker(
324 offset_encoding,
301 offset_encoding,
325 },
302 },
326 action| {
303 action| {
327− if current_path.as_ref() == Some(url) {
304+ jump_to_location(
328− let (view, doc) = current!(cx.editor);
305+ cx.editor,
329− push_jump(view, doc);
306+ &lsp::Location::new(url.clone(), diag.range),
330− } else {
307+ *offset_encoding,
331− let path = url.to_file_path().unwrap();
308+ action,
332− cx.editor.open(&path, action).expect("editor.open failed");
309+ )
333− }
334−
335− let (view, doc) = current!(cx.editor);
336−
337− if let Some(range) = lsp_range_to_range(doc.text(), diag.range, *offset_encoding) {
338− // we flip the range so that the cursor sits on the start of the symbol
339− // (for example start of the function).
340− doc.set_selection(view.id, Selection::single(range.head, range.anchor));
341− align_view(doc, view, Align::Center);
342− }
343 },
310 },
344 )
311 )
345 .with_preview(move |_editor, PickerDiagnostic { url, diag, .. }| {
312 .with_preview(move |_editor, PickerDiagnostic { url, diag, .. }| {
helix-term/tests/integration.rs+1
@@ -19,6 +19,7 @@ mod test {
19 mod auto_pairs;
19 mod auto_pairs;
20 mod commands;
20 mod commands;
21 mod movement;
21 mod movement;
22+ mod picker;
22 mod prompt;
23 mod prompt;
23 mod splits;
24 mod splits;
24}
25}
helix-term/tests/test/picker.rsnew file+80
@@ -0,0 +1,80 @@
1+use std::fs;
2+
3+use helix_core::{path::get_canonicalized_path, Range};
4+use helix_loader::{current_working_dir, set_current_working_dir};
5+use helix_view::{current_ref, editor::Action};
6+use tempfile::{Builder, TempDir};
7+
8+use super::*;
9+
10+#[tokio::test(flavor = "multi_thread")]
11+async fn test_picker_alt_ret() -> anyhow::Result<()> {
12+ // Create two files, open the first and run a global search for a word
13+ // from the second file. Press <alt-ret> to have helix open the second file in the
14+ // new buffer, but not change focus. Then check whether the word is highlighted
15+ // correctly and the view of the first file has not changed.
16+ let tmp_dir = TempDir::new()?;
17+ set_current_working_dir(tmp_dir.path().into())?;
18+
19+ let mut app = AppBuilder::new().build()?;
20+
21+ log::debug!(
22+ "set current working directory to {:?}",
23+ current_working_dir()
24+ );
25+
26+ // Add prefix so helix doesn't hide these files in a picker
27+ let files = [
28+ Builder::new().prefix("1").tempfile_in(&tmp_dir)?,
29+ Builder::new().prefix("2").tempfile_in(&tmp_dir)?,
30+ ];
31+ let paths = files
32+ .iter()
33+ .map(|f| get_canonicalized_path(f.path()).unwrap())
34+ .collect::<Vec<_>>();
35+
36+ fs::write(&paths[0], "1\n2\n3\n4")?;
37+ fs::write(&paths[1], "first\nsecond")?;
38+
39+ log::debug!(
40+ "created and wrote two temporary files: {:?} & {:?}",
41+ paths[0],
42+ paths[1]
43+ );
44+
45+ // Manually open to save the offset, otherwise we won't be able to change the state in the Fn trait
46+ app.editor.open(files[0].path(), Action::Replace)?;
47+ let view_offset = current_ref!(app.editor).0.offset;
48+
49+ test_key_sequences(
50+ &mut app,
51+ vec![
52+ (Some("<space>/"), None),
53+ (Some("second<ret>"), None),
54+ (
55+ Some("<A-ret><esc>"),
56+ Some(&|app| {
57+ let (view, doc) = current_ref!(app.editor);
58+ assert_eq!(doc.path().unwrap(), &paths[0]);
59+ let select_ranges = doc.selection(view.id).ranges();
60+ assert_eq!(select_ranges[0], Range::new(0, 1));
61+ assert_eq!(view.offset, view_offset);
62+ }),
63+ ),
64+ (
65+ Some(":buffer<minus>next<ret>"),
66+ Some(&|app| {
67+ let (view, doc) = current_ref!(app.editor);
68+ assert_eq!(doc.path().unwrap(), &paths[1]);
69+ let select_ranges = doc.selection(view.id).ranges();
70+ assert_eq!(select_ranges.len(), 1);
71+ assert_eq!(select_ranges[0], Range::new(6, 12));
72+ }),
73+ ),
74+ ],
75+ false,
76+ )
77+ .await?;
78+
79+ Ok(())
80+}
helix-view/src/editor.rs+7
@@ -995,6 +995,13 @@ pub enum Action {
995 VerticalSplit,
995 VerticalSplit,
996}
996}
997
997
998+impl Action {
999+ /// Whether to align the view to the cursor after executing this action
1000+ pub fn align_view(&self, view: &View, new_doc: DocumentId) -> bool {
1001+ !matches!((self, view.doc == new_doc), (Action::Load, false))
1002+ }
1003+}
1004+
998/// Error thrown on failed document closed
1005/// Error thrown on failed document closed
999pub enum CloseError {
1006pub enum CloseError {
1000 /// Document doesn't exist
1007 /// Document doesn't exist