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

Global search in a background thread #1543

Closed
wants to merge 2 commits into from
Closed

Global search in a background thread #1543

wants to merge 2 commits into from

Conversation

pppKin
Copy link
Contributor

@pppKin pppKin commented Jan 19, 2022

Upon PromptEvent::Validate, the regex will be sent through a channel awaited in cx.jobs, after that the search_task is spawned by tokio::task::spawn_blocking, which is again awaited in cx.jobs. After that the rest is mostly the same as before.

edit: Also removed unnecessary deps

Copy link
Contributor

@pickfire pickfire left a comment

Choose a reason for hiding this comment

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

I don't seemed to notice any difference.

Note that no dependency is being removed (looking at tokio-stream) because helix-lsp is still using it.

@pppKin
Copy link
Contributor Author

pppKin commented Jan 19, 2022

It would depend on project size, hard drive speed and CPU performance to notice any difference. I did a test on a 15Gb repo I work on, ripgrep took 28 seconds to complete a search, so it's quite noticeable in my case:)

Comment on lines 1798 to +1818
let show_picker = async move {
let all_matches: Vec<(usize, PathBuf)> =
UnboundedReceiverStream::new(all_matches_rx).collect().await;
let call: job::Callback =
Box::new(move |editor: &mut Editor, compositor: &mut Compositor| {
if all_matches.is_empty() {
editor.set_status("No matches found".to_string());
return;
}

let picker = FilePicker::new(
all_matches,
move |(_line_num, path)| {
let relative_path = helix_core::path::get_relative_path(path)
.to_str()
.unwrap()
.to_owned();
if current_path.as_ref().map(|p| p == path).unwrap_or(false) {
format!("{} (*)", relative_path).into()
} else {
relative_path.into()
}
},
move |editor: &mut Editor, (line_num, path), action| {
match editor.open(path.into(), action) {
Ok(_) => {}
Err(e) => {
editor.set_error(format!(
"Failed to open file '{}': {}",
path.display(),
e
));
return;
}
}

let line_num = *line_num;
let (view, doc) = current!(editor);
let text = doc.text();
let start = text.line_to_char(line_num);
let end = text.line_to_char((line_num + 1).min(text.len_lines()));
let regex = regex_rx.recv().await;
if let Some(regex) = regex {
if let Ok(matcher) = RegexMatcherBuilder::new()
.case_smart(smart_case)
.build(regex.as_str())
{
match global_search_task(file_picker_config, matcher).await {
Ok(all_matches) => {
let call: job::Callback = global_search_callback(all_matches, current_path);
return Ok(call);
}
Err(e) => {
log::error!("Global search error: {}", e);
}
};
}
}

doc.set_selection(view.id, Selection::single(start, end));
align_view(doc, view, Align::Center);
},
|_editor, (line_num, path)| Some((path.clone(), Some((*line_num, *line_num)))),
);
compositor.push(Box::new(picker));
});
Ok(call)
let empty_call: job::Callback = Box::new(|_, _| {});
Ok(empty_call)
Copy link
Member

Choose a reason for hiding this comment

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

You probably want

cx.jobs.spawn(future);

That way you're not returning an empty job::Callback

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sorry it took so long but I'm confused here; With job::Callback I have access to Editor and Compositor so I can set_status or push the picker to the compositor. How should I access Editor and Compositor if I'm using cx.jobs.spawn(future);, perhaps I misunderstand how jobs and everything work here?

fn global_search(cx: &mut Context) {
let (all_matches_sx, all_matches_rx) =
tokio::sync::mpsc::unbounded_channel::<(usize, PathBuf)>();
let (regex_sx, mut regex_rx) = tokio::sync::mpsc::channel(1);
Copy link
Member

Choose a reason for hiding this comment

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

You want to use tokio::sync::oneshot::channel instead

Copy link
Contributor Author

Choose a reason for hiding this comment

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

regex is sent from the callback of Prompt ( move |_view, _doc, regex, event| { ... regex_sx.send() ...}), hence capturing regex_sx, I have just discovered that oneshot::Sender does not implement Copy trait and compiler would refuse to compile here.

Copy link
Member

Choose a reason for hiding this comment

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

You can use a move closure to move it into the callback

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I have this error:

error[E0507]: cannot move out of regex_sx, a captured variable in an Fn closure

I think the compiler cannot guarantee that we would call regex_sx only once, so it refuse to compile here.

Copy link
Member

Choose a reason for hiding this comment

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

Right, using a channel seems like a hack to me anyway. I'd rather pass cx through the regex_prompt callback as a first parameter and manually call let (doc, view) = current!(cx). Then you can create the search picker instance inside the callback.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think having a dedicated FnOnce callback for PromptEvent::Validate might clear things up a bit and we can use oneshot channel, but I really don't want to introduce any breaking change to the API, hence the hack.
About passing cx through regex_prompt, wouldn't it mess up the lifetime of cx because we still need to await the search results (just my naive thought)?

// Otherwise do nothing
// log::warn!("Global Search Invalid Pattern")
}
match block_on(regex_sx.send(regex)) {
Copy link
Member

Choose a reason for hiding this comment

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

If you use oneshot then the sender isn't async and you don't need to block_on here.

Copy link
Member

Choose a reason for hiding this comment

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

Why is this using a channel rather than creating the picker and the global_search_task here? Is it because you have no access to cx or editor?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll check these out and work on improvements as soon as I can (a bit occupied at the moment:) )

@the-mikedavis the-mikedavis added the S-waiting-on-review Status: Awaiting review from a maintainer. label May 18, 2022
@pickfire pickfire added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from a maintainer. labels Jun 8, 2022
@kirawi kirawi added A-helix-term Area: Helix term improvements S-inactive Status: Inactive and waiting on the author. This is often applied to closed PRs. and removed S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. labels Sep 13, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-helix-term Area: Helix term improvements S-inactive Status: Inactive and waiting on the author. This is often applied to closed PRs.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants