### Background Zed extensions use WASI to access the file-system. They only have read-write access to one specific folder called their work dir. But extensions do need to be able to *refer* to other arbitrary files on the user's machine. For instance, extensions need to be able to look up existing binaries on the user's `PATH`, and request that Zed invoke them as language servers. Similarly, extensions can create paths to files in the user's project, and use them as arguments in commands that Zed should run. For these reasons, we pass *real* paths back and forth between the host and extensions; we don't try to abstract over the file-system with some virtualization scheme. On Windows, this results in a bit of mismatch, because `wasi-libc` uses *unix-like* path conventions (and thus, so does the Rust standard library when compiling to WASI). ### Change 1 - Fixing `current_dir` In order to keep the extension API minimal, extensions use the standard library function`env::current_dir()` to query the location of their "work" directory. Previously, when initializing extensions, we used the `env::set_current_dir` function to set their work directory, but on Windows, where absolute paths typically begin with a drive letter, like `C:`, the [`wasi-libc` implementation of `chdir`](https://github.com/WebAssembly/wasi-libc/blob/d1793637d8afcdc730408e7b6a19a050c3336ce7/libc-bottom-half/sources/chdir.c#L21) was prepending an extra forward slash to the path, which caused `current_dir()` to return an invalid path. See https://github.com/bytecodealliance/wasmtime/issues/10415 In this PR, I've switched our extension initialization function to *bypass* wasi-libc's `chdir` function, and instead write directly to wasi-libc's private, internal state. This is a bit of a hack, but it causes the `current_dir()` function to do what we want on Windows without any changes to extensions' source code. ### Change 2 - Working around WASI's relative path handling Once `current_dir` was fixed (giving us correct absolute paths on Windows), @kubkon and I discovered that without the spurious leading `/` character, windows absolute paths were no longer accepted by Rust's `std::fs` APIs, because they were now recognized as relative paths, and were being appended to the working directory. We first tried to override the `__wasilibc_find_abspath` function in `wasi-libc` to make it recognize windows absolute paths as being absolute, but that functionality is difficult to override. Eventually @kubkon realized that we could prevent WASI-libc's CWD handling from being linked into the WASM file by overriding the `chdir` function. wasi-libc is designed so that if you don't use their `chdir` function, then all paths will be interpreted as relative to `/`. This makes absolute paths behave correctly. Then, in order to make *relative* paths work again, we simply add a preopen for `.`. Relative paths will match that. ### Next Steps This is a change to `zed-extension-api`, so we do need to update every Zed extension to use the new version, in order for them to work on windows. Release Notes: - N/A --------- Co-authored-by: Jakub Konka <kubkon@jakubkonka.com>
587 lines
20 KiB
Rust
587 lines
20 KiB
Rust
//! The Zed Rust Extension API allows you write extensions for [Zed](https://zed.dev/) in Rust.
|
|
|
|
pub mod http_client;
|
|
pub mod process;
|
|
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::{
|
|
CodeLabel, CodeLabelSpan, CodeLabelSpanLiteral, Command, DownloadedFileType, EnvVars,
|
|
KeyValueStore, LanguageServerInstallationStatus, Project, Range, Worktree, download_file,
|
|
make_file_executable,
|
|
zed::extension::context_server::ContextServerConfiguration,
|
|
zed::extension::dap::{
|
|
AttachRequest, BuildTaskDefinition, BuildTaskDefinitionTemplatePayload, BuildTaskTemplate,
|
|
DebugAdapterBinary, DebugConfig, DebugRequest, DebugScenario, DebugTaskDefinition,
|
|
LaunchRequest, StartDebuggingRequestArguments, StartDebuggingRequestArgumentsRequest,
|
|
TaskTemplate, TcpArguments, TcpArgumentsTemplate, resolve_tcp_template,
|
|
},
|
|
zed::extension::github::{
|
|
GithubRelease, GithubReleaseAsset, GithubReleaseOptions, github_release_by_tag_name,
|
|
latest_github_release,
|
|
},
|
|
zed::extension::nodejs::{
|
|
node_binary_path, npm_install_package, npm_package_installed_version,
|
|
npm_package_latest_version,
|
|
},
|
|
zed::extension::platform::{Architecture, Os, current_platform},
|
|
zed::extension::slash_command::{
|
|
SlashCommand, SlashCommandArgumentCompletion, SlashCommandOutput, SlashCommandOutputSection,
|
|
},
|
|
};
|
|
|
|
// 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 initialization options to pass to the other language server.
|
|
fn language_server_additional_initialization_options(
|
|
&mut self,
|
|
_language_server_id: &LanguageServerId,
|
|
_target_language_server_id: &LanguageServerId,
|
|
_worktree: &Worktree,
|
|
) -> Result<Option<serde_json::Value>> {
|
|
Ok(None)
|
|
}
|
|
|
|
/// Returns the workspace configuration options to pass to the other language server.
|
|
fn language_server_additional_workspace_configuration(
|
|
&mut self,
|
|
_language_server_id: &LanguageServerId,
|
|
_target_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,
|
|
_args: Vec<String>,
|
|
) -> Result<Vec<SlashCommandArgumentCompletion>, String> {
|
|
Ok(Vec::new())
|
|
}
|
|
|
|
/// Returns the output from running the provided slash command.
|
|
fn run_slash_command(
|
|
&self,
|
|
_command: SlashCommand,
|
|
_args: Vec<String>,
|
|
_worktree: Option<&Worktree>,
|
|
) -> Result<SlashCommandOutput, String> {
|
|
Err("`run_slash_command` not implemented".to_string())
|
|
}
|
|
|
|
/// Returns the command used to start a context server.
|
|
fn context_server_command(
|
|
&mut self,
|
|
_context_server_id: &ContextServerId,
|
|
_project: &Project,
|
|
) -> Result<Command> {
|
|
Err("`context_server_command` not implemented".to_string())
|
|
}
|
|
|
|
/// Returns the configuration options for the specified context server.
|
|
fn context_server_configuration(
|
|
&mut self,
|
|
_context_server_id: &ContextServerId,
|
|
_project: &Project,
|
|
) -> Result<Option<ContextServerConfiguration>> {
|
|
Ok(None)
|
|
}
|
|
|
|
/// Returns a list of package names as suggestions to be included in the
|
|
/// search results of the `/docs` slash command.
|
|
///
|
|
/// This can be used to provide completions for known packages (e.g., from the
|
|
/// local project or a registry) before a package has been indexed.
|
|
fn suggest_docs_packages(&self, _provider: String) -> Result<Vec<String>, String> {
|
|
Ok(Vec::new())
|
|
}
|
|
|
|
/// Indexes the docs for the specified package.
|
|
fn index_docs(
|
|
&self,
|
|
_provider: String,
|
|
_package: String,
|
|
_database: &KeyValueStore,
|
|
) -> Result<(), String> {
|
|
Err("`index_docs` not implemented".to_string())
|
|
}
|
|
|
|
/// Returns the debug adapter binary for the specified adapter name and configuration.
|
|
fn get_dap_binary(
|
|
&mut self,
|
|
_adapter_name: String,
|
|
_config: DebugTaskDefinition,
|
|
_user_provided_debug_adapter_path: Option<String>,
|
|
_worktree: &Worktree,
|
|
) -> Result<DebugAdapterBinary, String> {
|
|
Err("`get_dap_binary` not implemented".to_string())
|
|
}
|
|
|
|
/// Determines whether the specified adapter configuration should *launch* a new debuggee process
|
|
/// or *attach* to an existing one. This function should not perform any further validation (outside of determining the kind of a request).
|
|
/// This function should return an error when the kind cannot be determined (rather than fall back to a known default).
|
|
fn dap_request_kind(
|
|
&mut self,
|
|
_adapter_name: String,
|
|
_config: serde_json::Value,
|
|
) -> Result<StartDebuggingRequestArgumentsRequest, String> {
|
|
Err("`dap_request_kind` not implemented".to_string())
|
|
}
|
|
/// Converts a high-level definition of a debug scenario (originating in a new session UI) to a "low-level" configuration suitable for a particular adapter.
|
|
///
|
|
/// In layman's terms: given a program, list of arguments, current working directory and environment variables,
|
|
/// create a configuration that can be used to start a debug session.
|
|
fn dap_config_to_scenario(&mut self, _config: DebugConfig) -> Result<DebugScenario, String> {
|
|
Err("`dap_config_to_scenario` not implemented".to_string())
|
|
}
|
|
|
|
/// Locators are entities that convert a Zed task into a debug scenario.
|
|
///
|
|
/// They can be provided even by extensions that don't provide a debug adapter.
|
|
/// For all tasks applicable to a given buffer, Zed will query all locators to find one that can turn the task into a debug scenario.
|
|
/// A converted debug scenario can include a build task (it shouldn't contain any configuration in such case); a build task result will later
|
|
/// be resolved with [`Extension::run_dap_locator`].
|
|
///
|
|
/// To work through a real-world example, take a `cargo run` task and a hypothetical `cargo` locator:
|
|
/// 1. We may need to modify the task; in this case, it is problematic that `cargo run` spawns a binary. We should turn `cargo run` into a debug scenario with
|
|
/// `cargo build` task. This is the decision we make at `dap_locator_create_scenario` scope.
|
|
/// 2. Then, after the build task finishes, we will run `run_dap_locator` of the locator that produced the build task to find the program to be debugged. This function
|
|
/// should give us a debugger-agnostic configuration for launching a debug target (that we end up resolving with [`Extension::dap_config_to_scenario`]). It's almost as if the user
|
|
/// found the artifact path by themselves.
|
|
///
|
|
/// Note that you're not obliged to use build tasks with locators. Specifically, it is sufficient to provide a debug configuration directly in the return value of
|
|
/// `dap_locator_create_scenario` if you're able to do that. Make sure to not fill out `build` field in that case, as that will prevent Zed from running second phase of resolution in such case.
|
|
/// This might be of particular relevance to interpreted languages.
|
|
fn dap_locator_create_scenario(
|
|
&mut self,
|
|
_locator_name: String,
|
|
_build_task: TaskTemplate,
|
|
_resolved_label: String,
|
|
_debug_adapter_name: String,
|
|
) -> Option<DebugScenario> {
|
|
None
|
|
}
|
|
|
|
/// Runs the second phase of locator resolution.
|
|
/// See [`Extension::dap_locator_create_scenario`] for a hefty comment on locators.
|
|
fn run_dap_locator(
|
|
&mut self,
|
|
_locator_name: String,
|
|
_build_task: TaskTemplate,
|
|
) -> Result<DebugRequest, String> {
|
|
Err("`run_dap_locator` 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) => {
|
|
#[cfg(target_os = "wasi")]
|
|
mod wasi_ext {
|
|
unsafe extern "C" {
|
|
static mut errno: i32;
|
|
pub static mut __wasilibc_cwd: *mut std::ffi::c_char;
|
|
}
|
|
|
|
pub fn init_cwd() {
|
|
unsafe {
|
|
// Ensure that our chdir function is linked, instead of the
|
|
// one from wasi-libc in the chdir.o translation unit. Otherwise
|
|
// we risk linking in `__wasilibc_find_relpath_alloc` which
|
|
// is a weak symbol and is being used by
|
|
// `__wasilibc_find_relpath`, which we do not want on
|
|
// Windows.
|
|
chdir(std::ptr::null());
|
|
|
|
__wasilibc_cwd = std::ffi::CString::new(std::env::var("PWD").unwrap())
|
|
.unwrap()
|
|
.into_raw()
|
|
.cast();
|
|
}
|
|
}
|
|
|
|
#[unsafe(no_mangle)]
|
|
pub unsafe extern "C" fn chdir(raw_path: *const std::ffi::c_char) -> i32 {
|
|
// Forbid extensions from changing CWD and so return an appropriate error code.
|
|
errno = 58; // NOTSUP
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
#[unsafe(export_name = "init-extension")]
|
|
pub extern "C" fn __init_extension() {
|
|
#[cfg(target_os = "wasi")]
|
|
wasi_ext::init_cwd();
|
|
|
|
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 {
|
|
#[expect(static_mut_refs)]
|
|
unsafe {
|
|
EXTENSION.as_deref_mut().unwrap()
|
|
}
|
|
}
|
|
|
|
static mut EXTENSION: Option<Box<dyn Extension>> = None;
|
|
|
|
#[cfg(target_arch = "wasm32")]
|
|
#[unsafe(link_section = "zed:api-version")]
|
|
#[doc(hidden)]
|
|
pub static ZED_API_VERSION: [u8; 6] = *include_bytes!(concat!(env!("OUT_DIR"), "/version_bytes"));
|
|
|
|
mod wit {
|
|
|
|
wit_bindgen::generate!({
|
|
skip: ["init-extension"],
|
|
path: "./wit/since_v0.6.0",
|
|
});
|
|
}
|
|
|
|
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 language_server_additional_initialization_options(
|
|
language_server_id: String,
|
|
target_language_server_id: String,
|
|
worktree: &Worktree,
|
|
) -> Result<Option<String>, String> {
|
|
let language_server_id = LanguageServerId(language_server_id);
|
|
let target_language_server_id = LanguageServerId(target_language_server_id);
|
|
Ok(extension()
|
|
.language_server_additional_initialization_options(
|
|
&language_server_id,
|
|
&target_language_server_id,
|
|
worktree,
|
|
)?
|
|
.and_then(|value| serde_json::to_string(&value).ok()))
|
|
}
|
|
|
|
fn language_server_additional_workspace_configuration(
|
|
language_server_id: String,
|
|
target_language_server_id: String,
|
|
worktree: &Worktree,
|
|
) -> Result<Option<String>, String> {
|
|
let language_server_id = LanguageServerId(language_server_id);
|
|
let target_language_server_id = LanguageServerId(target_language_server_id);
|
|
Ok(extension()
|
|
.language_server_additional_workspace_configuration(
|
|
&language_server_id,
|
|
&target_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,
|
|
args: Vec<String>,
|
|
) -> Result<Vec<SlashCommandArgumentCompletion>, String> {
|
|
extension().complete_slash_command_argument(command, args)
|
|
}
|
|
|
|
fn run_slash_command(
|
|
command: SlashCommand,
|
|
args: Vec<String>,
|
|
worktree: Option<&Worktree>,
|
|
) -> Result<SlashCommandOutput, String> {
|
|
extension().run_slash_command(command, args, worktree)
|
|
}
|
|
|
|
fn context_server_command(
|
|
context_server_id: String,
|
|
project: &Project,
|
|
) -> Result<wit::Command> {
|
|
let context_server_id = ContextServerId(context_server_id);
|
|
extension().context_server_command(&context_server_id, project)
|
|
}
|
|
|
|
fn context_server_configuration(
|
|
context_server_id: String,
|
|
project: &Project,
|
|
) -> Result<Option<ContextServerConfiguration>, String> {
|
|
let context_server_id = ContextServerId(context_server_id);
|
|
extension().context_server_configuration(&context_server_id, project)
|
|
}
|
|
|
|
fn suggest_docs_packages(provider: String) -> Result<Vec<String>, String> {
|
|
extension().suggest_docs_packages(provider)
|
|
}
|
|
|
|
fn index_docs(
|
|
provider: String,
|
|
package: String,
|
|
database: &KeyValueStore,
|
|
) -> Result<(), String> {
|
|
extension().index_docs(provider, package, database)
|
|
}
|
|
|
|
fn get_dap_binary(
|
|
adapter_name: String,
|
|
config: DebugTaskDefinition,
|
|
user_installed_path: Option<String>,
|
|
worktree: &Worktree,
|
|
) -> Result<wit::DebugAdapterBinary, String> {
|
|
extension().get_dap_binary(adapter_name, config, user_installed_path, worktree)
|
|
}
|
|
|
|
fn dap_request_kind(
|
|
adapter_name: String,
|
|
config: String,
|
|
) -> Result<StartDebuggingRequestArgumentsRequest, String> {
|
|
extension().dap_request_kind(
|
|
adapter_name,
|
|
serde_json::from_str(&config).map_err(|e| format!("Failed to parse config: {e}"))?,
|
|
)
|
|
}
|
|
fn dap_config_to_scenario(config: DebugConfig) -> Result<DebugScenario, String> {
|
|
extension().dap_config_to_scenario(config)
|
|
}
|
|
fn dap_locator_create_scenario(
|
|
locator_name: String,
|
|
build_task: TaskTemplate,
|
|
resolved_label: String,
|
|
debug_adapter_name: String,
|
|
) -> Option<DebugScenario> {
|
|
extension().dap_locator_create_scenario(
|
|
locator_name,
|
|
build_task,
|
|
resolved_label,
|
|
debug_adapter_name,
|
|
)
|
|
}
|
|
fn run_dap_locator(
|
|
locator_name: String,
|
|
build_task: TaskTemplate,
|
|
) -> Result<DebugRequest, String> {
|
|
extension().run_dap_locator(locator_name, build_task)
|
|
}
|
|
}
|
|
|
|
/// 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)
|
|
}
|
|
}
|
|
|
|
/// The ID of a context server.
|
|
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)]
|
|
pub struct ContextServerId(String);
|
|
|
|
impl AsRef<str> for ContextServerId {
|
|
fn as_ref(&self) -> &str {
|
|
&self.0
|
|
}
|
|
}
|
|
|
|
impl fmt::Display for ContextServerId {
|
|
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,
|
|
}
|
|
}
|
|
}
|