zed_extension_api: Fork new version of extension API (#30611)
This PR forks a new version of the `zed_extension_api` in preparation for new changes. Release Notes: - N/A
This commit is contained in:
parent
54c6d482b6
commit
18e911002f
17 changed files with 1504 additions and 669 deletions
6
Cargo.lock
generated
6
Cargo.lock
generated
|
@ -10037,7 +10037,7 @@ name = "perplexity"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"zed_extension_api 0.5.0",
|
||||
"zed_extension_api 0.6.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -18695,7 +18695,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "zed_extension_api"
|
||||
version = "0.5.0"
|
||||
version = "0.6.0"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
|
@ -18755,7 +18755,7 @@ dependencies = [
|
|||
name = "zed_test_extension"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"zed_extension_api 0.5.0",
|
||||
"zed_extension_api 0.6.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
[package]
|
||||
name = "zed_extension_api"
|
||||
version = "0.5.0"
|
||||
version = "0.6.0"
|
||||
description = "APIs for creating Zed extensions in Rust"
|
||||
repository = "https://github.com/zed-industries/zed"
|
||||
documentation = "https://docs.rs/zed_extension_api"
|
||||
keywords = ["zed", "extension"]
|
||||
edition.workspace = true
|
||||
publish = true
|
||||
# Change back to `true` when we're ready to publish v0.6.0.
|
||||
publish = false
|
||||
license = "Apache-2.0"
|
||||
|
||||
[lints]
|
||||
|
|
|
@ -228,7 +228,7 @@ mod wit {
|
|||
|
||||
wit_bindgen::generate!({
|
||||
skip: ["init-extension"],
|
||||
path: "./wit/since_v0.5.0",
|
||||
path: "./wit/since_v0.6.0",
|
||||
});
|
||||
}
|
||||
|
||||
|
|
12
crates/extension_api/wit/since_v0.6.0/common.wit
Normal file
12
crates/extension_api/wit/since_v0.6.0/common.wit
Normal file
|
@ -0,0 +1,12 @@
|
|||
interface common {
|
||||
/// A (half-open) range (`[start, end)`).
|
||||
record range {
|
||||
/// The start of the range (inclusive).
|
||||
start: u32,
|
||||
/// The end of the range (exclusive).
|
||||
end: u32,
|
||||
}
|
||||
|
||||
/// A list of environment variables.
|
||||
type env-vars = list<tuple<string, string>>;
|
||||
}
|
11
crates/extension_api/wit/since_v0.6.0/context-server.wit
Normal file
11
crates/extension_api/wit/since_v0.6.0/context-server.wit
Normal file
|
@ -0,0 +1,11 @@
|
|||
interface context-server {
|
||||
/// Configuration for context server setup and installation.
|
||||
record context-server-configuration {
|
||||
/// Installation instructions in Markdown format.
|
||||
installation-instructions: string,
|
||||
/// JSON schema for settings validation.
|
||||
settings-schema: string,
|
||||
/// Default settings template.
|
||||
default-settings: string,
|
||||
}
|
||||
}
|
156
crates/extension_api/wit/since_v0.6.0/extension.wit
Normal file
156
crates/extension_api/wit/since_v0.6.0/extension.wit
Normal file
|
@ -0,0 +1,156 @@
|
|||
package zed:extension;
|
||||
|
||||
world extension {
|
||||
import context-server;
|
||||
import github;
|
||||
import http-client;
|
||||
import platform;
|
||||
import process;
|
||||
import nodejs;
|
||||
|
||||
use common.{env-vars, range};
|
||||
use context-server.{context-server-configuration};
|
||||
use lsp.{completion, symbol};
|
||||
use process.{command};
|
||||
use slash-command.{slash-command, slash-command-argument-completion, slash-command-output};
|
||||
|
||||
/// Initializes the extension.
|
||||
export init-extension: func();
|
||||
|
||||
/// The type of a downloaded file.
|
||||
enum downloaded-file-type {
|
||||
/// A gzipped file (`.gz`).
|
||||
gzip,
|
||||
/// A gzipped tar archive (`.tar.gz`).
|
||||
gzip-tar,
|
||||
/// A ZIP file (`.zip`).
|
||||
zip,
|
||||
/// An uncompressed file.
|
||||
uncompressed,
|
||||
}
|
||||
|
||||
/// The installation status for a language server.
|
||||
variant language-server-installation-status {
|
||||
/// The language server has no installation status.
|
||||
none,
|
||||
/// The language server is being downloaded.
|
||||
downloading,
|
||||
/// The language server is checking for updates.
|
||||
checking-for-update,
|
||||
/// The language server installation failed for specified reason.
|
||||
failed(string),
|
||||
}
|
||||
|
||||
record settings-location {
|
||||
worktree-id: u64,
|
||||
path: string,
|
||||
}
|
||||
|
||||
import get-settings: func(path: option<settings-location>, category: string, key: option<string>) -> result<string, string>;
|
||||
|
||||
/// Downloads a file from the given URL and saves it to the given path within the extension's
|
||||
/// working directory.
|
||||
///
|
||||
/// The file will be extracted according to the given file type.
|
||||
import download-file: func(url: string, file-path: string, file-type: downloaded-file-type) -> result<_, string>;
|
||||
|
||||
/// Makes the file at the given path executable.
|
||||
import make-file-executable: func(filepath: string) -> result<_, string>;
|
||||
|
||||
/// Updates the installation status for the given language server.
|
||||
import set-language-server-installation-status: func(language-server-name: string, status: language-server-installation-status);
|
||||
|
||||
/// A Zed worktree.
|
||||
resource worktree {
|
||||
/// Returns the ID of the worktree.
|
||||
id: func() -> u64;
|
||||
/// Returns the root path of the worktree.
|
||||
root-path: func() -> string;
|
||||
/// Returns the textual contents of the specified file in the worktree.
|
||||
read-text-file: func(path: string) -> result<string, string>;
|
||||
/// Returns the path to the given binary name, if one is present on the `$PATH`.
|
||||
which: func(binary-name: string) -> option<string>;
|
||||
/// Returns the current shell environment.
|
||||
shell-env: func() -> env-vars;
|
||||
}
|
||||
|
||||
/// A Zed project.
|
||||
resource project {
|
||||
/// Returns the IDs of all of the worktrees in this project.
|
||||
worktree-ids: func() -> list<u64>;
|
||||
}
|
||||
|
||||
/// A key-value store.
|
||||
resource key-value-store {
|
||||
/// Inserts an entry under the specified key.
|
||||
insert: func(key: string, value: string) -> result<_, string>;
|
||||
}
|
||||
|
||||
/// Returns the command used to start up the language server.
|
||||
export language-server-command: func(language-server-id: string, worktree: borrow<worktree>) -> result<command, string>;
|
||||
|
||||
/// Returns the initialization options to pass to the language server on startup.
|
||||
///
|
||||
/// The initialization options are represented as a JSON string.
|
||||
export language-server-initialization-options: func(language-server-id: string, worktree: borrow<worktree>) -> result<option<string>, string>;
|
||||
|
||||
/// Returns the workspace configuration options to pass to the language server.
|
||||
export language-server-workspace-configuration: func(language-server-id: string, worktree: borrow<worktree>) -> result<option<string>, string>;
|
||||
|
||||
/// Returns the initialization options to pass to the other language server.
|
||||
export language-server-additional-initialization-options: func(language-server-id: string, target-language-server-id: string, worktree: borrow<worktree>) -> result<option<string>, string>;
|
||||
|
||||
/// Returns the workspace configuration options to pass to the other language server.
|
||||
export language-server-additional-workspace-configuration: func(language-server-id: string, target-language-server-id: string, worktree: borrow<worktree>) -> result<option<string>, string>;
|
||||
|
||||
/// A label containing some code.
|
||||
record code-label {
|
||||
/// The source code to parse with Tree-sitter.
|
||||
code: string,
|
||||
/// The spans to display in the label.
|
||||
spans: list<code-label-span>,
|
||||
/// The range of the displayed label to include when filtering.
|
||||
filter-range: range,
|
||||
}
|
||||
|
||||
/// A span within a code label.
|
||||
variant code-label-span {
|
||||
/// A range into the parsed code.
|
||||
code-range(range),
|
||||
/// A span containing a code literal.
|
||||
literal(code-label-span-literal),
|
||||
}
|
||||
|
||||
/// A span containing a code literal.
|
||||
record code-label-span-literal {
|
||||
/// The literal text.
|
||||
text: string,
|
||||
/// The name of the highlight to use for this literal.
|
||||
highlight-name: option<string>,
|
||||
}
|
||||
|
||||
export labels-for-completions: func(language-server-id: string, completions: list<completion>) -> result<list<option<code-label>>, string>;
|
||||
export labels-for-symbols: func(language-server-id: string, symbols: list<symbol>) -> result<list<option<code-label>>, string>;
|
||||
|
||||
/// Returns the completions that should be shown when completing the provided slash command with the given query.
|
||||
export complete-slash-command-argument: func(command: slash-command, args: list<string>) -> result<list<slash-command-argument-completion>, string>;
|
||||
|
||||
/// Returns the output from running the provided slash command.
|
||||
export run-slash-command: func(command: slash-command, args: list<string>, worktree: option<borrow<worktree>>) -> result<slash-command-output, string>;
|
||||
|
||||
/// Returns the command used to start up a context server.
|
||||
export context-server-command: func(context-server-id: string, project: borrow<project>) -> result<command, string>;
|
||||
|
||||
/// Returns the configuration for a context server.
|
||||
export context-server-configuration: func(context-server-id: string, project: borrow<project>) -> result<option<context-server-configuration>, string>;
|
||||
|
||||
/// Returns a list of packages as suggestions to be included in the `/docs`
|
||||
/// search results.
|
||||
///
|
||||
/// 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.
|
||||
export suggest-docs-packages: func(provider-name: string) -> result<list<string>, string>;
|
||||
|
||||
/// Indexes the docs for the specified package.
|
||||
export index-docs: func(provider-name: string, package-name: string, database: borrow<key-value-store>) -> result<_, string>;
|
||||
}
|
35
crates/extension_api/wit/since_v0.6.0/github.wit
Normal file
35
crates/extension_api/wit/since_v0.6.0/github.wit
Normal file
|
@ -0,0 +1,35 @@
|
|||
interface github {
|
||||
/// A GitHub release.
|
||||
record github-release {
|
||||
/// The version of the release.
|
||||
version: string,
|
||||
/// The list of assets attached to the release.
|
||||
assets: list<github-release-asset>,
|
||||
}
|
||||
|
||||
/// An asset from a GitHub release.
|
||||
record github-release-asset {
|
||||
/// The name of the asset.
|
||||
name: string,
|
||||
/// The download URL for the asset.
|
||||
download-url: string,
|
||||
}
|
||||
|
||||
/// The options used to filter down GitHub releases.
|
||||
record github-release-options {
|
||||
/// Whether releases without assets should be included.
|
||||
require-assets: bool,
|
||||
/// Whether pre-releases should be included.
|
||||
pre-release: bool,
|
||||
}
|
||||
|
||||
/// Returns the latest release for the given GitHub repository.
|
||||
///
|
||||
/// Takes repo as a string in the form "<owner-name>/<repo-name>", for example: "zed-industries/zed".
|
||||
latest-github-release: func(repo: string, options: github-release-options) -> result<github-release, string>;
|
||||
|
||||
/// Returns the GitHub release with the specified tag name for the given GitHub repository.
|
||||
///
|
||||
/// Returns an error if a release with the given tag name does not exist.
|
||||
github-release-by-tag-name: func(repo: string, tag: string) -> result<github-release, string>;
|
||||
}
|
67
crates/extension_api/wit/since_v0.6.0/http-client.wit
Normal file
67
crates/extension_api/wit/since_v0.6.0/http-client.wit
Normal file
|
@ -0,0 +1,67 @@
|
|||
interface http-client {
|
||||
/// An HTTP request.
|
||||
record http-request {
|
||||
/// The HTTP method for the request.
|
||||
method: http-method,
|
||||
/// The URL to which the request should be made.
|
||||
url: string,
|
||||
/// The headers for the request.
|
||||
headers: list<tuple<string, string>>,
|
||||
/// The request body.
|
||||
body: option<list<u8>>,
|
||||
/// The policy to use for redirects.
|
||||
redirect-policy: redirect-policy,
|
||||
}
|
||||
|
||||
/// HTTP methods.
|
||||
enum http-method {
|
||||
/// `GET`
|
||||
get,
|
||||
/// `HEAD`
|
||||
head,
|
||||
/// `POST`
|
||||
post,
|
||||
/// `PUT`
|
||||
put,
|
||||
/// `DELETE`
|
||||
delete,
|
||||
/// `OPTIONS`
|
||||
options,
|
||||
/// `PATCH`
|
||||
patch,
|
||||
}
|
||||
|
||||
/// The policy for dealing with redirects received from the server.
|
||||
variant redirect-policy {
|
||||
/// Redirects from the server will not be followed.
|
||||
///
|
||||
/// This is the default behavior.
|
||||
no-follow,
|
||||
/// Redirects from the server will be followed up to the specified limit.
|
||||
follow-limit(u32),
|
||||
/// All redirects from the server will be followed.
|
||||
follow-all,
|
||||
}
|
||||
|
||||
/// An HTTP response.
|
||||
record http-response {
|
||||
/// The response headers.
|
||||
headers: list<tuple<string, string>>,
|
||||
/// The response body.
|
||||
body: list<u8>,
|
||||
}
|
||||
|
||||
/// Performs an HTTP request and returns the response.
|
||||
fetch: func(req: http-request) -> result<http-response, string>;
|
||||
|
||||
/// An HTTP response stream.
|
||||
resource http-response-stream {
|
||||
/// Retrieves the next chunk of data from the response stream.
|
||||
///
|
||||
/// Returns `Ok(None)` if the stream has ended.
|
||||
next-chunk: func() -> result<option<list<u8>>, string>;
|
||||
}
|
||||
|
||||
/// Performs an HTTP request and returns a response stream.
|
||||
fetch-stream: func(req: http-request) -> result<http-response-stream, string>;
|
||||
}
|
90
crates/extension_api/wit/since_v0.6.0/lsp.wit
Normal file
90
crates/extension_api/wit/since_v0.6.0/lsp.wit
Normal file
|
@ -0,0 +1,90 @@
|
|||
interface lsp {
|
||||
/// An LSP completion.
|
||||
record completion {
|
||||
label: string,
|
||||
label-details: option<completion-label-details>,
|
||||
detail: option<string>,
|
||||
kind: option<completion-kind>,
|
||||
insert-text-format: option<insert-text-format>,
|
||||
}
|
||||
|
||||
/// The kind of an LSP completion.
|
||||
variant completion-kind {
|
||||
text,
|
||||
method,
|
||||
function,
|
||||
%constructor,
|
||||
field,
|
||||
variable,
|
||||
class,
|
||||
%interface,
|
||||
module,
|
||||
property,
|
||||
unit,
|
||||
value,
|
||||
%enum,
|
||||
keyword,
|
||||
snippet,
|
||||
color,
|
||||
file,
|
||||
reference,
|
||||
folder,
|
||||
enum-member,
|
||||
constant,
|
||||
struct,
|
||||
event,
|
||||
operator,
|
||||
type-parameter,
|
||||
other(s32),
|
||||
}
|
||||
|
||||
/// Label details for an LSP completion.
|
||||
record completion-label-details {
|
||||
detail: option<string>,
|
||||
description: option<string>,
|
||||
}
|
||||
|
||||
/// Defines how to interpret the insert text in a completion item.
|
||||
variant insert-text-format {
|
||||
plain-text,
|
||||
snippet,
|
||||
other(s32),
|
||||
}
|
||||
|
||||
/// An LSP symbol.
|
||||
record symbol {
|
||||
kind: symbol-kind,
|
||||
name: string,
|
||||
}
|
||||
|
||||
/// The kind of an LSP symbol.
|
||||
variant symbol-kind {
|
||||
file,
|
||||
module,
|
||||
namespace,
|
||||
%package,
|
||||
class,
|
||||
method,
|
||||
property,
|
||||
field,
|
||||
%constructor,
|
||||
%enum,
|
||||
%interface,
|
||||
function,
|
||||
variable,
|
||||
constant,
|
||||
%string,
|
||||
number,
|
||||
boolean,
|
||||
array,
|
||||
object,
|
||||
key,
|
||||
null,
|
||||
enum-member,
|
||||
struct,
|
||||
event,
|
||||
operator,
|
||||
type-parameter,
|
||||
other(s32),
|
||||
}
|
||||
}
|
13
crates/extension_api/wit/since_v0.6.0/nodejs.wit
Normal file
13
crates/extension_api/wit/since_v0.6.0/nodejs.wit
Normal file
|
@ -0,0 +1,13 @@
|
|||
interface nodejs {
|
||||
/// Returns the path to the Node binary used by Zed.
|
||||
node-binary-path: func() -> result<string, string>;
|
||||
|
||||
/// Returns the latest version of the given NPM package.
|
||||
npm-package-latest-version: func(package-name: string) -> result<string, string>;
|
||||
|
||||
/// Returns the installed version of the given NPM package, if it exists.
|
||||
npm-package-installed-version: func(package-name: string) -> result<option<string>, string>;
|
||||
|
||||
/// Installs the specified NPM package.
|
||||
npm-install-package: func(package-name: string, version: string) -> result<_, string>;
|
||||
}
|
24
crates/extension_api/wit/since_v0.6.0/platform.wit
Normal file
24
crates/extension_api/wit/since_v0.6.0/platform.wit
Normal file
|
@ -0,0 +1,24 @@
|
|||
interface platform {
|
||||
/// An operating system.
|
||||
enum os {
|
||||
/// macOS.
|
||||
mac,
|
||||
/// Linux.
|
||||
linux,
|
||||
/// Windows.
|
||||
windows,
|
||||
}
|
||||
|
||||
/// A platform architecture.
|
||||
enum architecture {
|
||||
/// AArch64 (e.g., Apple Silicon).
|
||||
aarch64,
|
||||
/// x86.
|
||||
x86,
|
||||
/// x86-64.
|
||||
x8664,
|
||||
}
|
||||
|
||||
/// Gets the current operating system and architecture.
|
||||
current-platform: func() -> tuple<os, architecture>;
|
||||
}
|
29
crates/extension_api/wit/since_v0.6.0/process.wit
Normal file
29
crates/extension_api/wit/since_v0.6.0/process.wit
Normal file
|
@ -0,0 +1,29 @@
|
|||
interface process {
|
||||
use common.{env-vars};
|
||||
|
||||
/// A command.
|
||||
record command {
|
||||
/// The command to execute.
|
||||
command: string,
|
||||
/// The arguments to pass to the command.
|
||||
args: list<string>,
|
||||
/// The environment variables to set for the command.
|
||||
env: env-vars,
|
||||
}
|
||||
|
||||
/// The output of a finished process.
|
||||
record output {
|
||||
/// The status (exit code) of the process.
|
||||
///
|
||||
/// On Unix, this will be `None` if the process was terminated by a signal.
|
||||
status: option<s32>,
|
||||
/// The data that the process wrote to stdout.
|
||||
stdout: list<u8>,
|
||||
/// The data that the process wrote to stderr.
|
||||
stderr: list<u8>,
|
||||
}
|
||||
|
||||
/// Executes the given command as a child process, waiting for it to finish
|
||||
/// and collecting all of its output.
|
||||
run-command: func(command: command) -> result<output, string>;
|
||||
}
|
40
crates/extension_api/wit/since_v0.6.0/settings.rs
Normal file
40
crates/extension_api/wit/since_v0.6.0/settings.rs
Normal file
|
@ -0,0 +1,40 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
use std::{collections::HashMap, num::NonZeroU32};
|
||||
|
||||
/// The settings for a particular language.
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct LanguageSettings {
|
||||
/// How many columns a tab should occupy.
|
||||
pub tab_size: NonZeroU32,
|
||||
}
|
||||
|
||||
/// The settings for a particular language server.
|
||||
#[derive(Default, Debug, Serialize, Deserialize)]
|
||||
pub struct LspSettings {
|
||||
/// The settings for the language server binary.
|
||||
pub binary: Option<CommandSettings>,
|
||||
/// The initialization options to pass to the language server.
|
||||
pub initialization_options: Option<serde_json::Value>,
|
||||
/// The settings to pass to language server.
|
||||
pub settings: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
/// The settings for a particular context server.
|
||||
#[derive(Default, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct ContextServerSettings {
|
||||
/// The settings for the context server binary.
|
||||
pub command: Option<CommandSettings>,
|
||||
/// The settings to pass to the context server.
|
||||
pub settings: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
/// The settings for a command.
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct CommandSettings {
|
||||
/// The path to the command.
|
||||
pub path: Option<String>,
|
||||
/// The arguments to pass to the command.
|
||||
pub arguments: Option<Vec<String>>,
|
||||
/// The environment variables.
|
||||
pub env: Option<HashMap<String, String>>,
|
||||
}
|
41
crates/extension_api/wit/since_v0.6.0/slash-command.wit
Normal file
41
crates/extension_api/wit/since_v0.6.0/slash-command.wit
Normal file
|
@ -0,0 +1,41 @@
|
|||
interface slash-command {
|
||||
use common.{range};
|
||||
|
||||
/// A slash command for use in the Assistant.
|
||||
record slash-command {
|
||||
/// The name of the slash command.
|
||||
name: string,
|
||||
/// The description of the slash command.
|
||||
description: string,
|
||||
/// The tooltip text to display for the run button.
|
||||
tooltip-text: string,
|
||||
/// Whether this slash command requires an argument.
|
||||
requires-argument: bool,
|
||||
}
|
||||
|
||||
/// The output of a slash command.
|
||||
record slash-command-output {
|
||||
/// The text produced by the slash command.
|
||||
text: string,
|
||||
/// The list of sections to show in the slash command placeholder.
|
||||
sections: list<slash-command-output-section>,
|
||||
}
|
||||
|
||||
/// A section in the slash command output.
|
||||
record slash-command-output-section {
|
||||
/// The range this section occupies.
|
||||
range: range,
|
||||
/// The label to display in the placeholder for this section.
|
||||
label: string,
|
||||
}
|
||||
|
||||
/// A completion for a slash command argument.
|
||||
record slash-command-argument-completion {
|
||||
/// The label to display for this completion.
|
||||
label: string,
|
||||
/// The new text that should be inserted into the command when this completion is accepted.
|
||||
new-text: string,
|
||||
/// Whether the command should be run when accepting this completion.
|
||||
run-command: bool,
|
||||
}
|
||||
}
|
|
@ -6,11 +6,12 @@ mod since_v0_2_0;
|
|||
mod since_v0_3_0;
|
||||
mod since_v0_4_0;
|
||||
mod since_v0_5_0;
|
||||
mod since_v0_6_0;
|
||||
use extension::{KeyValueStoreDelegate, WorktreeDelegate};
|
||||
use language::LanguageName;
|
||||
use lsp::LanguageServerName;
|
||||
use release_channel::ReleaseChannel;
|
||||
use since_v0_5_0 as latest;
|
||||
use since_v0_6_0 as latest;
|
||||
|
||||
use super::{WasmState, wasm_engine};
|
||||
use anyhow::{Context as _, Result, anyhow};
|
||||
|
@ -62,7 +63,7 @@ pub fn wasm_api_version_range(release_channel: ReleaseChannel) -> RangeInclusive
|
|||
|
||||
let max_version = match release_channel {
|
||||
ReleaseChannel::Dev | ReleaseChannel::Nightly => latest::MAX_VERSION,
|
||||
ReleaseChannel::Stable | ReleaseChannel::Preview => latest::MAX_VERSION,
|
||||
ReleaseChannel::Stable | ReleaseChannel::Preview => since_v0_5_0::MAX_VERSION,
|
||||
};
|
||||
|
||||
since_v0_0_1::MIN_VERSION..=max_version
|
||||
|
@ -92,6 +93,7 @@ pub fn authorize_access_to_unreleased_wasm_api_version(
|
|||
}
|
||||
|
||||
pub enum Extension {
|
||||
V0_6_0(since_v0_6_0::Extension),
|
||||
V0_5_0(since_v0_5_0::Extension),
|
||||
V0_4_0(since_v0_4_0::Extension),
|
||||
V0_3_0(since_v0_3_0::Extension),
|
||||
|
@ -113,10 +115,21 @@ impl Extension {
|
|||
let _ = release_channel;
|
||||
|
||||
if version >= latest::MIN_VERSION {
|
||||
authorize_access_to_unreleased_wasm_api_version(release_channel)?;
|
||||
|
||||
let extension =
|
||||
latest::Extension::instantiate_async(store, component, latest::linker())
|
||||
.await
|
||||
.context("failed to instantiate wasm extension")?;
|
||||
Ok(Self::V0_6_0(extension))
|
||||
} else if version >= since_v0_5_0::MIN_VERSION {
|
||||
let extension = since_v0_5_0::Extension::instantiate_async(
|
||||
store,
|
||||
component,
|
||||
since_v0_5_0::linker(),
|
||||
)
|
||||
.await
|
||||
.context("failed to instantiate wasm extension")?;
|
||||
Ok(Self::V0_5_0(extension))
|
||||
} else if version >= since_v0_4_0::MIN_VERSION {
|
||||
let extension = since_v0_4_0::Extension::instantiate_async(
|
||||
|
@ -186,6 +199,7 @@ impl Extension {
|
|||
|
||||
pub async fn call_init_extension(&self, store: &mut Store<WasmState>) -> Result<()> {
|
||||
match self {
|
||||
Extension::V0_6_0(ext) => ext.call_init_extension(store).await,
|
||||
Extension::V0_5_0(ext) => ext.call_init_extension(store).await,
|
||||
Extension::V0_4_0(ext) => ext.call_init_extension(store).await,
|
||||
Extension::V0_3_0(ext) => ext.call_init_extension(store).await,
|
||||
|
@ -205,6 +219,10 @@ impl Extension {
|
|||
resource: Resource<Arc<dyn WorktreeDelegate>>,
|
||||
) -> Result<Result<Command, String>> {
|
||||
match self {
|
||||
Extension::V0_6_0(ext) => {
|
||||
ext.call_language_server_command(store, &language_server_id.0, resource)
|
||||
.await
|
||||
}
|
||||
Extension::V0_5_0(ext) => {
|
||||
ext.call_language_server_command(store, &language_server_id.0, resource)
|
||||
.await
|
||||
|
@ -263,6 +281,14 @@ impl Extension {
|
|||
resource: Resource<Arc<dyn WorktreeDelegate>>,
|
||||
) -> Result<Result<Option<String>, String>> {
|
||||
match self {
|
||||
Extension::V0_6_0(ext) => {
|
||||
ext.call_language_server_initialization_options(
|
||||
store,
|
||||
&language_server_id.0,
|
||||
resource,
|
||||
)
|
||||
.await
|
||||
}
|
||||
Extension::V0_5_0(ext) => {
|
||||
ext.call_language_server_initialization_options(
|
||||
store,
|
||||
|
@ -344,6 +370,14 @@ impl Extension {
|
|||
resource: Resource<Arc<dyn WorktreeDelegate>>,
|
||||
) -> Result<Result<Option<String>, String>> {
|
||||
match self {
|
||||
Extension::V0_6_0(ext) => {
|
||||
ext.call_language_server_workspace_configuration(
|
||||
store,
|
||||
&language_server_id.0,
|
||||
resource,
|
||||
)
|
||||
.await
|
||||
}
|
||||
Extension::V0_5_0(ext) => {
|
||||
ext.call_language_server_workspace_configuration(
|
||||
store,
|
||||
|
@ -404,6 +438,15 @@ impl Extension {
|
|||
resource: Resource<Arc<dyn WorktreeDelegate>>,
|
||||
) -> Result<Result<Option<String>, String>> {
|
||||
match self {
|
||||
Extension::V0_6_0(ext) => {
|
||||
ext.call_language_server_additional_initialization_options(
|
||||
store,
|
||||
&language_server_id.0,
|
||||
&target_language_server_id.0,
|
||||
resource,
|
||||
)
|
||||
.await
|
||||
}
|
||||
Extension::V0_5_0(ext) => {
|
||||
ext.call_language_server_additional_initialization_options(
|
||||
store,
|
||||
|
@ -439,6 +482,15 @@ impl Extension {
|
|||
resource: Resource<Arc<dyn WorktreeDelegate>>,
|
||||
) -> Result<Result<Option<String>, String>> {
|
||||
match self {
|
||||
Extension::V0_6_0(ext) => {
|
||||
ext.call_language_server_additional_workspace_configuration(
|
||||
store,
|
||||
&language_server_id.0,
|
||||
&target_language_server_id.0,
|
||||
resource,
|
||||
)
|
||||
.await
|
||||
}
|
||||
Extension::V0_5_0(ext) => {
|
||||
ext.call_language_server_additional_workspace_configuration(
|
||||
store,
|
||||
|
@ -473,10 +525,23 @@ impl Extension {
|
|||
completions: Vec<latest::Completion>,
|
||||
) -> Result<Result<Vec<Option<CodeLabel>>, String>> {
|
||||
match self {
|
||||
Extension::V0_5_0(ext) => {
|
||||
Extension::V0_6_0(ext) => {
|
||||
ext.call_labels_for_completions(store, &language_server_id.0, &completions)
|
||||
.await
|
||||
}
|
||||
Extension::V0_5_0(ext) => Ok(ext
|
||||
.call_labels_for_completions(
|
||||
store,
|
||||
&language_server_id.0,
|
||||
&completions.into_iter().collect::<Vec<_>>(),
|
||||
)
|
||||
.await?
|
||||
.map(|labels| {
|
||||
labels
|
||||
.into_iter()
|
||||
.map(|label| label.map(Into::into))
|
||||
.collect()
|
||||
})),
|
||||
Extension::V0_4_0(ext) => Ok(ext
|
||||
.call_labels_for_completions(
|
||||
store,
|
||||
|
@ -553,10 +618,23 @@ impl Extension {
|
|||
symbols: Vec<latest::Symbol>,
|
||||
) -> Result<Result<Vec<Option<CodeLabel>>, String>> {
|
||||
match self {
|
||||
Extension::V0_5_0(ext) => {
|
||||
Extension::V0_6_0(ext) => {
|
||||
ext.call_labels_for_symbols(store, &language_server_id.0, &symbols)
|
||||
.await
|
||||
}
|
||||
Extension::V0_5_0(ext) => Ok(ext
|
||||
.call_labels_for_symbols(
|
||||
store,
|
||||
&language_server_id.0,
|
||||
&symbols.into_iter().collect::<Vec<_>>(),
|
||||
)
|
||||
.await?
|
||||
.map(|labels| {
|
||||
labels
|
||||
.into_iter()
|
||||
.map(|label| label.map(Into::into))
|
||||
.collect()
|
||||
})),
|
||||
Extension::V0_4_0(ext) => Ok(ext
|
||||
.call_labels_for_symbols(
|
||||
store,
|
||||
|
@ -633,6 +711,10 @@ impl Extension {
|
|||
arguments: &[String],
|
||||
) -> Result<Result<Vec<SlashCommandArgumentCompletion>, String>> {
|
||||
match self {
|
||||
Extension::V0_6_0(ext) => {
|
||||
ext.call_complete_slash_command_argument(store, command, arguments)
|
||||
.await
|
||||
}
|
||||
Extension::V0_5_0(ext) => {
|
||||
ext.call_complete_slash_command_argument(store, command, arguments)
|
||||
.await
|
||||
|
@ -667,6 +749,10 @@ impl Extension {
|
|||
resource: Option<Resource<Arc<dyn WorktreeDelegate>>>,
|
||||
) -> Result<Result<SlashCommandOutput, String>> {
|
||||
match self {
|
||||
Extension::V0_6_0(ext) => {
|
||||
ext.call_run_slash_command(store, command, arguments, resource)
|
||||
.await
|
||||
}
|
||||
Extension::V0_5_0(ext) => {
|
||||
ext.call_run_slash_command(store, command, arguments, resource)
|
||||
.await
|
||||
|
@ -700,6 +786,10 @@ impl Extension {
|
|||
project: Resource<ExtensionProject>,
|
||||
) -> Result<Result<Command, String>> {
|
||||
match self {
|
||||
Extension::V0_6_0(ext) => {
|
||||
ext.call_context_server_command(store, &context_server_id, project)
|
||||
.await
|
||||
}
|
||||
Extension::V0_5_0(ext) => {
|
||||
ext.call_context_server_command(store, &context_server_id, project)
|
||||
.await
|
||||
|
@ -732,6 +822,10 @@ impl Extension {
|
|||
project: Resource<ExtensionProject>,
|
||||
) -> Result<Result<Option<ContextServerConfiguration>, String>> {
|
||||
match self {
|
||||
Extension::V0_6_0(ext) => {
|
||||
ext.call_context_server_configuration(store, &context_server_id, project)
|
||||
.await
|
||||
}
|
||||
Extension::V0_5_0(ext) => {
|
||||
ext.call_context_server_configuration(store, &context_server_id, project)
|
||||
.await
|
||||
|
@ -754,6 +848,7 @@ impl Extension {
|
|||
provider: &str,
|
||||
) -> Result<Result<Vec<String>, String>> {
|
||||
match self {
|
||||
Extension::V0_6_0(ext) => ext.call_suggest_docs_packages(store, provider).await,
|
||||
Extension::V0_5_0(ext) => ext.call_suggest_docs_packages(store, provider).await,
|
||||
Extension::V0_4_0(ext) => ext.call_suggest_docs_packages(store, provider).await,
|
||||
Extension::V0_3_0(ext) => ext.call_suggest_docs_packages(store, provider).await,
|
||||
|
@ -773,6 +868,10 @@ impl Extension {
|
|||
kv_store: Resource<Arc<dyn KeyValueStoreDelegate>>,
|
||||
) -> Result<Result<(), String>> {
|
||||
match self {
|
||||
Extension::V0_6_0(ext) => {
|
||||
ext.call_index_docs(store, provider, package_name, kv_store)
|
||||
.await
|
||||
}
|
||||
Extension::V0_5_0(ext) => {
|
||||
ext.call_index_docs(store, provider, package_name, kv_store)
|
||||
.await
|
||||
|
|
|
@ -1,28 +1,12 @@
|
|||
use crate::wasm_host::wit::since_v0_5_0::slash_command::SlashCommandOutputSection;
|
||||
use crate::wasm_host::wit::{CompletionKind, CompletionLabelDetails, InsertTextFormat, SymbolKind};
|
||||
use crate::wasm_host::{WasmState, wit::ToWasmtimeResult};
|
||||
use ::http_client::{AsyncBody, HttpRequestExt};
|
||||
use ::settings::{Settings, WorktreeId};
|
||||
use anyhow::{Context, Result, anyhow, bail};
|
||||
use async_compression::futures::bufread::GzipDecoder;
|
||||
use async_tar::Archive;
|
||||
use async_trait::async_trait;
|
||||
use extension::{
|
||||
ExtensionLanguageServerProxy, KeyValueStoreDelegate, ProjectDelegate, WorktreeDelegate,
|
||||
};
|
||||
use futures::{AsyncReadExt, lock::Mutex};
|
||||
use futures::{FutureExt as _, io::BufReader};
|
||||
use language::{BinaryStatus, LanguageName, language_settings::AllLanguageSettings};
|
||||
use project::project_settings::ProjectSettings;
|
||||
use crate::wasm_host::WasmState;
|
||||
use anyhow::Result;
|
||||
use extension::{KeyValueStoreDelegate, ProjectDelegate, WorktreeDelegate};
|
||||
use semantic_version::SemanticVersion;
|
||||
use std::{
|
||||
env,
|
||||
path::{Path, PathBuf},
|
||||
sync::{Arc, OnceLock},
|
||||
};
|
||||
use util::maybe;
|
||||
use std::sync::{Arc, OnceLock};
|
||||
use wasmtime::component::{Linker, Resource};
|
||||
|
||||
use super::latest;
|
||||
|
||||
pub const MIN_VERSION: SemanticVersion = SemanticVersion::new(0, 5, 0);
|
||||
pub const MAX_VERSION: SemanticVersion = SemanticVersion::new(0, 5, 0);
|
||||
|
||||
|
@ -31,15 +15,21 @@ wasmtime::component::bindgen!({
|
|||
trappable_imports: true,
|
||||
path: "../extension_api/wit/since_v0.5.0",
|
||||
with: {
|
||||
"worktree": ExtensionWorktree,
|
||||
"project": ExtensionProject,
|
||||
"key-value-store": ExtensionKeyValueStore,
|
||||
"zed:extension/http-client/http-response-stream": ExtensionHttpResponseStream
|
||||
"worktree": ExtensionWorktree,
|
||||
"project": ExtensionProject,
|
||||
"key-value-store": ExtensionKeyValueStore,
|
||||
"zed:extension/common": latest::zed::extension::common,
|
||||
"zed:extension/github": latest::zed::extension::github,
|
||||
"zed:extension/http-client": latest::zed::extension::http_client,
|
||||
"zed:extension/lsp": latest::zed::extension::lsp,
|
||||
"zed:extension/nodejs": latest::zed::extension::nodejs,
|
||||
"zed:extension/platform": latest::zed::extension::platform,
|
||||
"zed:extension/process": latest::zed::extension::process,
|
||||
"zed:extension/slash-command": latest::zed::extension::slash_command,
|
||||
"zed:extension/context-server": latest::zed::extension::context_server,
|
||||
},
|
||||
});
|
||||
|
||||
pub use self::zed::extension::*;
|
||||
|
||||
mod settings {
|
||||
include!(concat!(env!("OUT_DIR"), "/since_v0.5.0/settings.rs"));
|
||||
}
|
||||
|
@ -47,51 +37,32 @@ mod settings {
|
|||
pub type ExtensionWorktree = Arc<dyn WorktreeDelegate>;
|
||||
pub type ExtensionProject = Arc<dyn ProjectDelegate>;
|
||||
pub type ExtensionKeyValueStore = Arc<dyn KeyValueStoreDelegate>;
|
||||
pub type ExtensionHttpResponseStream = Arc<Mutex<::http_client::Response<AsyncBody>>>;
|
||||
|
||||
pub fn linker() -> &'static Linker<WasmState> {
|
||||
static LINKER: OnceLock<Linker<WasmState>> = OnceLock::new();
|
||||
LINKER.get_or_init(|| super::new_linker(Extension::add_to_linker))
|
||||
}
|
||||
|
||||
impl From<Range> for std::ops::Range<usize> {
|
||||
fn from(range: Range) -> Self {
|
||||
let start = range.start as usize;
|
||||
let end = range.end as usize;
|
||||
start..end
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Command> for extension::Command {
|
||||
fn from(value: Command) -> Self {
|
||||
Self {
|
||||
command: value.command,
|
||||
args: value.args,
|
||||
env: value.env,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CodeLabel> for extension::CodeLabel {
|
||||
impl From<CodeLabel> for latest::CodeLabel {
|
||||
fn from(value: CodeLabel) -> Self {
|
||||
Self {
|
||||
code: value.code,
|
||||
spans: value.spans.into_iter().map(Into::into).collect(),
|
||||
filter_range: value.filter_range.into(),
|
||||
filter_range: value.filter_range,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CodeLabelSpan> for extension::CodeLabelSpan {
|
||||
impl From<CodeLabelSpan> for latest::CodeLabelSpan {
|
||||
fn from(value: CodeLabelSpan) -> Self {
|
||||
match value {
|
||||
CodeLabelSpan::CodeRange(range) => Self::CodeRange(range.into()),
|
||||
CodeLabelSpan::CodeRange(range) => Self::CodeRange(range),
|
||||
CodeLabelSpan::Literal(literal) => Self::Literal(literal.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CodeLabelSpanLiteral> for extension::CodeLabelSpanLiteral {
|
||||
impl From<CodeLabelSpanLiteral> for latest::CodeLabelSpanLiteral {
|
||||
fn from(value: CodeLabelSpanLiteral) -> Self {
|
||||
Self {
|
||||
text: value.text,
|
||||
|
@ -100,167 +71,37 @@ impl From<CodeLabelSpanLiteral> for extension::CodeLabelSpanLiteral {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<extension::Completion> for Completion {
|
||||
fn from(value: extension::Completion) -> Self {
|
||||
impl From<SettingsLocation> for latest::SettingsLocation {
|
||||
fn from(value: SettingsLocation) -> Self {
|
||||
Self {
|
||||
label: value.label,
|
||||
label_details: value.label_details.map(Into::into),
|
||||
detail: value.detail,
|
||||
kind: value.kind.map(Into::into),
|
||||
insert_text_format: value.insert_text_format.map(Into::into),
|
||||
worktree_id: value.worktree_id,
|
||||
path: value.path,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<extension::CompletionLabelDetails> for CompletionLabelDetails {
|
||||
fn from(value: extension::CompletionLabelDetails) -> Self {
|
||||
Self {
|
||||
detail: value.detail,
|
||||
description: value.description,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<extension::CompletionKind> for CompletionKind {
|
||||
fn from(value: extension::CompletionKind) -> Self {
|
||||
impl From<LanguageServerInstallationStatus> for latest::LanguageServerInstallationStatus {
|
||||
fn from(value: LanguageServerInstallationStatus) -> Self {
|
||||
match value {
|
||||
extension::CompletionKind::Text => Self::Text,
|
||||
extension::CompletionKind::Method => Self::Method,
|
||||
extension::CompletionKind::Function => Self::Function,
|
||||
extension::CompletionKind::Constructor => Self::Constructor,
|
||||
extension::CompletionKind::Field => Self::Field,
|
||||
extension::CompletionKind::Variable => Self::Variable,
|
||||
extension::CompletionKind::Class => Self::Class,
|
||||
extension::CompletionKind::Interface => Self::Interface,
|
||||
extension::CompletionKind::Module => Self::Module,
|
||||
extension::CompletionKind::Property => Self::Property,
|
||||
extension::CompletionKind::Unit => Self::Unit,
|
||||
extension::CompletionKind::Value => Self::Value,
|
||||
extension::CompletionKind::Enum => Self::Enum,
|
||||
extension::CompletionKind::Keyword => Self::Keyword,
|
||||
extension::CompletionKind::Snippet => Self::Snippet,
|
||||
extension::CompletionKind::Color => Self::Color,
|
||||
extension::CompletionKind::File => Self::File,
|
||||
extension::CompletionKind::Reference => Self::Reference,
|
||||
extension::CompletionKind::Folder => Self::Folder,
|
||||
extension::CompletionKind::EnumMember => Self::EnumMember,
|
||||
extension::CompletionKind::Constant => Self::Constant,
|
||||
extension::CompletionKind::Struct => Self::Struct,
|
||||
extension::CompletionKind::Event => Self::Event,
|
||||
extension::CompletionKind::Operator => Self::Operator,
|
||||
extension::CompletionKind::TypeParameter => Self::TypeParameter,
|
||||
extension::CompletionKind::Other(value) => Self::Other(value),
|
||||
LanguageServerInstallationStatus::None => Self::None,
|
||||
LanguageServerInstallationStatus::Downloading => Self::Downloading,
|
||||
LanguageServerInstallationStatus::CheckingForUpdate => Self::CheckingForUpdate,
|
||||
LanguageServerInstallationStatus::Failed(message) => Self::Failed(message),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<extension::InsertTextFormat> for InsertTextFormat {
|
||||
fn from(value: extension::InsertTextFormat) -> Self {
|
||||
impl From<DownloadedFileType> for latest::DownloadedFileType {
|
||||
fn from(value: DownloadedFileType) -> Self {
|
||||
match value {
|
||||
extension::InsertTextFormat::PlainText => Self::PlainText,
|
||||
extension::InsertTextFormat::Snippet => Self::Snippet,
|
||||
extension::InsertTextFormat::Other(value) => Self::Other(value),
|
||||
DownloadedFileType::Gzip => Self::Gzip,
|
||||
DownloadedFileType::GzipTar => Self::GzipTar,
|
||||
DownloadedFileType::Zip => Self::Zip,
|
||||
DownloadedFileType::Uncompressed => Self::Uncompressed,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<extension::Symbol> for Symbol {
|
||||
fn from(value: extension::Symbol) -> Self {
|
||||
Self {
|
||||
kind: value.kind.into(),
|
||||
name: value.name,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<extension::SymbolKind> for SymbolKind {
|
||||
fn from(value: extension::SymbolKind) -> Self {
|
||||
match value {
|
||||
extension::SymbolKind::File => Self::File,
|
||||
extension::SymbolKind::Module => Self::Module,
|
||||
extension::SymbolKind::Namespace => Self::Namespace,
|
||||
extension::SymbolKind::Package => Self::Package,
|
||||
extension::SymbolKind::Class => Self::Class,
|
||||
extension::SymbolKind::Method => Self::Method,
|
||||
extension::SymbolKind::Property => Self::Property,
|
||||
extension::SymbolKind::Field => Self::Field,
|
||||
extension::SymbolKind::Constructor => Self::Constructor,
|
||||
extension::SymbolKind::Enum => Self::Enum,
|
||||
extension::SymbolKind::Interface => Self::Interface,
|
||||
extension::SymbolKind::Function => Self::Function,
|
||||
extension::SymbolKind::Variable => Self::Variable,
|
||||
extension::SymbolKind::Constant => Self::Constant,
|
||||
extension::SymbolKind::String => Self::String,
|
||||
extension::SymbolKind::Number => Self::Number,
|
||||
extension::SymbolKind::Boolean => Self::Boolean,
|
||||
extension::SymbolKind::Array => Self::Array,
|
||||
extension::SymbolKind::Object => Self::Object,
|
||||
extension::SymbolKind::Key => Self::Key,
|
||||
extension::SymbolKind::Null => Self::Null,
|
||||
extension::SymbolKind::EnumMember => Self::EnumMember,
|
||||
extension::SymbolKind::Struct => Self::Struct,
|
||||
extension::SymbolKind::Event => Self::Event,
|
||||
extension::SymbolKind::Operator => Self::Operator,
|
||||
extension::SymbolKind::TypeParameter => Self::TypeParameter,
|
||||
extension::SymbolKind::Other(value) => Self::Other(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<extension::SlashCommand> for SlashCommand {
|
||||
fn from(value: extension::SlashCommand) -> Self {
|
||||
Self {
|
||||
name: value.name,
|
||||
description: value.description,
|
||||
tooltip_text: value.tooltip_text,
|
||||
requires_argument: value.requires_argument,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SlashCommandOutput> for extension::SlashCommandOutput {
|
||||
fn from(value: SlashCommandOutput) -> Self {
|
||||
Self {
|
||||
text: value.text,
|
||||
sections: value.sections.into_iter().map(Into::into).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SlashCommandOutputSection> for extension::SlashCommandOutputSection {
|
||||
fn from(value: SlashCommandOutputSection) -> Self {
|
||||
Self {
|
||||
range: value.range.start as usize..value.range.end as usize,
|
||||
label: value.label,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SlashCommandArgumentCompletion> for extension::SlashCommandArgumentCompletion {
|
||||
fn from(value: SlashCommandArgumentCompletion) -> Self {
|
||||
Self {
|
||||
label: value.label,
|
||||
new_text: value.new_text,
|
||||
run_command: value.run_command,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<ContextServerConfiguration> for extension::ContextServerConfiguration {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(value: ContextServerConfiguration) -> Result<Self, Self::Error> {
|
||||
let settings_schema: serde_json::Value = serde_json::from_str(&value.settings_schema)
|
||||
.context("Failed to parse settings_schema")?;
|
||||
|
||||
Ok(Self {
|
||||
installation_instructions: value.installation_instructions,
|
||||
default_settings: value.default_settings,
|
||||
settings_schema,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl HostKeyValueStore for WasmState {
|
||||
async fn insert(
|
||||
&mut self,
|
||||
|
@ -268,8 +109,7 @@ impl HostKeyValueStore for WasmState {
|
|||
key: String,
|
||||
value: String,
|
||||
) -> wasmtime::Result<Result<(), String>> {
|
||||
let kv_store = self.table.get(&kv_store)?;
|
||||
kv_store.insert(key, value).await.to_wasmtime_result()
|
||||
latest::HostKeyValueStore::insert(self, kv_store, key, value).await
|
||||
}
|
||||
|
||||
async fn drop(&mut self, _worktree: Resource<ExtensionKeyValueStore>) -> Result<()> {
|
||||
|
@ -283,8 +123,7 @@ impl HostProject for WasmState {
|
|||
&mut self,
|
||||
project: Resource<ExtensionProject>,
|
||||
) -> wasmtime::Result<Vec<u64>> {
|
||||
let project = self.table.get(&project)?;
|
||||
Ok(project.worktree_ids())
|
||||
latest::HostProject::worktree_ids(self, project).await
|
||||
}
|
||||
|
||||
async fn drop(&mut self, _project: Resource<Project>) -> Result<()> {
|
||||
|
@ -295,16 +134,14 @@ impl HostProject for WasmState {
|
|||
|
||||
impl HostWorktree for WasmState {
|
||||
async fn id(&mut self, delegate: Resource<Arc<dyn WorktreeDelegate>>) -> wasmtime::Result<u64> {
|
||||
let delegate = self.table.get(&delegate)?;
|
||||
Ok(delegate.id())
|
||||
latest::HostWorktree::id(self, delegate).await
|
||||
}
|
||||
|
||||
async fn root_path(
|
||||
&mut self,
|
||||
delegate: Resource<Arc<dyn WorktreeDelegate>>,
|
||||
) -> wasmtime::Result<String> {
|
||||
let delegate = self.table.get(&delegate)?;
|
||||
Ok(delegate.root_path())
|
||||
latest::HostWorktree::root_path(self, delegate).await
|
||||
}
|
||||
|
||||
async fn read_text_file(
|
||||
|
@ -312,19 +149,14 @@ impl HostWorktree for WasmState {
|
|||
delegate: Resource<Arc<dyn WorktreeDelegate>>,
|
||||
path: String,
|
||||
) -> wasmtime::Result<Result<String, String>> {
|
||||
let delegate = self.table.get(&delegate)?;
|
||||
Ok(delegate
|
||||
.read_text_file(path.into())
|
||||
.await
|
||||
.map_err(|error| error.to_string()))
|
||||
latest::HostWorktree::read_text_file(self, delegate, path).await
|
||||
}
|
||||
|
||||
async fn shell_env(
|
||||
&mut self,
|
||||
delegate: Resource<Arc<dyn WorktreeDelegate>>,
|
||||
) -> wasmtime::Result<EnvVars> {
|
||||
let delegate = self.table.get(&delegate)?;
|
||||
Ok(delegate.shell_env().await.into_iter().collect())
|
||||
latest::HostWorktree::shell_env(self, delegate).await
|
||||
}
|
||||
|
||||
async fn which(
|
||||
|
@ -332,8 +164,7 @@ impl HostWorktree for WasmState {
|
|||
delegate: Resource<Arc<dyn WorktreeDelegate>>,
|
||||
binary_name: String,
|
||||
) -> wasmtime::Result<Option<String>> {
|
||||
let delegate = self.table.get(&delegate)?;
|
||||
Ok(delegate.which(binary_name).await)
|
||||
latest::HostWorktree::which(self, delegate, binary_name).await
|
||||
}
|
||||
|
||||
async fn drop(&mut self, _worktree: Resource<Worktree>) -> Result<()> {
|
||||
|
@ -342,291 +173,6 @@ impl HostWorktree for WasmState {
|
|||
}
|
||||
}
|
||||
|
||||
impl common::Host for WasmState {}
|
||||
|
||||
impl http_client::Host for WasmState {
|
||||
async fn fetch(
|
||||
&mut self,
|
||||
request: http_client::HttpRequest,
|
||||
) -> wasmtime::Result<Result<http_client::HttpResponse, String>> {
|
||||
maybe!(async {
|
||||
let url = &request.url;
|
||||
let request = convert_request(&request)?;
|
||||
let mut response = self.host.http_client.send(request).await?;
|
||||
|
||||
if response.status().is_client_error() || response.status().is_server_error() {
|
||||
bail!("failed to fetch '{url}': status code {}", response.status())
|
||||
}
|
||||
convert_response(&mut response).await
|
||||
})
|
||||
.await
|
||||
.to_wasmtime_result()
|
||||
}
|
||||
|
||||
async fn fetch_stream(
|
||||
&mut self,
|
||||
request: http_client::HttpRequest,
|
||||
) -> wasmtime::Result<Result<Resource<ExtensionHttpResponseStream>, String>> {
|
||||
let request = convert_request(&request)?;
|
||||
let response = self.host.http_client.send(request);
|
||||
maybe!(async {
|
||||
let response = response.await?;
|
||||
let stream = Arc::new(Mutex::new(response));
|
||||
let resource = self.table.push(stream)?;
|
||||
Ok(resource)
|
||||
})
|
||||
.await
|
||||
.to_wasmtime_result()
|
||||
}
|
||||
}
|
||||
|
||||
impl http_client::HostHttpResponseStream for WasmState {
|
||||
async fn next_chunk(
|
||||
&mut self,
|
||||
resource: Resource<ExtensionHttpResponseStream>,
|
||||
) -> wasmtime::Result<Result<Option<Vec<u8>>, String>> {
|
||||
let stream = self.table.get(&resource)?.clone();
|
||||
maybe!(async move {
|
||||
let mut response = stream.lock().await;
|
||||
let mut buffer = vec![0; 8192]; // 8KB buffer
|
||||
let bytes_read = response.body_mut().read(&mut buffer).await?;
|
||||
if bytes_read == 0 {
|
||||
Ok(None)
|
||||
} else {
|
||||
buffer.truncate(bytes_read);
|
||||
Ok(Some(buffer))
|
||||
}
|
||||
})
|
||||
.await
|
||||
.to_wasmtime_result()
|
||||
}
|
||||
|
||||
async fn drop(&mut self, _resource: Resource<ExtensionHttpResponseStream>) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<http_client::HttpMethod> for ::http_client::Method {
|
||||
fn from(value: http_client::HttpMethod) -> Self {
|
||||
match value {
|
||||
http_client::HttpMethod::Get => Self::GET,
|
||||
http_client::HttpMethod::Post => Self::POST,
|
||||
http_client::HttpMethod::Put => Self::PUT,
|
||||
http_client::HttpMethod::Delete => Self::DELETE,
|
||||
http_client::HttpMethod::Head => Self::HEAD,
|
||||
http_client::HttpMethod::Options => Self::OPTIONS,
|
||||
http_client::HttpMethod::Patch => Self::PATCH,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_request(
|
||||
extension_request: &http_client::HttpRequest,
|
||||
) -> Result<::http_client::Request<AsyncBody>, anyhow::Error> {
|
||||
let mut request = ::http_client::Request::builder()
|
||||
.method(::http_client::Method::from(extension_request.method))
|
||||
.uri(&extension_request.url)
|
||||
.follow_redirects(match extension_request.redirect_policy {
|
||||
http_client::RedirectPolicy::NoFollow => ::http_client::RedirectPolicy::NoFollow,
|
||||
http_client::RedirectPolicy::FollowLimit(limit) => {
|
||||
::http_client::RedirectPolicy::FollowLimit(limit)
|
||||
}
|
||||
http_client::RedirectPolicy::FollowAll => ::http_client::RedirectPolicy::FollowAll,
|
||||
});
|
||||
for (key, value) in &extension_request.headers {
|
||||
request = request.header(key, value);
|
||||
}
|
||||
let body = extension_request
|
||||
.body
|
||||
.clone()
|
||||
.map(AsyncBody::from)
|
||||
.unwrap_or_default();
|
||||
request.body(body).map_err(anyhow::Error::from)
|
||||
}
|
||||
|
||||
async fn convert_response(
|
||||
response: &mut ::http_client::Response<AsyncBody>,
|
||||
) -> Result<http_client::HttpResponse, anyhow::Error> {
|
||||
let mut extension_response = http_client::HttpResponse {
|
||||
body: Vec::new(),
|
||||
headers: Vec::new(),
|
||||
};
|
||||
|
||||
for (key, value) in response.headers() {
|
||||
extension_response
|
||||
.headers
|
||||
.push((key.to_string(), value.to_str().unwrap_or("").to_string()));
|
||||
}
|
||||
|
||||
response
|
||||
.body_mut()
|
||||
.read_to_end(&mut extension_response.body)
|
||||
.await?;
|
||||
|
||||
Ok(extension_response)
|
||||
}
|
||||
|
||||
impl nodejs::Host for WasmState {
|
||||
async fn node_binary_path(&mut self) -> wasmtime::Result<Result<String, String>> {
|
||||
self.host
|
||||
.node_runtime
|
||||
.binary_path()
|
||||
.await
|
||||
.map(|path| path.to_string_lossy().to_string())
|
||||
.to_wasmtime_result()
|
||||
}
|
||||
|
||||
async fn npm_package_latest_version(
|
||||
&mut self,
|
||||
package_name: String,
|
||||
) -> wasmtime::Result<Result<String, String>> {
|
||||
self.host
|
||||
.node_runtime
|
||||
.npm_package_latest_version(&package_name)
|
||||
.await
|
||||
.to_wasmtime_result()
|
||||
}
|
||||
|
||||
async fn npm_package_installed_version(
|
||||
&mut self,
|
||||
package_name: String,
|
||||
) -> wasmtime::Result<Result<Option<String>, String>> {
|
||||
self.host
|
||||
.node_runtime
|
||||
.npm_package_installed_version(&self.work_dir(), &package_name)
|
||||
.await
|
||||
.to_wasmtime_result()
|
||||
}
|
||||
|
||||
async fn npm_install_package(
|
||||
&mut self,
|
||||
package_name: String,
|
||||
version: String,
|
||||
) -> wasmtime::Result<Result<(), String>> {
|
||||
self.host
|
||||
.node_runtime
|
||||
.npm_install_packages(&self.work_dir(), &[(&package_name, &version)])
|
||||
.await
|
||||
.to_wasmtime_result()
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl lsp::Host for WasmState {}
|
||||
|
||||
impl From<::http_client::github::GithubRelease> for github::GithubRelease {
|
||||
fn from(value: ::http_client::github::GithubRelease) -> Self {
|
||||
Self {
|
||||
version: value.tag_name,
|
||||
assets: value.assets.into_iter().map(Into::into).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<::http_client::github::GithubReleaseAsset> for github::GithubReleaseAsset {
|
||||
fn from(value: ::http_client::github::GithubReleaseAsset) -> Self {
|
||||
Self {
|
||||
name: value.name,
|
||||
download_url: value.browser_download_url,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl github::Host for WasmState {
|
||||
async fn latest_github_release(
|
||||
&mut self,
|
||||
repo: String,
|
||||
options: github::GithubReleaseOptions,
|
||||
) -> wasmtime::Result<Result<github::GithubRelease, String>> {
|
||||
maybe!(async {
|
||||
let release = ::http_client::github::latest_github_release(
|
||||
&repo,
|
||||
options.require_assets,
|
||||
options.pre_release,
|
||||
self.host.http_client.clone(),
|
||||
)
|
||||
.await?;
|
||||
Ok(release.into())
|
||||
})
|
||||
.await
|
||||
.to_wasmtime_result()
|
||||
}
|
||||
|
||||
async fn github_release_by_tag_name(
|
||||
&mut self,
|
||||
repo: String,
|
||||
tag: String,
|
||||
) -> wasmtime::Result<Result<github::GithubRelease, String>> {
|
||||
maybe!(async {
|
||||
let release = ::http_client::github::get_release_by_tag_name(
|
||||
&repo,
|
||||
&tag,
|
||||
self.host.http_client.clone(),
|
||||
)
|
||||
.await?;
|
||||
Ok(release.into())
|
||||
})
|
||||
.await
|
||||
.to_wasmtime_result()
|
||||
}
|
||||
}
|
||||
|
||||
impl platform::Host for WasmState {
|
||||
async fn current_platform(&mut self) -> Result<(platform::Os, platform::Architecture)> {
|
||||
Ok((
|
||||
match env::consts::OS {
|
||||
"macos" => platform::Os::Mac,
|
||||
"linux" => platform::Os::Linux,
|
||||
"windows" => platform::Os::Windows,
|
||||
_ => panic!("unsupported os"),
|
||||
},
|
||||
match env::consts::ARCH {
|
||||
"aarch64" => platform::Architecture::Aarch64,
|
||||
"x86" => platform::Architecture::X86,
|
||||
"x86_64" => platform::Architecture::X8664,
|
||||
_ => panic!("unsupported architecture"),
|
||||
},
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::process::Output> for process::Output {
|
||||
fn from(output: std::process::Output) -> Self {
|
||||
Self {
|
||||
status: output.status.code(),
|
||||
stdout: output.stdout,
|
||||
stderr: output.stderr,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl process::Host for WasmState {
|
||||
async fn run_command(
|
||||
&mut self,
|
||||
command: process::Command,
|
||||
) -> wasmtime::Result<Result<process::Output, String>> {
|
||||
maybe!(async {
|
||||
self.manifest.allow_exec(&command.command, &command.args)?;
|
||||
|
||||
let output = util::command::new_smol_command(command.command.as_str())
|
||||
.args(&command.args)
|
||||
.envs(command.env)
|
||||
.output()
|
||||
.await?;
|
||||
|
||||
Ok(output.into())
|
||||
})
|
||||
.await
|
||||
.to_wasmtime_result()
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl slash_command::Host for WasmState {}
|
||||
|
||||
#[async_trait]
|
||||
impl context_server::Host for WasmState {}
|
||||
|
||||
impl ExtensionImports for WasmState {
|
||||
async fn get_settings(
|
||||
&mut self,
|
||||
|
@ -634,75 +180,13 @@ impl ExtensionImports for WasmState {
|
|||
category: String,
|
||||
key: Option<String>,
|
||||
) -> wasmtime::Result<Result<String, String>> {
|
||||
self.on_main_thread(|cx| {
|
||||
async move {
|
||||
let location = location
|
||||
.as_ref()
|
||||
.map(|location| ::settings::SettingsLocation {
|
||||
worktree_id: WorktreeId::from_proto(location.worktree_id),
|
||||
path: Path::new(&location.path),
|
||||
});
|
||||
|
||||
cx.update(|cx| match category.as_str() {
|
||||
"language" => {
|
||||
let key = key.map(|k| LanguageName::new(&k));
|
||||
let settings = AllLanguageSettings::get(location, cx).language(
|
||||
location,
|
||||
key.as_ref(),
|
||||
cx,
|
||||
);
|
||||
Ok(serde_json::to_string(&settings::LanguageSettings {
|
||||
tab_size: settings.tab_size,
|
||||
})?)
|
||||
}
|
||||
"lsp" => {
|
||||
let settings = key
|
||||
.and_then(|key| {
|
||||
ProjectSettings::get(location, cx)
|
||||
.lsp
|
||||
.get(&::lsp::LanguageServerName::from_proto(key))
|
||||
})
|
||||
.cloned()
|
||||
.unwrap_or_default();
|
||||
Ok(serde_json::to_string(&settings::LspSettings {
|
||||
binary: settings.binary.map(|binary| settings::CommandSettings {
|
||||
path: binary.path,
|
||||
arguments: binary.arguments,
|
||||
env: binary.env,
|
||||
}),
|
||||
settings: settings.settings,
|
||||
initialization_options: settings.initialization_options,
|
||||
})?)
|
||||
}
|
||||
"context_servers" => {
|
||||
let configuration = key
|
||||
.and_then(|key| {
|
||||
ProjectSettings::get(location, cx)
|
||||
.context_servers
|
||||
.get(key.as_str())
|
||||
})
|
||||
.cloned()
|
||||
.unwrap_or_default();
|
||||
Ok(serde_json::to_string(&settings::ContextServerSettings {
|
||||
command: configuration.command.map(|command| {
|
||||
settings::CommandSettings {
|
||||
path: Some(command.path),
|
||||
arguments: Some(command.args),
|
||||
env: command.env.map(|env| env.into_iter().collect()),
|
||||
}
|
||||
}),
|
||||
settings: configuration.settings,
|
||||
})?)
|
||||
}
|
||||
_ => {
|
||||
bail!("Unknown settings category: {}", category);
|
||||
}
|
||||
})
|
||||
}
|
||||
.boxed_local()
|
||||
})
|
||||
.await?
|
||||
.to_wasmtime_result()
|
||||
latest::ExtensionImports::get_settings(
|
||||
self,
|
||||
location.map(|location| location.into()),
|
||||
category,
|
||||
key,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn set_language_server_installation_status(
|
||||
|
@ -710,18 +194,12 @@ impl ExtensionImports for WasmState {
|
|||
server_name: String,
|
||||
status: LanguageServerInstallationStatus,
|
||||
) -> wasmtime::Result<()> {
|
||||
let status = match status {
|
||||
LanguageServerInstallationStatus::CheckingForUpdate => BinaryStatus::CheckingForUpdate,
|
||||
LanguageServerInstallationStatus::Downloading => BinaryStatus::Downloading,
|
||||
LanguageServerInstallationStatus::None => BinaryStatus::None,
|
||||
LanguageServerInstallationStatus::Failed(error) => BinaryStatus::Failed { error },
|
||||
};
|
||||
|
||||
self.host
|
||||
.proxy
|
||||
.update_language_server_status(::lsp::LanguageServerName(server_name.into()), status);
|
||||
|
||||
Ok(())
|
||||
latest::ExtensionImports::set_language_server_installation_status(
|
||||
self,
|
||||
server_name,
|
||||
status.into(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn download_file(
|
||||
|
@ -730,86 +208,10 @@ impl ExtensionImports for WasmState {
|
|||
path: String,
|
||||
file_type: DownloadedFileType,
|
||||
) -> wasmtime::Result<Result<(), String>> {
|
||||
maybe!(async {
|
||||
let path = PathBuf::from(path);
|
||||
let extension_work_dir = self.host.work_dir.join(self.manifest.id.as_ref());
|
||||
|
||||
self.host.fs.create_dir(&extension_work_dir).await?;
|
||||
|
||||
let destination_path = self
|
||||
.host
|
||||
.writeable_path_from_extension(&self.manifest.id, &path)?;
|
||||
|
||||
let mut response = self
|
||||
.host
|
||||
.http_client
|
||||
.get(&url, Default::default(), true)
|
||||
.await
|
||||
.map_err(|err| anyhow!("error downloading release: {}", err))?;
|
||||
|
||||
if !response.status().is_success() {
|
||||
Err(anyhow!(
|
||||
"download failed with status {}",
|
||||
response.status().to_string()
|
||||
))?;
|
||||
}
|
||||
let body = BufReader::new(response.body_mut());
|
||||
|
||||
match file_type {
|
||||
DownloadedFileType::Uncompressed => {
|
||||
futures::pin_mut!(body);
|
||||
self.host
|
||||
.fs
|
||||
.create_file_with(&destination_path, body)
|
||||
.await?;
|
||||
}
|
||||
DownloadedFileType::Gzip => {
|
||||
let body = GzipDecoder::new(body);
|
||||
futures::pin_mut!(body);
|
||||
self.host
|
||||
.fs
|
||||
.create_file_with(&destination_path, body)
|
||||
.await?;
|
||||
}
|
||||
DownloadedFileType::GzipTar => {
|
||||
let body = GzipDecoder::new(body);
|
||||
futures::pin_mut!(body);
|
||||
self.host
|
||||
.fs
|
||||
.extract_tar_file(&destination_path, Archive::new(body))
|
||||
.await?;
|
||||
}
|
||||
DownloadedFileType::Zip => {
|
||||
futures::pin_mut!(body);
|
||||
node_runtime::extract_zip(&destination_path, body)
|
||||
.await
|
||||
.with_context(|| format!("failed to unzip {} archive", path.display()))?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.await
|
||||
.to_wasmtime_result()
|
||||
latest::ExtensionImports::download_file(self, url, path, file_type.into()).await
|
||||
}
|
||||
|
||||
async fn make_file_executable(&mut self, path: String) -> wasmtime::Result<Result<(), String>> {
|
||||
#[allow(unused)]
|
||||
let path = self
|
||||
.host
|
||||
.writeable_path_from_extension(&self.manifest.id, Path::new(&path))?;
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::fs::{self, Permissions};
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
|
||||
return fs::set_permissions(&path, Permissions::from_mode(0o755))
|
||||
.map_err(|error| anyhow!("failed to set permissions for path {path:?}: {error}"))
|
||||
.to_wasmtime_result();
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
Ok(Ok(()))
|
||||
latest::ExtensionImports::make_file_executable(self, path).await
|
||||
}
|
||||
}
|
||||
|
|
815
crates/extension_host/src/wasm_host/wit/since_v0_6_0.rs
Normal file
815
crates/extension_host/src/wasm_host/wit/since_v0_6_0.rs
Normal file
|
@ -0,0 +1,815 @@
|
|||
use crate::wasm_host::wit::since_v0_6_0::slash_command::SlashCommandOutputSection;
|
||||
use crate::wasm_host::wit::{CompletionKind, CompletionLabelDetails, InsertTextFormat, SymbolKind};
|
||||
use crate::wasm_host::{WasmState, wit::ToWasmtimeResult};
|
||||
use ::http_client::{AsyncBody, HttpRequestExt};
|
||||
use ::settings::{Settings, WorktreeId};
|
||||
use anyhow::{Context, Result, anyhow, bail};
|
||||
use async_compression::futures::bufread::GzipDecoder;
|
||||
use async_tar::Archive;
|
||||
use async_trait::async_trait;
|
||||
use extension::{
|
||||
ExtensionLanguageServerProxy, KeyValueStoreDelegate, ProjectDelegate, WorktreeDelegate,
|
||||
};
|
||||
use futures::{AsyncReadExt, lock::Mutex};
|
||||
use futures::{FutureExt as _, io::BufReader};
|
||||
use language::{BinaryStatus, LanguageName, language_settings::AllLanguageSettings};
|
||||
use project::project_settings::ProjectSettings;
|
||||
use semantic_version::SemanticVersion;
|
||||
use std::{
|
||||
env,
|
||||
path::{Path, PathBuf},
|
||||
sync::{Arc, OnceLock},
|
||||
};
|
||||
use util::maybe;
|
||||
use wasmtime::component::{Linker, Resource};
|
||||
|
||||
pub const MIN_VERSION: SemanticVersion = SemanticVersion::new(0, 6, 0);
|
||||
pub const MAX_VERSION: SemanticVersion = SemanticVersion::new(0, 6, 0);
|
||||
|
||||
wasmtime::component::bindgen!({
|
||||
async: true,
|
||||
trappable_imports: true,
|
||||
path: "../extension_api/wit/since_v0.6.0",
|
||||
with: {
|
||||
"worktree": ExtensionWorktree,
|
||||
"project": ExtensionProject,
|
||||
"key-value-store": ExtensionKeyValueStore,
|
||||
"zed:extension/http-client/http-response-stream": ExtensionHttpResponseStream
|
||||
},
|
||||
});
|
||||
|
||||
pub use self::zed::extension::*;
|
||||
|
||||
mod settings {
|
||||
include!(concat!(env!("OUT_DIR"), "/since_v0.5.0/settings.rs"));
|
||||
}
|
||||
|
||||
pub type ExtensionWorktree = Arc<dyn WorktreeDelegate>;
|
||||
pub type ExtensionProject = Arc<dyn ProjectDelegate>;
|
||||
pub type ExtensionKeyValueStore = Arc<dyn KeyValueStoreDelegate>;
|
||||
pub type ExtensionHttpResponseStream = Arc<Mutex<::http_client::Response<AsyncBody>>>;
|
||||
|
||||
pub fn linker() -> &'static Linker<WasmState> {
|
||||
static LINKER: OnceLock<Linker<WasmState>> = OnceLock::new();
|
||||
LINKER.get_or_init(|| super::new_linker(Extension::add_to_linker))
|
||||
}
|
||||
|
||||
impl From<Range> for std::ops::Range<usize> {
|
||||
fn from(range: Range) -> Self {
|
||||
let start = range.start as usize;
|
||||
let end = range.end as usize;
|
||||
start..end
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Command> for extension::Command {
|
||||
fn from(value: Command) -> Self {
|
||||
Self {
|
||||
command: value.command,
|
||||
args: value.args,
|
||||
env: value.env,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CodeLabel> for extension::CodeLabel {
|
||||
fn from(value: CodeLabel) -> Self {
|
||||
Self {
|
||||
code: value.code,
|
||||
spans: value.spans.into_iter().map(Into::into).collect(),
|
||||
filter_range: value.filter_range.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CodeLabelSpan> for extension::CodeLabelSpan {
|
||||
fn from(value: CodeLabelSpan) -> Self {
|
||||
match value {
|
||||
CodeLabelSpan::CodeRange(range) => Self::CodeRange(range.into()),
|
||||
CodeLabelSpan::Literal(literal) => Self::Literal(literal.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CodeLabelSpanLiteral> for extension::CodeLabelSpanLiteral {
|
||||
fn from(value: CodeLabelSpanLiteral) -> Self {
|
||||
Self {
|
||||
text: value.text,
|
||||
highlight_name: value.highlight_name,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<extension::Completion> for Completion {
|
||||
fn from(value: extension::Completion) -> Self {
|
||||
Self {
|
||||
label: value.label,
|
||||
label_details: value.label_details.map(Into::into),
|
||||
detail: value.detail,
|
||||
kind: value.kind.map(Into::into),
|
||||
insert_text_format: value.insert_text_format.map(Into::into),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<extension::CompletionLabelDetails> for CompletionLabelDetails {
|
||||
fn from(value: extension::CompletionLabelDetails) -> Self {
|
||||
Self {
|
||||
detail: value.detail,
|
||||
description: value.description,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<extension::CompletionKind> for CompletionKind {
|
||||
fn from(value: extension::CompletionKind) -> Self {
|
||||
match value {
|
||||
extension::CompletionKind::Text => Self::Text,
|
||||
extension::CompletionKind::Method => Self::Method,
|
||||
extension::CompletionKind::Function => Self::Function,
|
||||
extension::CompletionKind::Constructor => Self::Constructor,
|
||||
extension::CompletionKind::Field => Self::Field,
|
||||
extension::CompletionKind::Variable => Self::Variable,
|
||||
extension::CompletionKind::Class => Self::Class,
|
||||
extension::CompletionKind::Interface => Self::Interface,
|
||||
extension::CompletionKind::Module => Self::Module,
|
||||
extension::CompletionKind::Property => Self::Property,
|
||||
extension::CompletionKind::Unit => Self::Unit,
|
||||
extension::CompletionKind::Value => Self::Value,
|
||||
extension::CompletionKind::Enum => Self::Enum,
|
||||
extension::CompletionKind::Keyword => Self::Keyword,
|
||||
extension::CompletionKind::Snippet => Self::Snippet,
|
||||
extension::CompletionKind::Color => Self::Color,
|
||||
extension::CompletionKind::File => Self::File,
|
||||
extension::CompletionKind::Reference => Self::Reference,
|
||||
extension::CompletionKind::Folder => Self::Folder,
|
||||
extension::CompletionKind::EnumMember => Self::EnumMember,
|
||||
extension::CompletionKind::Constant => Self::Constant,
|
||||
extension::CompletionKind::Struct => Self::Struct,
|
||||
extension::CompletionKind::Event => Self::Event,
|
||||
extension::CompletionKind::Operator => Self::Operator,
|
||||
extension::CompletionKind::TypeParameter => Self::TypeParameter,
|
||||
extension::CompletionKind::Other(value) => Self::Other(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<extension::InsertTextFormat> for InsertTextFormat {
|
||||
fn from(value: extension::InsertTextFormat) -> Self {
|
||||
match value {
|
||||
extension::InsertTextFormat::PlainText => Self::PlainText,
|
||||
extension::InsertTextFormat::Snippet => Self::Snippet,
|
||||
extension::InsertTextFormat::Other(value) => Self::Other(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<extension::Symbol> for Symbol {
|
||||
fn from(value: extension::Symbol) -> Self {
|
||||
Self {
|
||||
kind: value.kind.into(),
|
||||
name: value.name,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<extension::SymbolKind> for SymbolKind {
|
||||
fn from(value: extension::SymbolKind) -> Self {
|
||||
match value {
|
||||
extension::SymbolKind::File => Self::File,
|
||||
extension::SymbolKind::Module => Self::Module,
|
||||
extension::SymbolKind::Namespace => Self::Namespace,
|
||||
extension::SymbolKind::Package => Self::Package,
|
||||
extension::SymbolKind::Class => Self::Class,
|
||||
extension::SymbolKind::Method => Self::Method,
|
||||
extension::SymbolKind::Property => Self::Property,
|
||||
extension::SymbolKind::Field => Self::Field,
|
||||
extension::SymbolKind::Constructor => Self::Constructor,
|
||||
extension::SymbolKind::Enum => Self::Enum,
|
||||
extension::SymbolKind::Interface => Self::Interface,
|
||||
extension::SymbolKind::Function => Self::Function,
|
||||
extension::SymbolKind::Variable => Self::Variable,
|
||||
extension::SymbolKind::Constant => Self::Constant,
|
||||
extension::SymbolKind::String => Self::String,
|
||||
extension::SymbolKind::Number => Self::Number,
|
||||
extension::SymbolKind::Boolean => Self::Boolean,
|
||||
extension::SymbolKind::Array => Self::Array,
|
||||
extension::SymbolKind::Object => Self::Object,
|
||||
extension::SymbolKind::Key => Self::Key,
|
||||
extension::SymbolKind::Null => Self::Null,
|
||||
extension::SymbolKind::EnumMember => Self::EnumMember,
|
||||
extension::SymbolKind::Struct => Self::Struct,
|
||||
extension::SymbolKind::Event => Self::Event,
|
||||
extension::SymbolKind::Operator => Self::Operator,
|
||||
extension::SymbolKind::TypeParameter => Self::TypeParameter,
|
||||
extension::SymbolKind::Other(value) => Self::Other(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<extension::SlashCommand> for SlashCommand {
|
||||
fn from(value: extension::SlashCommand) -> Self {
|
||||
Self {
|
||||
name: value.name,
|
||||
description: value.description,
|
||||
tooltip_text: value.tooltip_text,
|
||||
requires_argument: value.requires_argument,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SlashCommandOutput> for extension::SlashCommandOutput {
|
||||
fn from(value: SlashCommandOutput) -> Self {
|
||||
Self {
|
||||
text: value.text,
|
||||
sections: value.sections.into_iter().map(Into::into).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SlashCommandOutputSection> for extension::SlashCommandOutputSection {
|
||||
fn from(value: SlashCommandOutputSection) -> Self {
|
||||
Self {
|
||||
range: value.range.start as usize..value.range.end as usize,
|
||||
label: value.label,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SlashCommandArgumentCompletion> for extension::SlashCommandArgumentCompletion {
|
||||
fn from(value: SlashCommandArgumentCompletion) -> Self {
|
||||
Self {
|
||||
label: value.label,
|
||||
new_text: value.new_text,
|
||||
run_command: value.run_command,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<ContextServerConfiguration> for extension::ContextServerConfiguration {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(value: ContextServerConfiguration) -> Result<Self, Self::Error> {
|
||||
let settings_schema: serde_json::Value = serde_json::from_str(&value.settings_schema)
|
||||
.context("Failed to parse settings_schema")?;
|
||||
|
||||
Ok(Self {
|
||||
installation_instructions: value.installation_instructions,
|
||||
default_settings: value.default_settings,
|
||||
settings_schema,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl HostKeyValueStore for WasmState {
|
||||
async fn insert(
|
||||
&mut self,
|
||||
kv_store: Resource<ExtensionKeyValueStore>,
|
||||
key: String,
|
||||
value: String,
|
||||
) -> wasmtime::Result<Result<(), String>> {
|
||||
let kv_store = self.table.get(&kv_store)?;
|
||||
kv_store.insert(key, value).await.to_wasmtime_result()
|
||||
}
|
||||
|
||||
async fn drop(&mut self, _worktree: Resource<ExtensionKeyValueStore>) -> Result<()> {
|
||||
// We only ever hand out borrows of key-value stores.
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl HostProject for WasmState {
|
||||
async fn worktree_ids(
|
||||
&mut self,
|
||||
project: Resource<ExtensionProject>,
|
||||
) -> wasmtime::Result<Vec<u64>> {
|
||||
let project = self.table.get(&project)?;
|
||||
Ok(project.worktree_ids())
|
||||
}
|
||||
|
||||
async fn drop(&mut self, _project: Resource<Project>) -> Result<()> {
|
||||
// We only ever hand out borrows of projects.
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl HostWorktree for WasmState {
|
||||
async fn id(&mut self, delegate: Resource<Arc<dyn WorktreeDelegate>>) -> wasmtime::Result<u64> {
|
||||
let delegate = self.table.get(&delegate)?;
|
||||
Ok(delegate.id())
|
||||
}
|
||||
|
||||
async fn root_path(
|
||||
&mut self,
|
||||
delegate: Resource<Arc<dyn WorktreeDelegate>>,
|
||||
) -> wasmtime::Result<String> {
|
||||
let delegate = self.table.get(&delegate)?;
|
||||
Ok(delegate.root_path())
|
||||
}
|
||||
|
||||
async fn read_text_file(
|
||||
&mut self,
|
||||
delegate: Resource<Arc<dyn WorktreeDelegate>>,
|
||||
path: String,
|
||||
) -> wasmtime::Result<Result<String, String>> {
|
||||
let delegate = self.table.get(&delegate)?;
|
||||
Ok(delegate
|
||||
.read_text_file(path.into())
|
||||
.await
|
||||
.map_err(|error| error.to_string()))
|
||||
}
|
||||
|
||||
async fn shell_env(
|
||||
&mut self,
|
||||
delegate: Resource<Arc<dyn WorktreeDelegate>>,
|
||||
) -> wasmtime::Result<EnvVars> {
|
||||
let delegate = self.table.get(&delegate)?;
|
||||
Ok(delegate.shell_env().await.into_iter().collect())
|
||||
}
|
||||
|
||||
async fn which(
|
||||
&mut self,
|
||||
delegate: Resource<Arc<dyn WorktreeDelegate>>,
|
||||
binary_name: String,
|
||||
) -> wasmtime::Result<Option<String>> {
|
||||
let delegate = self.table.get(&delegate)?;
|
||||
Ok(delegate.which(binary_name).await)
|
||||
}
|
||||
|
||||
async fn drop(&mut self, _worktree: Resource<Worktree>) -> Result<()> {
|
||||
// We only ever hand out borrows of worktrees.
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl common::Host for WasmState {}
|
||||
|
||||
impl http_client::Host for WasmState {
|
||||
async fn fetch(
|
||||
&mut self,
|
||||
request: http_client::HttpRequest,
|
||||
) -> wasmtime::Result<Result<http_client::HttpResponse, String>> {
|
||||
maybe!(async {
|
||||
let url = &request.url;
|
||||
let request = convert_request(&request)?;
|
||||
let mut response = self.host.http_client.send(request).await?;
|
||||
|
||||
if response.status().is_client_error() || response.status().is_server_error() {
|
||||
bail!("failed to fetch '{url}': status code {}", response.status())
|
||||
}
|
||||
convert_response(&mut response).await
|
||||
})
|
||||
.await
|
||||
.to_wasmtime_result()
|
||||
}
|
||||
|
||||
async fn fetch_stream(
|
||||
&mut self,
|
||||
request: http_client::HttpRequest,
|
||||
) -> wasmtime::Result<Result<Resource<ExtensionHttpResponseStream>, String>> {
|
||||
let request = convert_request(&request)?;
|
||||
let response = self.host.http_client.send(request);
|
||||
maybe!(async {
|
||||
let response = response.await?;
|
||||
let stream = Arc::new(Mutex::new(response));
|
||||
let resource = self.table.push(stream)?;
|
||||
Ok(resource)
|
||||
})
|
||||
.await
|
||||
.to_wasmtime_result()
|
||||
}
|
||||
}
|
||||
|
||||
impl http_client::HostHttpResponseStream for WasmState {
|
||||
async fn next_chunk(
|
||||
&mut self,
|
||||
resource: Resource<ExtensionHttpResponseStream>,
|
||||
) -> wasmtime::Result<Result<Option<Vec<u8>>, String>> {
|
||||
let stream = self.table.get(&resource)?.clone();
|
||||
maybe!(async move {
|
||||
let mut response = stream.lock().await;
|
||||
let mut buffer = vec![0; 8192]; // 8KB buffer
|
||||
let bytes_read = response.body_mut().read(&mut buffer).await?;
|
||||
if bytes_read == 0 {
|
||||
Ok(None)
|
||||
} else {
|
||||
buffer.truncate(bytes_read);
|
||||
Ok(Some(buffer))
|
||||
}
|
||||
})
|
||||
.await
|
||||
.to_wasmtime_result()
|
||||
}
|
||||
|
||||
async fn drop(&mut self, _resource: Resource<ExtensionHttpResponseStream>) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<http_client::HttpMethod> for ::http_client::Method {
|
||||
fn from(value: http_client::HttpMethod) -> Self {
|
||||
match value {
|
||||
http_client::HttpMethod::Get => Self::GET,
|
||||
http_client::HttpMethod::Post => Self::POST,
|
||||
http_client::HttpMethod::Put => Self::PUT,
|
||||
http_client::HttpMethod::Delete => Self::DELETE,
|
||||
http_client::HttpMethod::Head => Self::HEAD,
|
||||
http_client::HttpMethod::Options => Self::OPTIONS,
|
||||
http_client::HttpMethod::Patch => Self::PATCH,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_request(
|
||||
extension_request: &http_client::HttpRequest,
|
||||
) -> Result<::http_client::Request<AsyncBody>, anyhow::Error> {
|
||||
let mut request = ::http_client::Request::builder()
|
||||
.method(::http_client::Method::from(extension_request.method))
|
||||
.uri(&extension_request.url)
|
||||
.follow_redirects(match extension_request.redirect_policy {
|
||||
http_client::RedirectPolicy::NoFollow => ::http_client::RedirectPolicy::NoFollow,
|
||||
http_client::RedirectPolicy::FollowLimit(limit) => {
|
||||
::http_client::RedirectPolicy::FollowLimit(limit)
|
||||
}
|
||||
http_client::RedirectPolicy::FollowAll => ::http_client::RedirectPolicy::FollowAll,
|
||||
});
|
||||
for (key, value) in &extension_request.headers {
|
||||
request = request.header(key, value);
|
||||
}
|
||||
let body = extension_request
|
||||
.body
|
||||
.clone()
|
||||
.map(AsyncBody::from)
|
||||
.unwrap_or_default();
|
||||
request.body(body).map_err(anyhow::Error::from)
|
||||
}
|
||||
|
||||
async fn convert_response(
|
||||
response: &mut ::http_client::Response<AsyncBody>,
|
||||
) -> Result<http_client::HttpResponse, anyhow::Error> {
|
||||
let mut extension_response = http_client::HttpResponse {
|
||||
body: Vec::new(),
|
||||
headers: Vec::new(),
|
||||
};
|
||||
|
||||
for (key, value) in response.headers() {
|
||||
extension_response
|
||||
.headers
|
||||
.push((key.to_string(), value.to_str().unwrap_or("").to_string()));
|
||||
}
|
||||
|
||||
response
|
||||
.body_mut()
|
||||
.read_to_end(&mut extension_response.body)
|
||||
.await?;
|
||||
|
||||
Ok(extension_response)
|
||||
}
|
||||
|
||||
impl nodejs::Host for WasmState {
|
||||
async fn node_binary_path(&mut self) -> wasmtime::Result<Result<String, String>> {
|
||||
self.host
|
||||
.node_runtime
|
||||
.binary_path()
|
||||
.await
|
||||
.map(|path| path.to_string_lossy().to_string())
|
||||
.to_wasmtime_result()
|
||||
}
|
||||
|
||||
async fn npm_package_latest_version(
|
||||
&mut self,
|
||||
package_name: String,
|
||||
) -> wasmtime::Result<Result<String, String>> {
|
||||
self.host
|
||||
.node_runtime
|
||||
.npm_package_latest_version(&package_name)
|
||||
.await
|
||||
.to_wasmtime_result()
|
||||
}
|
||||
|
||||
async fn npm_package_installed_version(
|
||||
&mut self,
|
||||
package_name: String,
|
||||
) -> wasmtime::Result<Result<Option<String>, String>> {
|
||||
self.host
|
||||
.node_runtime
|
||||
.npm_package_installed_version(&self.work_dir(), &package_name)
|
||||
.await
|
||||
.to_wasmtime_result()
|
||||
}
|
||||
|
||||
async fn npm_install_package(
|
||||
&mut self,
|
||||
package_name: String,
|
||||
version: String,
|
||||
) -> wasmtime::Result<Result<(), String>> {
|
||||
self.host
|
||||
.node_runtime
|
||||
.npm_install_packages(&self.work_dir(), &[(&package_name, &version)])
|
||||
.await
|
||||
.to_wasmtime_result()
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl lsp::Host for WasmState {}
|
||||
|
||||
impl From<::http_client::github::GithubRelease> for github::GithubRelease {
|
||||
fn from(value: ::http_client::github::GithubRelease) -> Self {
|
||||
Self {
|
||||
version: value.tag_name,
|
||||
assets: value.assets.into_iter().map(Into::into).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<::http_client::github::GithubReleaseAsset> for github::GithubReleaseAsset {
|
||||
fn from(value: ::http_client::github::GithubReleaseAsset) -> Self {
|
||||
Self {
|
||||
name: value.name,
|
||||
download_url: value.browser_download_url,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl github::Host for WasmState {
|
||||
async fn latest_github_release(
|
||||
&mut self,
|
||||
repo: String,
|
||||
options: github::GithubReleaseOptions,
|
||||
) -> wasmtime::Result<Result<github::GithubRelease, String>> {
|
||||
maybe!(async {
|
||||
let release = ::http_client::github::latest_github_release(
|
||||
&repo,
|
||||
options.require_assets,
|
||||
options.pre_release,
|
||||
self.host.http_client.clone(),
|
||||
)
|
||||
.await?;
|
||||
Ok(release.into())
|
||||
})
|
||||
.await
|
||||
.to_wasmtime_result()
|
||||
}
|
||||
|
||||
async fn github_release_by_tag_name(
|
||||
&mut self,
|
||||
repo: String,
|
||||
tag: String,
|
||||
) -> wasmtime::Result<Result<github::GithubRelease, String>> {
|
||||
maybe!(async {
|
||||
let release = ::http_client::github::get_release_by_tag_name(
|
||||
&repo,
|
||||
&tag,
|
||||
self.host.http_client.clone(),
|
||||
)
|
||||
.await?;
|
||||
Ok(release.into())
|
||||
})
|
||||
.await
|
||||
.to_wasmtime_result()
|
||||
}
|
||||
}
|
||||
|
||||
impl platform::Host for WasmState {
|
||||
async fn current_platform(&mut self) -> Result<(platform::Os, platform::Architecture)> {
|
||||
Ok((
|
||||
match env::consts::OS {
|
||||
"macos" => platform::Os::Mac,
|
||||
"linux" => platform::Os::Linux,
|
||||
"windows" => platform::Os::Windows,
|
||||
_ => panic!("unsupported os"),
|
||||
},
|
||||
match env::consts::ARCH {
|
||||
"aarch64" => platform::Architecture::Aarch64,
|
||||
"x86" => platform::Architecture::X86,
|
||||
"x86_64" => platform::Architecture::X8664,
|
||||
_ => panic!("unsupported architecture"),
|
||||
},
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::process::Output> for process::Output {
|
||||
fn from(output: std::process::Output) -> Self {
|
||||
Self {
|
||||
status: output.status.code(),
|
||||
stdout: output.stdout,
|
||||
stderr: output.stderr,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl process::Host for WasmState {
|
||||
async fn run_command(
|
||||
&mut self,
|
||||
command: process::Command,
|
||||
) -> wasmtime::Result<Result<process::Output, String>> {
|
||||
maybe!(async {
|
||||
self.manifest.allow_exec(&command.command, &command.args)?;
|
||||
|
||||
let output = util::command::new_smol_command(command.command.as_str())
|
||||
.args(&command.args)
|
||||
.envs(command.env)
|
||||
.output()
|
||||
.await?;
|
||||
|
||||
Ok(output.into())
|
||||
})
|
||||
.await
|
||||
.to_wasmtime_result()
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl slash_command::Host for WasmState {}
|
||||
|
||||
#[async_trait]
|
||||
impl context_server::Host for WasmState {}
|
||||
|
||||
impl ExtensionImports for WasmState {
|
||||
async fn get_settings(
|
||||
&mut self,
|
||||
location: Option<self::SettingsLocation>,
|
||||
category: String,
|
||||
key: Option<String>,
|
||||
) -> wasmtime::Result<Result<String, String>> {
|
||||
self.on_main_thread(|cx| {
|
||||
async move {
|
||||
let location = location
|
||||
.as_ref()
|
||||
.map(|location| ::settings::SettingsLocation {
|
||||
worktree_id: WorktreeId::from_proto(location.worktree_id),
|
||||
path: Path::new(&location.path),
|
||||
});
|
||||
|
||||
cx.update(|cx| match category.as_str() {
|
||||
"language" => {
|
||||
let key = key.map(|k| LanguageName::new(&k));
|
||||
let settings = AllLanguageSettings::get(location, cx).language(
|
||||
location,
|
||||
key.as_ref(),
|
||||
cx,
|
||||
);
|
||||
Ok(serde_json::to_string(&settings::LanguageSettings {
|
||||
tab_size: settings.tab_size,
|
||||
})?)
|
||||
}
|
||||
"lsp" => {
|
||||
let settings = key
|
||||
.and_then(|key| {
|
||||
ProjectSettings::get(location, cx)
|
||||
.lsp
|
||||
.get(&::lsp::LanguageServerName::from_proto(key))
|
||||
})
|
||||
.cloned()
|
||||
.unwrap_or_default();
|
||||
Ok(serde_json::to_string(&settings::LspSettings {
|
||||
binary: settings.binary.map(|binary| settings::CommandSettings {
|
||||
path: binary.path,
|
||||
arguments: binary.arguments,
|
||||
env: binary.env,
|
||||
}),
|
||||
settings: settings.settings,
|
||||
initialization_options: settings.initialization_options,
|
||||
})?)
|
||||
}
|
||||
"context_servers" => {
|
||||
let configuration = key
|
||||
.and_then(|key| {
|
||||
ProjectSettings::get(location, cx)
|
||||
.context_servers
|
||||
.get(key.as_str())
|
||||
})
|
||||
.cloned()
|
||||
.unwrap_or_default();
|
||||
Ok(serde_json::to_string(&settings::ContextServerSettings {
|
||||
command: configuration.command.map(|command| {
|
||||
settings::CommandSettings {
|
||||
path: Some(command.path),
|
||||
arguments: Some(command.args),
|
||||
env: command.env.map(|env| env.into_iter().collect()),
|
||||
}
|
||||
}),
|
||||
settings: configuration.settings,
|
||||
})?)
|
||||
}
|
||||
_ => {
|
||||
bail!("Unknown settings category: {}", category);
|
||||
}
|
||||
})
|
||||
}
|
||||
.boxed_local()
|
||||
})
|
||||
.await?
|
||||
.to_wasmtime_result()
|
||||
}
|
||||
|
||||
async fn set_language_server_installation_status(
|
||||
&mut self,
|
||||
server_name: String,
|
||||
status: LanguageServerInstallationStatus,
|
||||
) -> wasmtime::Result<()> {
|
||||
let status = match status {
|
||||
LanguageServerInstallationStatus::CheckingForUpdate => BinaryStatus::CheckingForUpdate,
|
||||
LanguageServerInstallationStatus::Downloading => BinaryStatus::Downloading,
|
||||
LanguageServerInstallationStatus::None => BinaryStatus::None,
|
||||
LanguageServerInstallationStatus::Failed(error) => BinaryStatus::Failed { error },
|
||||
};
|
||||
|
||||
self.host
|
||||
.proxy
|
||||
.update_language_server_status(::lsp::LanguageServerName(server_name.into()), status);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn download_file(
|
||||
&mut self,
|
||||
url: String,
|
||||
path: String,
|
||||
file_type: DownloadedFileType,
|
||||
) -> wasmtime::Result<Result<(), String>> {
|
||||
maybe!(async {
|
||||
let path = PathBuf::from(path);
|
||||
let extension_work_dir = self.host.work_dir.join(self.manifest.id.as_ref());
|
||||
|
||||
self.host.fs.create_dir(&extension_work_dir).await?;
|
||||
|
||||
let destination_path = self
|
||||
.host
|
||||
.writeable_path_from_extension(&self.manifest.id, &path)?;
|
||||
|
||||
let mut response = self
|
||||
.host
|
||||
.http_client
|
||||
.get(&url, Default::default(), true)
|
||||
.await
|
||||
.map_err(|err| anyhow!("error downloading release: {}", err))?;
|
||||
|
||||
if !response.status().is_success() {
|
||||
Err(anyhow!(
|
||||
"download failed with status {}",
|
||||
response.status().to_string()
|
||||
))?;
|
||||
}
|
||||
let body = BufReader::new(response.body_mut());
|
||||
|
||||
match file_type {
|
||||
DownloadedFileType::Uncompressed => {
|
||||
futures::pin_mut!(body);
|
||||
self.host
|
||||
.fs
|
||||
.create_file_with(&destination_path, body)
|
||||
.await?;
|
||||
}
|
||||
DownloadedFileType::Gzip => {
|
||||
let body = GzipDecoder::new(body);
|
||||
futures::pin_mut!(body);
|
||||
self.host
|
||||
.fs
|
||||
.create_file_with(&destination_path, body)
|
||||
.await?;
|
||||
}
|
||||
DownloadedFileType::GzipTar => {
|
||||
let body = GzipDecoder::new(body);
|
||||
futures::pin_mut!(body);
|
||||
self.host
|
||||
.fs
|
||||
.extract_tar_file(&destination_path, Archive::new(body))
|
||||
.await?;
|
||||
}
|
||||
DownloadedFileType::Zip => {
|
||||
futures::pin_mut!(body);
|
||||
node_runtime::extract_zip(&destination_path, body)
|
||||
.await
|
||||
.with_context(|| format!("failed to unzip {} archive", path.display()))?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.await
|
||||
.to_wasmtime_result()
|
||||
}
|
||||
|
||||
async fn make_file_executable(&mut self, path: String) -> wasmtime::Result<Result<(), String>> {
|
||||
#[allow(unused)]
|
||||
let path = self
|
||||
.host
|
||||
.writeable_path_from_extension(&self.manifest.id, Path::new(&path))?;
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::fs::{self, Permissions};
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
|
||||
return fs::set_permissions(&path, Permissions::from_mode(0o755))
|
||||
.map_err(|error| anyhow!("failed to set permissions for path {path:?}: {error}"))
|
||||
.to_wasmtime_result();
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
Ok(Ok(()))
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue