assistant: Add /docs slash command (#13794)

This PR adds a new `/docs` slash command to the Assistant. This slash
command replaces `/rustdoc`.

The `/docs` slash command works with different providers. There is
currently a built-in provider for rustdoc, but new providers can be
defined within extensions. The Gleam extension contains an example of
this.

When you first type `/docs` a completion menu will be shown with the
list of available providers:


https://github.com/zed-industries/zed/assets/1486634/32287000-5855-44d9-a2eb-569596f5abd9

After completing the provider you want to use then you can type the
package name and/or item path to search for the relevant docs:


https://github.com/zed-industries/zed/assets/1486634/6fc55a63-7fcd-42ea-80ce-08c670bf03fc

There are still some rough edges around completions that I would like to
get cleaned up in a future PR. Both of these seem to stem from the fact
that we're using an intermediate completion in the slash command:

1. Accepting a provider completion will show an error until you press
<kbd>Space</kbd> to continue typing.
- We need a way of not submitting a slash command when a completion is
accepted.
2. We currently need to show the provider name in the documentation item
completion list.
- Without it, the provider name gets wiped out when accepting a
completion, causing the slash command to become invalid.

Release Notes:

- N/A
This commit is contained in:
Marshall Bowers 2024-07-03 17:04:08 -04:00 committed by GitHub
parent 492040dec4
commit 75d2e04a1d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 397 additions and 307 deletions

View file

@ -1,3 +1,4 @@
use crate::slash_command::docs_command::{DocsSlashCommand, DocsSlashCommandArgs};
use crate::{
assistant_settings::{AssistantDockPosition, AssistantSettings},
humanize_token_count,
@ -39,7 +40,7 @@ use gpui::{
Subscription, Task, Transformation, UpdateGlobal, View, ViewContext, VisualContext, WeakView,
WindowContext,
};
use indexed_docs::{IndexedDocsStore, PackageName, ProviderId};
use indexed_docs::IndexedDocsStore;
use language::{
language_settings::SoftWrap, AnchorRangeExt as _, AutoindentMode, Buffer, LanguageRegistry,
LspAdapterDelegate, OffsetRangeExt as _, Point, ToOffset as _,
@ -2695,8 +2696,8 @@ impl ContextEditor {
// TODO: In the future we should investigate how we can expose
// this as a hook on the `SlashCommand` trait so that we don't
// need to special-case it here.
if command.name == "rustdoc" {
return render_rustdoc_slash_command_trailer(
if command.name == DocsSlashCommand::NAME {
return render_docs_slash_command_trailer(
row,
command.clone(),
cx,
@ -3405,25 +3406,29 @@ fn render_pending_slash_command_gutter_decoration(
icon.into_any_element()
}
fn render_rustdoc_slash_command_trailer(
fn render_docs_slash_command_trailer(
row: MultiBufferRow,
command: PendingSlashCommand,
cx: &mut WindowContext,
) -> AnyElement {
let Some(rustdoc_store) = IndexedDocsStore::try_global(ProviderId::rustdoc(), cx).ok() else {
let Some(argument) = command.argument else {
return Empty.into_any();
};
let Some((crate_name, _)) = command
.argument
.as_ref()
.and_then(|arg| arg.split_once(':'))
let args = DocsSlashCommandArgs::parse(&argument);
let Some(store) = args
.provider()
.and_then(|provider| IndexedDocsStore::try_global(provider, cx).ok())
else {
return Empty.into_any();
};
let crate_name = PackageName::from(crate_name);
if !rustdoc_store.is_indexing(&crate_name) {
let Some(package) = args.package() else {
return Empty.into_any();
};
if !store.is_indexing(&package) {
return Empty.into_any();
}
@ -3434,7 +3439,7 @@ fn render_rustdoc_slash_command_trailer(
Animation::new(Duration::from_secs(4)).repeat(),
|icon, delta| icon.transform(Transformation::rotate(percentage(delta))),
))
.tooltip(move |cx| Tooltip::text(format!("Indexing {crate_name}"), cx))
.tooltip(move |cx| Tooltip::text(format!("Indexing {package}"), cx))
.into_any_element()
}