
This PR updates the extension API to use structured slash command completions instead of plain strings. This allows slash commands defined in extensions to take advantage of the improvements made in #13876. Release Notes: - N/A
320 lines
9.6 KiB
Rust
320 lines
9.6 KiB
Rust
//! The Zed Rust Extension API allows you write extensions for [Zed](https://zed.dev/) in Rust.
|
|
|
|
/// Provides access to Zed settings.
|
|
pub mod settings;
|
|
|
|
use core::fmt;
|
|
|
|
use wit::*;
|
|
|
|
pub use serde_json;
|
|
|
|
// WIT re-exports.
|
|
//
|
|
// We explicitly enumerate the symbols we want to re-export, as there are some
|
|
// that we may want to shadow to provide a cleaner Rust API.
|
|
pub use wit::{
|
|
download_file, make_file_executable,
|
|
zed::extension::github::{
|
|
github_release_by_tag_name, latest_github_release, GithubRelease, GithubReleaseAsset,
|
|
GithubReleaseOptions,
|
|
},
|
|
zed::extension::http_client::{fetch, HttpRequest, HttpResponse},
|
|
zed::extension::nodejs::{
|
|
node_binary_path, npm_install_package, npm_package_installed_version,
|
|
npm_package_latest_version,
|
|
},
|
|
zed::extension::platform::{current_platform, Architecture, Os},
|
|
zed::extension::slash_command::{
|
|
SlashCommand, SlashCommandArgumentCompletion, SlashCommandOutput, SlashCommandOutputSection,
|
|
},
|
|
CodeLabel, CodeLabelSpan, CodeLabelSpanLiteral, Command, DownloadedFileType, EnvVars,
|
|
KeyValueStore, LanguageServerInstallationStatus, Range, Worktree,
|
|
};
|
|
|
|
// Undocumented WIT re-exports.
|
|
//
|
|
// These are symbols that need to be public for the purposes of implementing
|
|
// the extension host, but aren't relevant to extension authors.
|
|
#[doc(hidden)]
|
|
pub use wit::Guest;
|
|
|
|
/// Constructs for interacting with language servers over the
|
|
/// Language Server Protocol (LSP).
|
|
pub mod lsp {
|
|
pub use crate::wit::zed::extension::lsp::{
|
|
Completion, CompletionKind, InsertTextFormat, Symbol, SymbolKind,
|
|
};
|
|
}
|
|
|
|
/// A result returned from a Zed extension.
|
|
pub type Result<T, E = String> = core::result::Result<T, E>;
|
|
|
|
/// Updates the installation status for the given language server.
|
|
pub fn set_language_server_installation_status(
|
|
language_server_id: &LanguageServerId,
|
|
status: &LanguageServerInstallationStatus,
|
|
) {
|
|
wit::set_language_server_installation_status(&language_server_id.0, status)
|
|
}
|
|
|
|
/// A Zed extension.
|
|
pub trait Extension: Send + Sync {
|
|
/// Returns a new instance of the extension.
|
|
fn new() -> Self
|
|
where
|
|
Self: Sized;
|
|
|
|
/// Returns the command used to start the language server for the specified
|
|
/// language.
|
|
fn language_server_command(
|
|
&mut self,
|
|
_language_server_id: &LanguageServerId,
|
|
_worktree: &Worktree,
|
|
) -> Result<Command> {
|
|
Err("`language_server_command` not implemented".to_string())
|
|
}
|
|
|
|
/// Returns the initialization options to pass to the specified language server.
|
|
fn language_server_initialization_options(
|
|
&mut self,
|
|
_language_server_id: &LanguageServerId,
|
|
_worktree: &Worktree,
|
|
) -> Result<Option<serde_json::Value>> {
|
|
Ok(None)
|
|
}
|
|
|
|
/// Returns the workspace configuration options to pass to the language server.
|
|
fn language_server_workspace_configuration(
|
|
&mut self,
|
|
_language_server_id: &LanguageServerId,
|
|
_worktree: &Worktree,
|
|
) -> Result<Option<serde_json::Value>> {
|
|
Ok(None)
|
|
}
|
|
|
|
/// Returns the label for the given completion.
|
|
fn label_for_completion(
|
|
&self,
|
|
_language_server_id: &LanguageServerId,
|
|
_completion: Completion,
|
|
) -> Option<CodeLabel> {
|
|
None
|
|
}
|
|
|
|
/// Returns the label for the given symbol.
|
|
fn label_for_symbol(
|
|
&self,
|
|
_language_server_id: &LanguageServerId,
|
|
_symbol: Symbol,
|
|
) -> Option<CodeLabel> {
|
|
None
|
|
}
|
|
|
|
/// Returns the completions that should be shown when completing the provided slash command with the given query.
|
|
fn complete_slash_command_argument(
|
|
&self,
|
|
_command: SlashCommand,
|
|
_query: String,
|
|
) -> Result<Vec<SlashCommandArgumentCompletion>, String> {
|
|
Ok(Vec::new())
|
|
}
|
|
|
|
/// Returns the output from running the provided slash command.
|
|
fn run_slash_command(
|
|
&self,
|
|
_command: SlashCommand,
|
|
_argument: Option<String>,
|
|
_worktree: &Worktree,
|
|
) -> Result<SlashCommandOutput, String> {
|
|
Err("`run_slash_command` not implemented".to_string())
|
|
}
|
|
|
|
fn index_docs(
|
|
&self,
|
|
_provider: String,
|
|
_package: String,
|
|
_database: &KeyValueStore,
|
|
) -> Result<(), String> {
|
|
Err("`index_docs` not implemented".to_string())
|
|
}
|
|
}
|
|
|
|
/// Registers the provided type as a Zed extension.
|
|
///
|
|
/// The type must implement the [`Extension`] trait.
|
|
#[macro_export]
|
|
macro_rules! register_extension {
|
|
($extension_type:ty) => {
|
|
#[export_name = "init-extension"]
|
|
pub extern "C" fn __init_extension() {
|
|
std::env::set_current_dir(std::env::var("PWD").unwrap()).unwrap();
|
|
zed_extension_api::register_extension(|| {
|
|
Box::new(<$extension_type as zed_extension_api::Extension>::new())
|
|
});
|
|
}
|
|
};
|
|
}
|
|
|
|
#[doc(hidden)]
|
|
pub fn register_extension(build_extension: fn() -> Box<dyn Extension>) {
|
|
unsafe { EXTENSION = Some((build_extension)()) }
|
|
}
|
|
|
|
fn extension() -> &'static mut dyn Extension {
|
|
unsafe { EXTENSION.as_deref_mut().unwrap() }
|
|
}
|
|
|
|
static mut EXTENSION: Option<Box<dyn Extension>> = None;
|
|
|
|
#[cfg(target_arch = "wasm32")]
|
|
#[link_section = "zed:api-version"]
|
|
#[doc(hidden)]
|
|
pub static ZED_API_VERSION: [u8; 6] = *include_bytes!(concat!(env!("OUT_DIR"), "/version_bytes"));
|
|
|
|
mod wit {
|
|
#![allow(clippy::too_many_arguments)]
|
|
|
|
wit_bindgen::generate!({
|
|
skip: ["init-extension"],
|
|
path: "./wit/since_v0.0.7",
|
|
});
|
|
}
|
|
|
|
wit::export!(Component);
|
|
|
|
struct Component;
|
|
|
|
impl wit::Guest for Component {
|
|
fn language_server_command(
|
|
language_server_id: String,
|
|
worktree: &wit::Worktree,
|
|
) -> Result<wit::Command> {
|
|
let language_server_id = LanguageServerId(language_server_id);
|
|
extension().language_server_command(&language_server_id, worktree)
|
|
}
|
|
|
|
fn language_server_initialization_options(
|
|
language_server_id: String,
|
|
worktree: &Worktree,
|
|
) -> Result<Option<String>, String> {
|
|
let language_server_id = LanguageServerId(language_server_id);
|
|
Ok(extension()
|
|
.language_server_initialization_options(&language_server_id, worktree)?
|
|
.and_then(|value| serde_json::to_string(&value).ok()))
|
|
}
|
|
|
|
fn language_server_workspace_configuration(
|
|
language_server_id: String,
|
|
worktree: &Worktree,
|
|
) -> Result<Option<String>, String> {
|
|
let language_server_id = LanguageServerId(language_server_id);
|
|
Ok(extension()
|
|
.language_server_workspace_configuration(&language_server_id, worktree)?
|
|
.and_then(|value| serde_json::to_string(&value).ok()))
|
|
}
|
|
|
|
fn labels_for_completions(
|
|
language_server_id: String,
|
|
completions: Vec<Completion>,
|
|
) -> Result<Vec<Option<CodeLabel>>, String> {
|
|
let language_server_id = LanguageServerId(language_server_id);
|
|
let mut labels = Vec::new();
|
|
for (ix, completion) in completions.into_iter().enumerate() {
|
|
let label = extension().label_for_completion(&language_server_id, completion);
|
|
if let Some(label) = label {
|
|
labels.resize(ix + 1, None);
|
|
*labels.last_mut().unwrap() = Some(label);
|
|
}
|
|
}
|
|
Ok(labels)
|
|
}
|
|
|
|
fn labels_for_symbols(
|
|
language_server_id: String,
|
|
symbols: Vec<Symbol>,
|
|
) -> Result<Vec<Option<CodeLabel>>, String> {
|
|
let language_server_id = LanguageServerId(language_server_id);
|
|
let mut labels = Vec::new();
|
|
for (ix, symbol) in symbols.into_iter().enumerate() {
|
|
let label = extension().label_for_symbol(&language_server_id, symbol);
|
|
if let Some(label) = label {
|
|
labels.resize(ix + 1, None);
|
|
*labels.last_mut().unwrap() = Some(label);
|
|
}
|
|
}
|
|
Ok(labels)
|
|
}
|
|
|
|
fn complete_slash_command_argument(
|
|
command: SlashCommand,
|
|
query: String,
|
|
) -> Result<Vec<SlashCommandArgumentCompletion>, String> {
|
|
extension().complete_slash_command_argument(command, query)
|
|
}
|
|
|
|
fn run_slash_command(
|
|
command: SlashCommand,
|
|
argument: Option<String>,
|
|
worktree: &Worktree,
|
|
) -> Result<SlashCommandOutput, String> {
|
|
extension().run_slash_command(command, argument, worktree)
|
|
}
|
|
|
|
fn index_docs(
|
|
provider: String,
|
|
package: String,
|
|
database: &KeyValueStore,
|
|
) -> Result<(), String> {
|
|
extension().index_docs(provider, package, database)
|
|
}
|
|
}
|
|
|
|
/// The ID of a language server.
|
|
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)]
|
|
pub struct LanguageServerId(String);
|
|
|
|
impl AsRef<str> for LanguageServerId {
|
|
fn as_ref(&self) -> &str {
|
|
&self.0
|
|
}
|
|
}
|
|
|
|
impl fmt::Display for LanguageServerId {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
write!(f, "{}", self.0)
|
|
}
|
|
}
|
|
|
|
impl CodeLabelSpan {
|
|
/// Returns a [`CodeLabelSpan::CodeRange`].
|
|
pub fn code_range(range: impl Into<wit::Range>) -> Self {
|
|
Self::CodeRange(range.into())
|
|
}
|
|
|
|
/// Returns a [`CodeLabelSpan::Literal`].
|
|
pub fn literal(text: impl Into<String>, highlight_name: Option<String>) -> Self {
|
|
Self::Literal(CodeLabelSpanLiteral {
|
|
text: text.into(),
|
|
highlight_name,
|
|
})
|
|
}
|
|
}
|
|
|
|
impl From<std::ops::Range<u32>> for wit::Range {
|
|
fn from(value: std::ops::Range<u32>) -> Self {
|
|
Self {
|
|
start: value.start,
|
|
end: value.end,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<std::ops::Range<usize>> for wit::Range {
|
|
fn from(value: std::ops::Range<usize>) -> Self {
|
|
Self {
|
|
start: value.start as u32,
|
|
end: value.end as u32,
|
|
}
|
|
}
|
|
}
|