Compare commits
13 Commits
telemetry-
...
pass-remot
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
983e878cff | ||
|
|
1cfcdfa7ac | ||
|
|
c9f2c2792c | ||
|
|
8240a52a39 | ||
|
|
c28f5b11f8 | ||
|
|
96854c68ea | ||
|
|
becc36380f | ||
|
|
1a0a8a9559 | ||
|
|
2fd210bc9a | ||
|
|
23321be2ce | ||
|
|
659b1c9dcf | ||
|
|
cb8028c092 | ||
|
|
ca76948044 |
50
Cargo.lock
generated
50
Cargo.lock
generated
@@ -2601,6 +2601,7 @@ dependencies = [
|
||||
"editor",
|
||||
"env_logger 0.11.5",
|
||||
"envy",
|
||||
"extension",
|
||||
"file_finder",
|
||||
"fs",
|
||||
"futures 0.3.31",
|
||||
@@ -2842,6 +2843,7 @@ dependencies = [
|
||||
"anyhow",
|
||||
"collections",
|
||||
"command_palette_hooks",
|
||||
"extension",
|
||||
"futures 0.3.31",
|
||||
"gpui",
|
||||
"log",
|
||||
@@ -4127,6 +4129,7 @@ dependencies = [
|
||||
"language",
|
||||
"log",
|
||||
"lsp",
|
||||
"parking_lot",
|
||||
"semantic_version",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -4178,6 +4181,7 @@ dependencies = [
|
||||
"gpui",
|
||||
"http_client",
|
||||
"language",
|
||||
"language_extension",
|
||||
"log",
|
||||
"lsp",
|
||||
"node_runtime",
|
||||
@@ -4196,6 +4200,7 @@ dependencies = [
|
||||
"task",
|
||||
"tempfile",
|
||||
"theme",
|
||||
"theme_extension",
|
||||
"toml 0.8.19",
|
||||
"url",
|
||||
"util",
|
||||
@@ -4209,21 +4214,15 @@ name = "extensions_ui"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"assistant_slash_command",
|
||||
"client",
|
||||
"collections",
|
||||
"context_servers",
|
||||
"db",
|
||||
"editor",
|
||||
"extension",
|
||||
"extension_host",
|
||||
"fs",
|
||||
"fuzzy",
|
||||
"gpui",
|
||||
"indexed_docs",
|
||||
"language",
|
||||
"log",
|
||||
"lsp",
|
||||
"num-format",
|
||||
"picker",
|
||||
"project",
|
||||
@@ -4232,12 +4231,10 @@ dependencies = [
|
||||
"serde",
|
||||
"settings",
|
||||
"smallvec",
|
||||
"snippet_provider",
|
||||
"theme",
|
||||
"ui",
|
||||
"util",
|
||||
"vim_mode_setting",
|
||||
"wasmtime-wasi",
|
||||
"workspace",
|
||||
"zed_actions",
|
||||
]
|
||||
@@ -6534,6 +6531,23 @@ dependencies = [
|
||||
"util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "language_extension"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"collections",
|
||||
"extension",
|
||||
"futures 0.3.31",
|
||||
"gpui",
|
||||
"language",
|
||||
"lsp",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "language_model"
|
||||
version = "0.1.0"
|
||||
@@ -9854,6 +9868,7 @@ dependencies = [
|
||||
"client",
|
||||
"clock",
|
||||
"env_logger 0.11.5",
|
||||
"extension",
|
||||
"extension_host",
|
||||
"fork",
|
||||
"fs",
|
||||
@@ -9863,6 +9878,7 @@ dependencies = [
|
||||
"gpui",
|
||||
"http_client",
|
||||
"language",
|
||||
"language_extension",
|
||||
"languages",
|
||||
"libc",
|
||||
"log",
|
||||
@@ -11305,6 +11321,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"collections",
|
||||
"extension",
|
||||
"fs",
|
||||
"futures 0.3.31",
|
||||
"gpui",
|
||||
@@ -12358,6 +12375,17 @@ dependencies = [
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "theme_extension"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"extension",
|
||||
"fs",
|
||||
"gpui",
|
||||
"theme",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "theme_importer"
|
||||
version = "0.1.0"
|
||||
@@ -15467,7 +15495,6 @@ dependencies = [
|
||||
"ashpd",
|
||||
"assets",
|
||||
"assistant",
|
||||
"assistant_slash_command",
|
||||
"async-watch",
|
||||
"audio",
|
||||
"auto_update",
|
||||
@@ -15484,12 +15511,12 @@ dependencies = [
|
||||
"collections",
|
||||
"command_palette",
|
||||
"command_palette_hooks",
|
||||
"context_servers",
|
||||
"copilot",
|
||||
"db",
|
||||
"diagnostics",
|
||||
"editor",
|
||||
"env_logger 0.11.5",
|
||||
"extension",
|
||||
"extension_host",
|
||||
"extensions_ui",
|
||||
"feature_flags",
|
||||
@@ -15504,11 +15531,11 @@ dependencies = [
|
||||
"gpui",
|
||||
"http_client",
|
||||
"image_viewer",
|
||||
"indexed_docs",
|
||||
"inline_completion_button",
|
||||
"install_cli",
|
||||
"journal",
|
||||
"language",
|
||||
"language_extension",
|
||||
"language_model",
|
||||
"language_models",
|
||||
"language_selector",
|
||||
@@ -15557,6 +15584,7 @@ dependencies = [
|
||||
"telemetry_events",
|
||||
"terminal_view",
|
||||
"theme",
|
||||
"theme_extension",
|
||||
"theme_selector",
|
||||
"time",
|
||||
"toolchain_selector",
|
||||
|
||||
@@ -55,6 +55,7 @@ members = [
|
||||
"crates/install_cli",
|
||||
"crates/journal",
|
||||
"crates/language",
|
||||
"crates/language_extension",
|
||||
"crates/language_model",
|
||||
"crates/language_models",
|
||||
"crates/language_selector",
|
||||
@@ -116,6 +117,7 @@ members = [
|
||||
"crates/terminal_view",
|
||||
"crates/text",
|
||||
"crates/theme",
|
||||
"crates/theme_extension",
|
||||
"crates/theme_importer",
|
||||
"crates/theme_selector",
|
||||
"crates/time_format",
|
||||
@@ -230,6 +232,7 @@ inline_completion_button = { path = "crates/inline_completion_button" }
|
||||
install_cli = { path = "crates/install_cli" }
|
||||
journal = { path = "crates/journal" }
|
||||
language = { path = "crates/language" }
|
||||
language_extension = { path = "crates/language_extension" }
|
||||
language_model = { path = "crates/language_model" }
|
||||
language_models = { path = "crates/language_models" }
|
||||
language_selector = { path = "crates/language_selector" }
|
||||
@@ -292,6 +295,7 @@ terminal = { path = "crates/terminal" }
|
||||
terminal_view = { path = "crates/terminal_view" }
|
||||
text = { path = "crates/text" }
|
||||
theme = { path = "crates/theme" }
|
||||
theme_extension = { path = "crates/theme_extension" }
|
||||
theme_importer = { path = "crates/theme_importer" }
|
||||
theme_selector = { path = "crates/theme_selector" }
|
||||
time_format = { path = "crates/time_format" }
|
||||
|
||||
@@ -683,10 +683,7 @@
|
||||
// ignored by git. This is useful for files that are not tracked by git,
|
||||
// but are still important to your project. Note that globs that are
|
||||
// overly broad can slow down Zed's file scanning. Overridden by `file_scan_exclusions`.
|
||||
"file_scan_inclusions": [
|
||||
".env*",
|
||||
"docker-compose.*.yml"
|
||||
],
|
||||
"file_scan_inclusions": [".env*"],
|
||||
// Git gutter behavior configuration.
|
||||
"git": {
|
||||
// Control whether the git gutter is shown. May take 2 values:
|
||||
|
||||
@@ -33,7 +33,6 @@ use feature_flags::FeatureFlagAppExt;
|
||||
use fs::Fs;
|
||||
use gpui::impl_actions;
|
||||
use gpui::{actions, AppContext, Global, SharedString, UpdateGlobal};
|
||||
use indexed_docs::IndexedDocsRegistry;
|
||||
pub(crate) use inline_assistant::*;
|
||||
use language_model::{
|
||||
LanguageModelId, LanguageModelProviderId, LanguageModelRegistry, LanguageModelResponseMessage,
|
||||
@@ -275,7 +274,7 @@ pub fn init(
|
||||
client.telemetry().clone(),
|
||||
cx,
|
||||
);
|
||||
IndexedDocsRegistry::init_global(cx);
|
||||
indexed_docs::init(cx);
|
||||
|
||||
CommandPaletteFilter::update_global(cx, |filter, _cx| {
|
||||
filter.hide_namespace(Assistant::NAMESPACE);
|
||||
|
||||
@@ -18,6 +18,7 @@ use workspace::{ui::IconName, Workspace};
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
SlashCommandRegistry::default_global(cx);
|
||||
extension_slash_command::init(cx);
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
|
||||
@@ -3,17 +3,39 @@ use std::sync::{atomic::AtomicBool, Arc};
|
||||
|
||||
use anyhow::Result;
|
||||
use async_trait::async_trait;
|
||||
use extension::{Extension, WorktreeDelegate};
|
||||
use gpui::{Task, WeakView, WindowContext};
|
||||
use extension::{Extension, ExtensionHostProxy, ExtensionSlashCommandProxy, WorktreeDelegate};
|
||||
use gpui::{AppContext, Task, WeakView, WindowContext};
|
||||
use language::{BufferSnapshot, LspAdapterDelegate};
|
||||
use ui::prelude::*;
|
||||
use workspace::Workspace;
|
||||
|
||||
use crate::{
|
||||
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
|
||||
SlashCommandResult,
|
||||
SlashCommandRegistry, SlashCommandResult,
|
||||
};
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
let proxy = ExtensionHostProxy::default_global(cx);
|
||||
proxy.register_slash_command_proxy(SlashCommandRegistryProxy {
|
||||
slash_command_registry: SlashCommandRegistry::global(cx),
|
||||
});
|
||||
}
|
||||
|
||||
struct SlashCommandRegistryProxy {
|
||||
slash_command_registry: Arc<SlashCommandRegistry>,
|
||||
}
|
||||
|
||||
impl ExtensionSlashCommandProxy for SlashCommandRegistryProxy {
|
||||
fn register_slash_command(
|
||||
&self,
|
||||
extension: Arc<dyn Extension>,
|
||||
command: extension::SlashCommand,
|
||||
) {
|
||||
self.slash_command_registry
|
||||
.register_command(ExtensionSlashCommand::new(extension, command), false)
|
||||
}
|
||||
}
|
||||
|
||||
/// An adapter that allows an [`LspAdapterDelegate`] to be used as a [`WorktreeDelegate`].
|
||||
struct WorktreeDelegateAdapter(Arc<dyn LspAdapterDelegate>);
|
||||
|
||||
|
||||
@@ -90,6 +90,7 @@ collections = { workspace = true, features = ["test-support"] }
|
||||
ctor.workspace = true
|
||||
editor = { workspace = true, features = ["test-support"] }
|
||||
env_logger.workspace = true
|
||||
extension.workspace = true
|
||||
file_finder.workspace = true
|
||||
fs = { workspace = true, features = ["test-support"] }
|
||||
git = { workspace = true, features = ["test-support"] }
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::tests::TestServer;
|
||||
use call::ActiveCall;
|
||||
use collections::HashSet;
|
||||
use extension::ExtensionHostProxy;
|
||||
use fs::{FakeFs, Fs as _};
|
||||
use futures::StreamExt as _;
|
||||
use gpui::{BackgroundExecutor, Context as _, SemanticVersion, TestAppContext, UpdateGlobal as _};
|
||||
@@ -81,6 +82,7 @@ async fn test_sharing_an_ssh_remote_project(
|
||||
http_client: remote_http_client,
|
||||
node_runtime: node,
|
||||
languages,
|
||||
extension_host_proxy: Arc::new(ExtensionHostProxy::new()),
|
||||
},
|
||||
cx,
|
||||
)
|
||||
@@ -243,6 +245,7 @@ async fn test_ssh_collaboration_git_branches(
|
||||
http_client: remote_http_client,
|
||||
node_runtime: node,
|
||||
languages,
|
||||
extension_host_proxy: Arc::new(ExtensionHostProxy::new()),
|
||||
},
|
||||
cx,
|
||||
)
|
||||
@@ -400,6 +403,7 @@ async fn test_ssh_collaboration_formatting_with_prettier(
|
||||
http_client: remote_http_client,
|
||||
node_runtime: NodeRuntime::unavailable(),
|
||||
languages,
|
||||
extension_host_proxy: Arc::new(ExtensionHostProxy::new()),
|
||||
},
|
||||
cx,
|
||||
)
|
||||
|
||||
@@ -15,6 +15,7 @@ path = "src/context_servers.rs"
|
||||
anyhow.workspace = true
|
||||
collections.workspace = true
|
||||
command_palette_hooks.workspace = true
|
||||
extension.workspace = true
|
||||
futures.workspace = true
|
||||
gpui.workspace = true
|
||||
log.workspace = true
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
pub mod client;
|
||||
mod extension_context_server;
|
||||
pub mod manager;
|
||||
pub mod protocol;
|
||||
mod registry;
|
||||
@@ -19,6 +20,7 @@ pub const CONTEXT_SERVERS_NAMESPACE: &'static str = "context_servers";
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
ContextServerSettings::register(cx);
|
||||
ContextServerFactoryRegistry::default_global(cx);
|
||||
extension_context_server::init(cx);
|
||||
|
||||
CommandPaletteFilter::update_global(cx, |filter, _cx| {
|
||||
filter.hide_namespace(CONTEXT_SERVERS_NAMESPACE);
|
||||
|
||||
78
crates/context_servers/src/extension_context_server.rs
Normal file
78
crates/context_servers/src/extension_context_server.rs
Normal file
@@ -0,0 +1,78 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use extension::{Extension, ExtensionContextServerProxy, ExtensionHostProxy, ProjectDelegate};
|
||||
use gpui::{AppContext, Model};
|
||||
|
||||
use crate::manager::ServerCommand;
|
||||
use crate::ContextServerFactoryRegistry;
|
||||
|
||||
struct ExtensionProject {
|
||||
worktree_ids: Vec<u64>,
|
||||
}
|
||||
|
||||
impl ProjectDelegate for ExtensionProject {
|
||||
fn worktree_ids(&self) -> Vec<u64> {
|
||||
self.worktree_ids.clone()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
let proxy = ExtensionHostProxy::default_global(cx);
|
||||
proxy.register_context_server_proxy(ContextServerFactoryRegistryProxy {
|
||||
context_server_factory_registry: ContextServerFactoryRegistry::global(cx),
|
||||
});
|
||||
}
|
||||
|
||||
struct ContextServerFactoryRegistryProxy {
|
||||
context_server_factory_registry: Model<ContextServerFactoryRegistry>,
|
||||
}
|
||||
|
||||
impl ExtensionContextServerProxy for ContextServerFactoryRegistryProxy {
|
||||
fn register_context_server(
|
||||
&self,
|
||||
extension: Arc<dyn Extension>,
|
||||
id: Arc<str>,
|
||||
cx: &mut AppContext,
|
||||
) {
|
||||
self.context_server_factory_registry
|
||||
.update(cx, |registry, _| {
|
||||
registry.register_server_factory(
|
||||
id.clone(),
|
||||
Arc::new({
|
||||
move |project, cx| {
|
||||
log::info!(
|
||||
"loading command for context server {id} from extension {}",
|
||||
extension.manifest().id
|
||||
);
|
||||
|
||||
let id = id.clone();
|
||||
let extension = extension.clone();
|
||||
cx.spawn(|mut cx| async move {
|
||||
let extension_project =
|
||||
project.update(&mut cx, |project, cx| {
|
||||
Arc::new(ExtensionProject {
|
||||
worktree_ids: project
|
||||
.visible_worktrees(cx)
|
||||
.map(|worktree| worktree.read(cx).id().to_proto())
|
||||
.collect(),
|
||||
})
|
||||
})?;
|
||||
|
||||
let command = extension
|
||||
.context_server_command(id.clone(), extension_project)
|
||||
.await?;
|
||||
|
||||
log::info!("loaded command for context server {id}: {command:?}");
|
||||
|
||||
Ok(ServerCommand {
|
||||
path: command.command,
|
||||
args: command.args,
|
||||
env: Some(command.env.into_iter().collect()),
|
||||
})
|
||||
})
|
||||
}
|
||||
}),
|
||||
)
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,7 @@ http_client.workspace = true
|
||||
language.workspace = true
|
||||
log.workspace = true
|
||||
lsp.workspace = true
|
||||
parking_lot.workspace = true
|
||||
semantic_version.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
pub mod extension_builder;
|
||||
mod extension_host_proxy;
|
||||
mod extension_manifest;
|
||||
mod types;
|
||||
|
||||
@@ -9,13 +10,19 @@ use ::lsp::LanguageServerName;
|
||||
use anyhow::{anyhow, bail, Context as _, Result};
|
||||
use async_trait::async_trait;
|
||||
use fs::normalize_path;
|
||||
use gpui::Task;
|
||||
use gpui::{AppContext, Task};
|
||||
use language::LanguageName;
|
||||
use semantic_version::SemanticVersion;
|
||||
|
||||
pub use crate::extension_host_proxy::*;
|
||||
pub use crate::extension_manifest::*;
|
||||
pub use crate::types::*;
|
||||
|
||||
/// Initializes the `extension` crate.
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
ExtensionHostProxy::default_global(cx);
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait WorktreeDelegate: Send + Sync + 'static {
|
||||
fn id(&self) -> u64;
|
||||
@@ -25,6 +32,10 @@ pub trait WorktreeDelegate: Send + Sync + 'static {
|
||||
async fn shell_env(&self) -> Vec<(String, String)>;
|
||||
}
|
||||
|
||||
pub trait ProjectDelegate: Send + Sync + 'static {
|
||||
fn worktree_ids(&self) -> Vec<u64>;
|
||||
}
|
||||
|
||||
pub trait KeyValueStoreDelegate: Send + Sync + 'static {
|
||||
fn insert(&self, key: String, docs: String) -> Task<Result<()>>;
|
||||
}
|
||||
@@ -87,6 +98,12 @@ pub trait Extension: Send + Sync + 'static {
|
||||
worktree: Option<Arc<dyn WorktreeDelegate>>,
|
||||
) -> Result<SlashCommandOutput>;
|
||||
|
||||
async fn context_server_command(
|
||||
&self,
|
||||
context_server_id: Arc<str>,
|
||||
project: Arc<dyn ProjectDelegate>,
|
||||
) -> Result<Command>;
|
||||
|
||||
async fn suggest_docs_packages(&self, provider: Arc<str>) -> Result<Vec<String>>;
|
||||
|
||||
async fn index_docs(
|
||||
|
||||
324
crates/extension/src/extension_host_proxy.rs
Normal file
324
crates/extension/src/extension_host_proxy.rs
Normal file
@@ -0,0 +1,324 @@
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::Result;
|
||||
use fs::Fs;
|
||||
use gpui::{AppContext, Global, ReadGlobal, SharedString, Task};
|
||||
use language::{LanguageMatcher, LanguageName, LanguageServerBinaryStatus, LoadedLanguage};
|
||||
use lsp::LanguageServerName;
|
||||
use parking_lot::RwLock;
|
||||
|
||||
use crate::{Extension, SlashCommand};
|
||||
|
||||
#[derive(Default)]
|
||||
struct GlobalExtensionHostProxy(Arc<ExtensionHostProxy>);
|
||||
|
||||
impl Global for GlobalExtensionHostProxy {}
|
||||
|
||||
/// A proxy for interacting with the extension host.
|
||||
///
|
||||
/// This object implements each of the individual proxy types so that their
|
||||
/// methods can be called directly on it.
|
||||
#[derive(Default)]
|
||||
pub struct ExtensionHostProxy {
|
||||
theme_proxy: RwLock<Option<Arc<dyn ExtensionThemeProxy>>>,
|
||||
grammar_proxy: RwLock<Option<Arc<dyn ExtensionGrammarProxy>>>,
|
||||
language_proxy: RwLock<Option<Arc<dyn ExtensionLanguageProxy>>>,
|
||||
language_server_proxy: RwLock<Option<Arc<dyn ExtensionLanguageServerProxy>>>,
|
||||
snippet_proxy: RwLock<Option<Arc<dyn ExtensionSnippetProxy>>>,
|
||||
slash_command_proxy: RwLock<Option<Arc<dyn ExtensionSlashCommandProxy>>>,
|
||||
context_server_proxy: RwLock<Option<Arc<dyn ExtensionContextServerProxy>>>,
|
||||
indexed_docs_provider_proxy: RwLock<Option<Arc<dyn ExtensionIndexedDocsProviderProxy>>>,
|
||||
}
|
||||
|
||||
impl ExtensionHostProxy {
|
||||
/// Returns the global [`ExtensionHostProxy`].
|
||||
pub fn global(cx: &AppContext) -> Arc<Self> {
|
||||
GlobalExtensionHostProxy::global(cx).0.clone()
|
||||
}
|
||||
|
||||
/// Returns the global [`ExtensionHostProxy`].
|
||||
///
|
||||
/// Inserts a default [`ExtensionHostProxy`] if one does not yet exist.
|
||||
pub fn default_global(cx: &mut AppContext) -> Arc<Self> {
|
||||
cx.default_global::<GlobalExtensionHostProxy>().0.clone()
|
||||
}
|
||||
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
theme_proxy: RwLock::default(),
|
||||
grammar_proxy: RwLock::default(),
|
||||
language_proxy: RwLock::default(),
|
||||
language_server_proxy: RwLock::default(),
|
||||
snippet_proxy: RwLock::default(),
|
||||
slash_command_proxy: RwLock::default(),
|
||||
context_server_proxy: RwLock::default(),
|
||||
indexed_docs_provider_proxy: RwLock::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn register_theme_proxy(&self, proxy: impl ExtensionThemeProxy) {
|
||||
self.theme_proxy.write().replace(Arc::new(proxy));
|
||||
}
|
||||
|
||||
pub fn register_grammar_proxy(&self, proxy: impl ExtensionGrammarProxy) {
|
||||
self.grammar_proxy.write().replace(Arc::new(proxy));
|
||||
}
|
||||
|
||||
pub fn register_language_proxy(&self, proxy: impl ExtensionLanguageProxy) {
|
||||
self.language_proxy.write().replace(Arc::new(proxy));
|
||||
}
|
||||
|
||||
pub fn register_language_server_proxy(&self, proxy: impl ExtensionLanguageServerProxy) {
|
||||
self.language_server_proxy.write().replace(Arc::new(proxy));
|
||||
}
|
||||
|
||||
pub fn register_snippet_proxy(&self, proxy: impl ExtensionSnippetProxy) {
|
||||
self.snippet_proxy.write().replace(Arc::new(proxy));
|
||||
}
|
||||
|
||||
pub fn register_slash_command_proxy(&self, proxy: impl ExtensionSlashCommandProxy) {
|
||||
self.slash_command_proxy.write().replace(Arc::new(proxy));
|
||||
}
|
||||
|
||||
pub fn register_context_server_proxy(&self, proxy: impl ExtensionContextServerProxy) {
|
||||
self.context_server_proxy.write().replace(Arc::new(proxy));
|
||||
}
|
||||
|
||||
pub fn register_indexed_docs_provider_proxy(
|
||||
&self,
|
||||
proxy: impl ExtensionIndexedDocsProviderProxy,
|
||||
) {
|
||||
self.indexed_docs_provider_proxy
|
||||
.write()
|
||||
.replace(Arc::new(proxy));
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ExtensionThemeProxy: Send + Sync + 'static {
|
||||
fn list_theme_names(&self, theme_path: PathBuf, fs: Arc<dyn Fs>) -> Task<Result<Vec<String>>>;
|
||||
|
||||
fn remove_user_themes(&self, themes: Vec<SharedString>);
|
||||
|
||||
fn load_user_theme(&self, theme_path: PathBuf, fs: Arc<dyn Fs>) -> Task<Result<()>>;
|
||||
|
||||
fn reload_current_theme(&self, cx: &mut AppContext);
|
||||
}
|
||||
|
||||
impl ExtensionThemeProxy for ExtensionHostProxy {
|
||||
fn list_theme_names(&self, theme_path: PathBuf, fs: Arc<dyn Fs>) -> Task<Result<Vec<String>>> {
|
||||
let Some(proxy) = self.theme_proxy.read().clone() else {
|
||||
return Task::ready(Ok(Vec::new()));
|
||||
};
|
||||
|
||||
proxy.list_theme_names(theme_path, fs)
|
||||
}
|
||||
|
||||
fn remove_user_themes(&self, themes: Vec<SharedString>) {
|
||||
let Some(proxy) = self.theme_proxy.read().clone() else {
|
||||
return;
|
||||
};
|
||||
|
||||
proxy.remove_user_themes(themes)
|
||||
}
|
||||
|
||||
fn load_user_theme(&self, theme_path: PathBuf, fs: Arc<dyn Fs>) -> Task<Result<()>> {
|
||||
let Some(proxy) = self.theme_proxy.read().clone() else {
|
||||
return Task::ready(Ok(()));
|
||||
};
|
||||
|
||||
proxy.load_user_theme(theme_path, fs)
|
||||
}
|
||||
|
||||
fn reload_current_theme(&self, cx: &mut AppContext) {
|
||||
let Some(proxy) = self.theme_proxy.read().clone() else {
|
||||
return;
|
||||
};
|
||||
|
||||
proxy.reload_current_theme(cx)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ExtensionGrammarProxy: Send + Sync + 'static {
|
||||
fn register_grammars(&self, grammars: Vec<(Arc<str>, PathBuf)>);
|
||||
}
|
||||
|
||||
impl ExtensionGrammarProxy for ExtensionHostProxy {
|
||||
fn register_grammars(&self, grammars: Vec<(Arc<str>, PathBuf)>) {
|
||||
let Some(proxy) = self.grammar_proxy.read().clone() else {
|
||||
return;
|
||||
};
|
||||
|
||||
proxy.register_grammars(grammars)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ExtensionLanguageProxy: Send + Sync + 'static {
|
||||
fn register_language(
|
||||
&self,
|
||||
language: LanguageName,
|
||||
grammar: Option<Arc<str>>,
|
||||
matcher: LanguageMatcher,
|
||||
load: Arc<dyn Fn() -> Result<LoadedLanguage> + Send + Sync + 'static>,
|
||||
);
|
||||
|
||||
fn remove_languages(
|
||||
&self,
|
||||
languages_to_remove: &[LanguageName],
|
||||
grammars_to_remove: &[Arc<str>],
|
||||
);
|
||||
}
|
||||
|
||||
impl ExtensionLanguageProxy for ExtensionHostProxy {
|
||||
fn register_language(
|
||||
&self,
|
||||
language: LanguageName,
|
||||
grammar: Option<Arc<str>>,
|
||||
matcher: LanguageMatcher,
|
||||
load: Arc<dyn Fn() -> Result<LoadedLanguage> + Send + Sync + 'static>,
|
||||
) {
|
||||
let Some(proxy) = self.language_proxy.read().clone() else {
|
||||
return;
|
||||
};
|
||||
|
||||
proxy.register_language(language, grammar, matcher, load)
|
||||
}
|
||||
|
||||
fn remove_languages(
|
||||
&self,
|
||||
languages_to_remove: &[LanguageName],
|
||||
grammars_to_remove: &[Arc<str>],
|
||||
) {
|
||||
let Some(proxy) = self.language_proxy.read().clone() else {
|
||||
return;
|
||||
};
|
||||
|
||||
proxy.remove_languages(languages_to_remove, grammars_to_remove)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ExtensionLanguageServerProxy: Send + Sync + 'static {
|
||||
fn register_language_server(
|
||||
&self,
|
||||
extension: Arc<dyn Extension>,
|
||||
language_server_id: LanguageServerName,
|
||||
language: LanguageName,
|
||||
);
|
||||
|
||||
fn remove_language_server(
|
||||
&self,
|
||||
language: &LanguageName,
|
||||
language_server_id: &LanguageServerName,
|
||||
);
|
||||
|
||||
fn update_language_server_status(
|
||||
&self,
|
||||
language_server_id: LanguageServerName,
|
||||
status: LanguageServerBinaryStatus,
|
||||
);
|
||||
}
|
||||
|
||||
impl ExtensionLanguageServerProxy for ExtensionHostProxy {
|
||||
fn register_language_server(
|
||||
&self,
|
||||
extension: Arc<dyn Extension>,
|
||||
language_server_id: LanguageServerName,
|
||||
language: LanguageName,
|
||||
) {
|
||||
let Some(proxy) = self.language_server_proxy.read().clone() else {
|
||||
return;
|
||||
};
|
||||
|
||||
proxy.register_language_server(extension, language_server_id, language)
|
||||
}
|
||||
|
||||
fn remove_language_server(
|
||||
&self,
|
||||
language: &LanguageName,
|
||||
language_server_id: &LanguageServerName,
|
||||
) {
|
||||
let Some(proxy) = self.language_server_proxy.read().clone() else {
|
||||
return;
|
||||
};
|
||||
|
||||
proxy.remove_language_server(language, language_server_id)
|
||||
}
|
||||
|
||||
fn update_language_server_status(
|
||||
&self,
|
||||
language_server_id: LanguageServerName,
|
||||
status: LanguageServerBinaryStatus,
|
||||
) {
|
||||
let Some(proxy) = self.language_server_proxy.read().clone() else {
|
||||
return;
|
||||
};
|
||||
|
||||
proxy.update_language_server_status(language_server_id, status)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ExtensionSnippetProxy: Send + Sync + 'static {
|
||||
fn register_snippet(&self, path: &PathBuf, snippet_contents: &str) -> Result<()>;
|
||||
}
|
||||
|
||||
impl ExtensionSnippetProxy for ExtensionHostProxy {
|
||||
fn register_snippet(&self, path: &PathBuf, snippet_contents: &str) -> Result<()> {
|
||||
let Some(proxy) = self.snippet_proxy.read().clone() else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
proxy.register_snippet(path, snippet_contents)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ExtensionSlashCommandProxy: Send + Sync + 'static {
|
||||
fn register_slash_command(&self, extension: Arc<dyn Extension>, command: SlashCommand);
|
||||
}
|
||||
|
||||
impl ExtensionSlashCommandProxy for ExtensionHostProxy {
|
||||
fn register_slash_command(&self, extension: Arc<dyn Extension>, command: SlashCommand) {
|
||||
let Some(proxy) = self.slash_command_proxy.read().clone() else {
|
||||
return;
|
||||
};
|
||||
|
||||
proxy.register_slash_command(extension, command)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ExtensionContextServerProxy: Send + Sync + 'static {
|
||||
fn register_context_server(
|
||||
&self,
|
||||
extension: Arc<dyn Extension>,
|
||||
server_id: Arc<str>,
|
||||
cx: &mut AppContext,
|
||||
);
|
||||
}
|
||||
|
||||
impl ExtensionContextServerProxy for ExtensionHostProxy {
|
||||
fn register_context_server(
|
||||
&self,
|
||||
extension: Arc<dyn Extension>,
|
||||
server_id: Arc<str>,
|
||||
cx: &mut AppContext,
|
||||
) {
|
||||
let Some(proxy) = self.context_server_proxy.read().clone() else {
|
||||
return;
|
||||
};
|
||||
|
||||
proxy.register_context_server(extension, server_id, cx)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ExtensionIndexedDocsProviderProxy: Send + Sync + 'static {
|
||||
fn register_indexed_docs_provider(&self, extension: Arc<dyn Extension>, provider_id: Arc<str>);
|
||||
}
|
||||
|
||||
impl ExtensionIndexedDocsProviderProxy for ExtensionHostProxy {
|
||||
fn register_indexed_docs_provider(&self, extension: Arc<dyn Extension>, provider_id: Arc<str>) {
|
||||
let Some(proxy) = self.indexed_docs_provider_proxy.read().clone() else {
|
||||
return;
|
||||
};
|
||||
|
||||
proxy.register_indexed_docs_provider(extension, provider_id)
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ pub use slash_command::*;
|
||||
pub type EnvVars = Vec<(String, String)>;
|
||||
|
||||
/// A command.
|
||||
#[derive(Debug)]
|
||||
pub struct Command {
|
||||
/// The command to execute.
|
||||
pub command: String,
|
||||
|
||||
@@ -57,7 +57,9 @@ env_logger.workspace = true
|
||||
fs = { workspace = true, features = ["test-support"] }
|
||||
gpui = { workspace = true, features = ["test-support"] }
|
||||
language = { workspace = true, features = ["test-support"] }
|
||||
language_extension.workspace = true
|
||||
parking_lot.workspace = true
|
||||
project = { workspace = true, features = ["test-support"] }
|
||||
reqwest_client.workspace = true
|
||||
theme = { workspace = true, features = ["test-support"] }
|
||||
theme_extension.workspace = true
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
pub mod extension_lsp_adapter;
|
||||
pub mod extension_settings;
|
||||
pub mod headless_host;
|
||||
pub mod wasm_host;
|
||||
@@ -12,8 +11,12 @@ use async_tar::Archive;
|
||||
use client::{proto, telemetry::Telemetry, Client, ExtensionMetadata, GetExtensionsResponse};
|
||||
use collections::{btree_map, BTreeMap, HashMap, HashSet};
|
||||
use extension::extension_builder::{CompileExtensionOptions, ExtensionBuilder};
|
||||
use extension::Extension;
|
||||
pub use extension::ExtensionManifest;
|
||||
use extension::{
|
||||
ExtensionContextServerProxy, ExtensionGrammarProxy, ExtensionHostProxy,
|
||||
ExtensionIndexedDocsProviderProxy, ExtensionLanguageProxy, ExtensionLanguageServerProxy,
|
||||
ExtensionSlashCommandProxy, ExtensionSnippetProxy, ExtensionThemeProxy,
|
||||
};
|
||||
use fs::{Fs, RemoveOptions};
|
||||
use futures::{
|
||||
channel::{
|
||||
@@ -24,15 +27,14 @@ use futures::{
|
||||
select_biased, AsyncReadExt as _, Future, FutureExt as _, StreamExt as _,
|
||||
};
|
||||
use gpui::{
|
||||
actions, AppContext, AsyncAppContext, Context, EventEmitter, Global, Model, ModelContext,
|
||||
SharedString, Task, WeakModel,
|
||||
actions, AppContext, AsyncAppContext, Context, EventEmitter, Global, Model, ModelContext, Task,
|
||||
WeakModel,
|
||||
};
|
||||
use http_client::{AsyncBody, HttpClient, HttpClientWithUrl};
|
||||
use language::{
|
||||
LanguageConfig, LanguageMatcher, LanguageName, LanguageQueries, LoadedLanguage,
|
||||
QUERY_FILENAME_PREFIXES,
|
||||
};
|
||||
use lsp::LanguageServerName;
|
||||
use node_runtime::NodeRuntime;
|
||||
use project::ContextProviderWithTasks;
|
||||
use release_channel::ReleaseChannel;
|
||||
@@ -95,82 +97,8 @@ pub fn is_version_compatible(
|
||||
true
|
||||
}
|
||||
|
||||
pub trait ExtensionRegistrationHooks: Send + Sync + 'static {
|
||||
fn remove_user_themes(&self, _themes: Vec<SharedString>) {}
|
||||
|
||||
fn load_user_theme(&self, _theme_path: PathBuf, _fs: Arc<dyn Fs>) -> Task<Result<()>> {
|
||||
Task::ready(Ok(()))
|
||||
}
|
||||
|
||||
fn list_theme_names(
|
||||
&self,
|
||||
_theme_path: PathBuf,
|
||||
_fs: Arc<dyn Fs>,
|
||||
) -> Task<Result<Vec<String>>> {
|
||||
Task::ready(Ok(Vec::new()))
|
||||
}
|
||||
|
||||
fn reload_current_theme(&self, _cx: &mut AppContext) {}
|
||||
|
||||
fn register_language(
|
||||
&self,
|
||||
_language: LanguageName,
|
||||
_grammar: Option<Arc<str>>,
|
||||
_matcher: language::LanguageMatcher,
|
||||
_load: Arc<dyn Fn() -> Result<LoadedLanguage> + 'static + Send + Sync>,
|
||||
) {
|
||||
}
|
||||
|
||||
fn register_lsp_adapter(
|
||||
&self,
|
||||
_extension: Arc<dyn Extension>,
|
||||
_language_server_id: LanguageServerName,
|
||||
_language: LanguageName,
|
||||
) {
|
||||
}
|
||||
|
||||
fn remove_lsp_adapter(&self, _language: &LanguageName, _server_name: &LanguageServerName) {}
|
||||
|
||||
fn register_wasm_grammars(&self, _grammars: Vec<(Arc<str>, PathBuf)>) {}
|
||||
|
||||
fn remove_languages(
|
||||
&self,
|
||||
_languages_to_remove: &[LanguageName],
|
||||
_grammars_to_remove: &[Arc<str>],
|
||||
) {
|
||||
}
|
||||
|
||||
fn register_slash_command(
|
||||
&self,
|
||||
_extension: Arc<dyn Extension>,
|
||||
_command: extension::SlashCommand,
|
||||
) {
|
||||
}
|
||||
|
||||
fn register_context_server(
|
||||
&self,
|
||||
_id: Arc<str>,
|
||||
_extension: WasmExtension,
|
||||
_cx: &mut AppContext,
|
||||
) {
|
||||
}
|
||||
|
||||
fn register_docs_provider(&self, _extension: Arc<dyn Extension>, _provider_id: Arc<str>) {}
|
||||
|
||||
fn register_snippets(&self, _path: &PathBuf, _snippet_contents: &str) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn update_lsp_status(
|
||||
&self,
|
||||
_server_name: lsp::LanguageServerName,
|
||||
_status: language::LanguageServerBinaryStatus,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ExtensionStore {
|
||||
pub registration_hooks: Arc<dyn ExtensionRegistrationHooks>,
|
||||
pub proxy: Arc<ExtensionHostProxy>,
|
||||
pub builder: Arc<ExtensionBuilder>,
|
||||
pub extension_index: ExtensionIndex,
|
||||
pub fs: Arc<dyn Fs>,
|
||||
@@ -240,7 +168,7 @@ pub struct ExtensionIndexLanguageEntry {
|
||||
actions!(zed, [ReloadExtensions]);
|
||||
|
||||
pub fn init(
|
||||
registration_hooks: Arc<dyn ExtensionRegistrationHooks>,
|
||||
extension_host_proxy: Arc<ExtensionHostProxy>,
|
||||
fs: Arc<dyn Fs>,
|
||||
client: Arc<Client>,
|
||||
node_runtime: NodeRuntime,
|
||||
@@ -252,7 +180,7 @@ pub fn init(
|
||||
ExtensionStore::new(
|
||||
paths::extensions_dir().clone(),
|
||||
None,
|
||||
registration_hooks,
|
||||
extension_host_proxy,
|
||||
fs,
|
||||
client.http_client().clone(),
|
||||
client.http_client().clone(),
|
||||
@@ -284,7 +212,7 @@ impl ExtensionStore {
|
||||
pub fn new(
|
||||
extensions_dir: PathBuf,
|
||||
build_dir: Option<PathBuf>,
|
||||
extension_api: Arc<dyn ExtensionRegistrationHooks>,
|
||||
extension_host_proxy: Arc<ExtensionHostProxy>,
|
||||
fs: Arc<dyn Fs>,
|
||||
http_client: Arc<HttpClientWithUrl>,
|
||||
builder_client: Arc<dyn HttpClient>,
|
||||
@@ -300,7 +228,7 @@ impl ExtensionStore {
|
||||
let (reload_tx, mut reload_rx) = unbounded();
|
||||
let (connection_registered_tx, mut connection_registered_rx) = unbounded();
|
||||
let mut this = Self {
|
||||
registration_hooks: extension_api.clone(),
|
||||
proxy: extension_host_proxy.clone(),
|
||||
extension_index: Default::default(),
|
||||
installed_dir,
|
||||
index_path,
|
||||
@@ -312,7 +240,7 @@ impl ExtensionStore {
|
||||
fs.clone(),
|
||||
http_client.clone(),
|
||||
node_runtime,
|
||||
extension_api,
|
||||
extension_host_proxy,
|
||||
work_dir,
|
||||
cx,
|
||||
),
|
||||
@@ -1113,16 +1041,16 @@ impl ExtensionStore {
|
||||
grammars_to_remove.extend(extension.manifest.grammars.keys().cloned());
|
||||
for (language_server_name, config) in extension.manifest.language_servers.iter() {
|
||||
for language in config.languages() {
|
||||
self.registration_hooks
|
||||
.remove_lsp_adapter(&language, language_server_name);
|
||||
self.proxy
|
||||
.remove_language_server(&language, language_server_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.wasm_extensions
|
||||
.retain(|(extension, _)| !extensions_to_unload.contains(&extension.id));
|
||||
self.registration_hooks.remove_user_themes(themes_to_remove);
|
||||
self.registration_hooks
|
||||
self.proxy.remove_user_themes(themes_to_remove);
|
||||
self.proxy
|
||||
.remove_languages(&languages_to_remove, &grammars_to_remove);
|
||||
|
||||
let languages_to_add = new_index
|
||||
@@ -1157,8 +1085,7 @@ impl ExtensionStore {
|
||||
}));
|
||||
}
|
||||
|
||||
self.registration_hooks
|
||||
.register_wasm_grammars(grammars_to_add);
|
||||
self.proxy.register_grammars(grammars_to_add);
|
||||
|
||||
for (language_name, language) in languages_to_add {
|
||||
let mut language_path = self.installed_dir.clone();
|
||||
@@ -1166,7 +1093,7 @@ impl ExtensionStore {
|
||||
Path::new(language.extension.as_ref()),
|
||||
language.path.as_path(),
|
||||
]);
|
||||
self.registration_hooks.register_language(
|
||||
self.proxy.register_language(
|
||||
language_name.clone(),
|
||||
language.grammar.clone(),
|
||||
language.matcher.clone(),
|
||||
@@ -1196,7 +1123,7 @@ impl ExtensionStore {
|
||||
let fs = self.fs.clone();
|
||||
let wasm_host = self.wasm_host.clone();
|
||||
let root_dir = self.installed_dir.clone();
|
||||
let api = self.registration_hooks.clone();
|
||||
let proxy = self.proxy.clone();
|
||||
let extension_entries = extensions_to_load
|
||||
.iter()
|
||||
.filter_map(|name| new_index.extensions.get(name).cloned())
|
||||
@@ -1212,13 +1139,17 @@ impl ExtensionStore {
|
||||
let fs = fs.clone();
|
||||
async move {
|
||||
for theme_path in themes_to_add.into_iter() {
|
||||
api.load_user_theme(theme_path, fs.clone()).await.log_err();
|
||||
proxy
|
||||
.load_user_theme(theme_path, fs.clone())
|
||||
.await
|
||||
.log_err();
|
||||
}
|
||||
|
||||
for snippets_path in &snippets_to_add {
|
||||
if let Some(snippets_contents) = fs.load(snippets_path).await.log_err()
|
||||
{
|
||||
api.register_snippets(snippets_path, &snippets_contents)
|
||||
proxy
|
||||
.register_snippet(snippets_path, &snippets_contents)
|
||||
.log_err();
|
||||
}
|
||||
}
|
||||
@@ -1259,7 +1190,7 @@ impl ExtensionStore {
|
||||
|
||||
for (language_server_id, language_server_config) in &manifest.language_servers {
|
||||
for language in language_server_config.languages() {
|
||||
this.registration_hooks.register_lsp_adapter(
|
||||
this.proxy.register_language_server(
|
||||
extension.clone(),
|
||||
language_server_id.clone(),
|
||||
language.clone(),
|
||||
@@ -1268,7 +1199,7 @@ impl ExtensionStore {
|
||||
}
|
||||
|
||||
for (slash_command_name, slash_command) in &manifest.slash_commands {
|
||||
this.registration_hooks.register_slash_command(
|
||||
this.proxy.register_slash_command(
|
||||
extension.clone(),
|
||||
extension::SlashCommand {
|
||||
name: slash_command_name.to_string(),
|
||||
@@ -1283,21 +1214,18 @@ impl ExtensionStore {
|
||||
}
|
||||
|
||||
for (id, _context_server_entry) in &manifest.context_servers {
|
||||
this.registration_hooks.register_context_server(
|
||||
id.clone(),
|
||||
wasm_extension.clone(),
|
||||
cx,
|
||||
);
|
||||
this.proxy
|
||||
.register_context_server(extension.clone(), id.clone(), cx);
|
||||
}
|
||||
|
||||
for (provider_id, _provider) in &manifest.indexed_docs_providers {
|
||||
this.registration_hooks
|
||||
.register_docs_provider(extension.clone(), provider_id.clone());
|
||||
this.proxy
|
||||
.register_indexed_docs_provider(extension.clone(), provider_id.clone());
|
||||
}
|
||||
}
|
||||
|
||||
this.wasm_extensions.extend(wasm_extensions);
|
||||
this.registration_hooks.reload_current_theme(cx);
|
||||
this.proxy.reload_current_theme(cx);
|
||||
})
|
||||
.ok();
|
||||
})
|
||||
@@ -1308,7 +1236,7 @@ impl ExtensionStore {
|
||||
let work_dir = self.wasm_host.work_dir.clone();
|
||||
let extensions_dir = self.installed_dir.clone();
|
||||
let index_path = self.index_path.clone();
|
||||
let extension_api = self.registration_hooks.clone();
|
||||
let proxy = self.proxy.clone();
|
||||
cx.background_executor().spawn(async move {
|
||||
let start_time = Instant::now();
|
||||
let mut index = ExtensionIndex::default();
|
||||
@@ -1334,7 +1262,7 @@ impl ExtensionStore {
|
||||
fs.clone(),
|
||||
extension_dir,
|
||||
&mut index,
|
||||
extension_api.clone(),
|
||||
proxy.clone(),
|
||||
)
|
||||
.await
|
||||
.log_err();
|
||||
@@ -1357,7 +1285,7 @@ impl ExtensionStore {
|
||||
fs: Arc<dyn Fs>,
|
||||
extension_dir: PathBuf,
|
||||
index: &mut ExtensionIndex,
|
||||
extension_api: Arc<dyn ExtensionRegistrationHooks>,
|
||||
proxy: Arc<ExtensionHostProxy>,
|
||||
) -> Result<()> {
|
||||
let mut extension_manifest = ExtensionManifest::load(fs.clone(), &extension_dir).await?;
|
||||
let extension_id = extension_manifest.id.clone();
|
||||
@@ -1409,7 +1337,7 @@ impl ExtensionStore {
|
||||
continue;
|
||||
};
|
||||
|
||||
let Some(theme_families) = extension_api
|
||||
let Some(theme_families) = proxy
|
||||
.list_theme_names(theme_path.clone(), fs.clone())
|
||||
.await
|
||||
.log_err()
|
||||
|
||||
@@ -1,20 +1,16 @@
|
||||
use crate::extension_lsp_adapter::ExtensionLspAdapter;
|
||||
use crate::{
|
||||
Event, ExtensionIndex, ExtensionIndexEntry, ExtensionIndexLanguageEntry,
|
||||
ExtensionIndexThemeEntry, ExtensionManifest, ExtensionSettings, ExtensionStore,
|
||||
GrammarManifestEntry, SchemaVersion, RELOAD_DEBOUNCE_DURATION,
|
||||
};
|
||||
use anyhow::Result;
|
||||
use async_compression::futures::bufread::GzipEncoder;
|
||||
use collections::BTreeMap;
|
||||
use extension::Extension;
|
||||
use extension::ExtensionHostProxy;
|
||||
use fs::{FakeFs, Fs, RealFs};
|
||||
use futures::{io::BufReader, AsyncReadExt, StreamExt};
|
||||
use gpui::{BackgroundExecutor, Context, SemanticVersion, SharedString, Task, TestAppContext};
|
||||
use gpui::{Context, SemanticVersion, TestAppContext};
|
||||
use http_client::{FakeHttpClient, Response};
|
||||
use language::{
|
||||
LanguageMatcher, LanguageName, LanguageRegistry, LanguageServerBinaryStatus, LoadedLanguage,
|
||||
};
|
||||
use language::{LanguageMatcher, LanguageRegistry, LanguageServerBinaryStatus};
|
||||
use lsp::LanguageServerName;
|
||||
use node_runtime::NodeRuntime;
|
||||
use parking_lot::Mutex;
|
||||
@@ -31,91 +27,6 @@ use std::{
|
||||
use theme::ThemeRegistry;
|
||||
use util::test::temp_tree;
|
||||
|
||||
use crate::ExtensionRegistrationHooks;
|
||||
|
||||
struct TestExtensionRegistrationHooks {
|
||||
executor: BackgroundExecutor,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
theme_registry: Arc<ThemeRegistry>,
|
||||
}
|
||||
|
||||
impl ExtensionRegistrationHooks for TestExtensionRegistrationHooks {
|
||||
fn list_theme_names(&self, path: PathBuf, fs: Arc<dyn Fs>) -> Task<Result<Vec<String>>> {
|
||||
self.executor.spawn(async move {
|
||||
let themes = theme::read_user_theme(&path, fs).await?;
|
||||
Ok(themes.themes.into_iter().map(|theme| theme.name).collect())
|
||||
})
|
||||
}
|
||||
|
||||
fn load_user_theme(&self, theme_path: PathBuf, fs: Arc<dyn fs::Fs>) -> Task<Result<()>> {
|
||||
let theme_registry = self.theme_registry.clone();
|
||||
self.executor
|
||||
.spawn(async move { theme_registry.load_user_theme(&theme_path, fs).await })
|
||||
}
|
||||
|
||||
fn remove_user_themes(&self, themes: Vec<SharedString>) {
|
||||
self.theme_registry.remove_user_themes(&themes);
|
||||
}
|
||||
|
||||
fn register_language(
|
||||
&self,
|
||||
language: language::LanguageName,
|
||||
grammar: Option<Arc<str>>,
|
||||
matcher: language::LanguageMatcher,
|
||||
load: Arc<dyn Fn() -> Result<LoadedLanguage> + 'static + Send + Sync>,
|
||||
) {
|
||||
self.language_registry
|
||||
.register_language(language, grammar, matcher, load)
|
||||
}
|
||||
|
||||
fn remove_languages(
|
||||
&self,
|
||||
languages_to_remove: &[language::LanguageName],
|
||||
grammars_to_remove: &[Arc<str>],
|
||||
) {
|
||||
self.language_registry
|
||||
.remove_languages(&languages_to_remove, &grammars_to_remove);
|
||||
}
|
||||
|
||||
fn register_wasm_grammars(&self, grammars: Vec<(Arc<str>, PathBuf)>) {
|
||||
self.language_registry.register_wasm_grammars(grammars)
|
||||
}
|
||||
|
||||
fn register_lsp_adapter(
|
||||
&self,
|
||||
extension: Arc<dyn Extension>,
|
||||
language_server_id: LanguageServerName,
|
||||
language: LanguageName,
|
||||
) {
|
||||
self.language_registry.register_lsp_adapter(
|
||||
language.clone(),
|
||||
Arc::new(ExtensionLspAdapter::new(
|
||||
extension,
|
||||
language_server_id,
|
||||
language,
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
||||
fn update_lsp_status(
|
||||
&self,
|
||||
server_name: lsp::LanguageServerName,
|
||||
status: LanguageServerBinaryStatus,
|
||||
) {
|
||||
self.language_registry
|
||||
.update_lsp_status(server_name, status);
|
||||
}
|
||||
|
||||
fn remove_lsp_adapter(
|
||||
&self,
|
||||
language_name: &language::LanguageName,
|
||||
server_name: &lsp::LanguageServerName,
|
||||
) {
|
||||
self.language_registry
|
||||
.remove_lsp_adapter(language_name, server_name);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[ctor::ctor]
|
||||
fn init_logger() {
|
||||
@@ -347,20 +258,18 @@ async fn test_extension_store(cx: &mut TestAppContext) {
|
||||
.collect(),
|
||||
};
|
||||
|
||||
let language_registry = Arc::new(LanguageRegistry::test(cx.executor()));
|
||||
let proxy = Arc::new(ExtensionHostProxy::new());
|
||||
let theme_registry = Arc::new(ThemeRegistry::new(Box::new(())));
|
||||
let registration_hooks = Arc::new(TestExtensionRegistrationHooks {
|
||||
executor: cx.executor(),
|
||||
language_registry: language_registry.clone(),
|
||||
theme_registry: theme_registry.clone(),
|
||||
});
|
||||
theme_extension::init(proxy.clone(), theme_registry.clone(), cx.executor());
|
||||
let language_registry = Arc::new(LanguageRegistry::test(cx.executor()));
|
||||
language_extension::init(proxy.clone(), language_registry.clone());
|
||||
let node_runtime = NodeRuntime::unavailable();
|
||||
|
||||
let store = cx.new_model(|cx| {
|
||||
ExtensionStore::new(
|
||||
PathBuf::from("/the-extension-dir"),
|
||||
None,
|
||||
registration_hooks.clone(),
|
||||
proxy.clone(),
|
||||
fs.clone(),
|
||||
http_client.clone(),
|
||||
http_client.clone(),
|
||||
@@ -485,7 +394,7 @@ async fn test_extension_store(cx: &mut TestAppContext) {
|
||||
ExtensionStore::new(
|
||||
PathBuf::from("/the-extension-dir"),
|
||||
None,
|
||||
registration_hooks,
|
||||
proxy,
|
||||
fs.clone(),
|
||||
http_client.clone(),
|
||||
http_client.clone(),
|
||||
@@ -568,13 +477,11 @@ async fn test_extension_store_with_test_extension(cx: &mut TestAppContext) {
|
||||
|
||||
let project = Project::test(fs.clone(), [project_dir.as_path()], cx).await;
|
||||
|
||||
let language_registry = project.read_with(cx, |project, _cx| project.languages().clone());
|
||||
let proxy = Arc::new(ExtensionHostProxy::new());
|
||||
let theme_registry = Arc::new(ThemeRegistry::new(Box::new(())));
|
||||
let registration_hooks = Arc::new(TestExtensionRegistrationHooks {
|
||||
executor: cx.executor(),
|
||||
language_registry: language_registry.clone(),
|
||||
theme_registry: theme_registry.clone(),
|
||||
});
|
||||
theme_extension::init(proxy.clone(), theme_registry.clone(), cx.executor());
|
||||
let language_registry = project.read_with(cx, |project, _cx| project.languages().clone());
|
||||
language_extension::init(proxy.clone(), language_registry.clone());
|
||||
let node_runtime = NodeRuntime::unavailable();
|
||||
|
||||
let mut status_updates = language_registry.language_server_binary_statuses();
|
||||
@@ -668,7 +575,7 @@ async fn test_extension_store_with_test_extension(cx: &mut TestAppContext) {
|
||||
ExtensionStore::new(
|
||||
extensions_dir.clone(),
|
||||
Some(cache_dir),
|
||||
registration_hooks,
|
||||
proxy,
|
||||
fs.clone(),
|
||||
extension_client.clone(),
|
||||
builder_client,
|
||||
|
||||
@@ -3,29 +3,18 @@ use std::{path::PathBuf, sync::Arc};
|
||||
use anyhow::{anyhow, Context as _, Result};
|
||||
use client::{proto, TypedEnvelope};
|
||||
use collections::{HashMap, HashSet};
|
||||
use extension::{Extension, ExtensionManifest};
|
||||
use extension::{
|
||||
Extension, ExtensionHostProxy, ExtensionLanguageProxy, ExtensionLanguageServerProxy,
|
||||
ExtensionManifest,
|
||||
};
|
||||
use fs::{Fs, RemoveOptions, RenameOptions};
|
||||
use gpui::{AppContext, AsyncAppContext, Context, Model, ModelContext, Task, WeakModel};
|
||||
use http_client::HttpClient;
|
||||
use language::{LanguageConfig, LanguageName, LanguageQueries, LanguageRegistry, LoadedLanguage};
|
||||
use language::{LanguageConfig, LanguageName, LanguageQueries, LoadedLanguage};
|
||||
use lsp::LanguageServerName;
|
||||
use node_runtime::NodeRuntime;
|
||||
|
||||
use crate::{
|
||||
extension_lsp_adapter::ExtensionLspAdapter,
|
||||
wasm_host::{WasmExtension, WasmHost},
|
||||
ExtensionRegistrationHooks,
|
||||
};
|
||||
|
||||
pub struct HeadlessExtensionStore {
|
||||
pub registration_hooks: Arc<dyn ExtensionRegistrationHooks>,
|
||||
pub fs: Arc<dyn Fs>,
|
||||
pub extension_dir: PathBuf,
|
||||
pub wasm_host: Arc<WasmHost>,
|
||||
pub loaded_extensions: HashMap<Arc<str>, Arc<str>>,
|
||||
pub loaded_languages: HashMap<Arc<str>, Vec<LanguageName>>,
|
||||
pub loaded_language_servers: HashMap<Arc<str>, Vec<(LanguageServerName, LanguageName)>>,
|
||||
}
|
||||
use crate::wasm_host::{WasmExtension, WasmHost};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ExtensionVersion {
|
||||
@@ -34,28 +23,37 @@ pub struct ExtensionVersion {
|
||||
pub dev: bool,
|
||||
}
|
||||
|
||||
pub struct HeadlessExtensionStore {
|
||||
pub fs: Arc<dyn Fs>,
|
||||
pub extension_dir: PathBuf,
|
||||
pub proxy: Arc<ExtensionHostProxy>,
|
||||
pub wasm_host: Arc<WasmHost>,
|
||||
pub loaded_extensions: HashMap<Arc<str>, Arc<str>>,
|
||||
pub loaded_languages: HashMap<Arc<str>, Vec<LanguageName>>,
|
||||
pub loaded_language_servers: HashMap<Arc<str>, Vec<(LanguageServerName, LanguageName)>>,
|
||||
}
|
||||
|
||||
impl HeadlessExtensionStore {
|
||||
pub fn new(
|
||||
fs: Arc<dyn Fs>,
|
||||
http_client: Arc<dyn HttpClient>,
|
||||
languages: Arc<LanguageRegistry>,
|
||||
extension_dir: PathBuf,
|
||||
extension_host_proxy: Arc<ExtensionHostProxy>,
|
||||
node_runtime: NodeRuntime,
|
||||
cx: &mut AppContext,
|
||||
) -> Model<Self> {
|
||||
let registration_hooks = Arc::new(HeadlessRegistrationHooks::new(languages.clone()));
|
||||
cx.new_model(|cx| Self {
|
||||
registration_hooks: registration_hooks.clone(),
|
||||
fs: fs.clone(),
|
||||
wasm_host: WasmHost::new(
|
||||
fs.clone(),
|
||||
http_client.clone(),
|
||||
node_runtime,
|
||||
registration_hooks,
|
||||
extension_host_proxy.clone(),
|
||||
extension_dir.join("work"),
|
||||
cx,
|
||||
),
|
||||
extension_dir,
|
||||
proxy: extension_host_proxy,
|
||||
loaded_extensions: Default::default(),
|
||||
loaded_languages: Default::default(),
|
||||
loaded_language_servers: Default::default(),
|
||||
@@ -154,7 +152,7 @@ impl HeadlessExtensionStore {
|
||||
|
||||
config.grammar = None;
|
||||
|
||||
this.registration_hooks.register_language(
|
||||
this.proxy.register_language(
|
||||
config.name.clone(),
|
||||
None,
|
||||
config.matcher.clone(),
|
||||
@@ -184,7 +182,7 @@ impl HeadlessExtensionStore {
|
||||
.entry(manifest.id.clone())
|
||||
.or_default()
|
||||
.push((language_server_id.clone(), language.clone()));
|
||||
this.registration_hooks.register_lsp_adapter(
|
||||
this.proxy.register_language_server(
|
||||
wasm_extension.clone(),
|
||||
language_server_id.clone(),
|
||||
language.clone(),
|
||||
@@ -202,19 +200,20 @@ impl HeadlessExtensionStore {
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
self.loaded_extensions.remove(extension_id);
|
||||
|
||||
let languages_to_remove = self
|
||||
.loaded_languages
|
||||
.remove(extension_id)
|
||||
.unwrap_or_default();
|
||||
self.registration_hooks
|
||||
.remove_languages(&languages_to_remove, &[]);
|
||||
self.proxy.remove_languages(&languages_to_remove, &[]);
|
||||
|
||||
for (language_server_name, language) in self
|
||||
.loaded_language_servers
|
||||
.remove(extension_id)
|
||||
.unwrap_or_default()
|
||||
{
|
||||
self.registration_hooks
|
||||
.remove_lsp_adapter(&language, &language_server_name);
|
||||
self.proxy
|
||||
.remove_language_server(&language, &language_server_name);
|
||||
}
|
||||
|
||||
let path = self.extension_dir.join(&extension_id.to_string());
|
||||
@@ -318,71 +317,3 @@ impl HeadlessExtensionStore {
|
||||
Ok(proto::Ack {})
|
||||
}
|
||||
}
|
||||
|
||||
struct HeadlessRegistrationHooks {
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
}
|
||||
|
||||
impl HeadlessRegistrationHooks {
|
||||
fn new(language_registry: Arc<LanguageRegistry>) -> Self {
|
||||
Self { language_registry }
|
||||
}
|
||||
}
|
||||
|
||||
impl ExtensionRegistrationHooks for HeadlessRegistrationHooks {
|
||||
fn register_language(
|
||||
&self,
|
||||
language: LanguageName,
|
||||
_grammar: Option<Arc<str>>,
|
||||
matcher: language::LanguageMatcher,
|
||||
load: Arc<dyn Fn() -> Result<LoadedLanguage> + 'static + Send + Sync>,
|
||||
) {
|
||||
log::info!("registering language: {:?}", language);
|
||||
self.language_registry
|
||||
.register_language(language, None, matcher, load)
|
||||
}
|
||||
|
||||
fn register_lsp_adapter(
|
||||
&self,
|
||||
extension: Arc<dyn Extension>,
|
||||
language_server_id: LanguageServerName,
|
||||
language: LanguageName,
|
||||
) {
|
||||
log::info!("registering lsp adapter {:?}", language);
|
||||
self.language_registry.register_lsp_adapter(
|
||||
language.clone(),
|
||||
Arc::new(ExtensionLspAdapter::new(
|
||||
extension,
|
||||
language_server_id,
|
||||
language,
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
||||
fn register_wasm_grammars(&self, grammars: Vec<(Arc<str>, PathBuf)>) {
|
||||
self.language_registry.register_wasm_grammars(grammars)
|
||||
}
|
||||
|
||||
fn remove_lsp_adapter(&self, language: &LanguageName, server_name: &LanguageServerName) {
|
||||
self.language_registry
|
||||
.remove_lsp_adapter(language, server_name)
|
||||
}
|
||||
|
||||
fn remove_languages(
|
||||
&self,
|
||||
languages_to_remove: &[LanguageName],
|
||||
_grammars_to_remove: &[Arc<str>],
|
||||
) {
|
||||
self.language_registry
|
||||
.remove_languages(languages_to_remove, &[])
|
||||
}
|
||||
|
||||
fn update_lsp_status(
|
||||
&self,
|
||||
server_name: LanguageServerName,
|
||||
status: language::LanguageServerBinaryStatus,
|
||||
) {
|
||||
self.language_registry
|
||||
.update_lsp_status(server_name, status)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
pub mod wit;
|
||||
|
||||
use crate::{ExtensionManifest, ExtensionRegistrationHooks};
|
||||
use crate::ExtensionManifest;
|
||||
use anyhow::{anyhow, bail, Context as _, Result};
|
||||
use async_trait::async_trait;
|
||||
use extension::{
|
||||
CodeLabel, Command, Completion, KeyValueStoreDelegate, SlashCommand,
|
||||
SlashCommandArgumentCompletion, SlashCommandOutput, Symbol, WorktreeDelegate,
|
||||
CodeLabel, Command, Completion, ExtensionHostProxy, KeyValueStoreDelegate, ProjectDelegate,
|
||||
SlashCommand, SlashCommandArgumentCompletion, SlashCommandOutput, Symbol, WorktreeDelegate,
|
||||
};
|
||||
use fs::{normalize_path, Fs};
|
||||
use futures::future::LocalBoxFuture;
|
||||
@@ -34,14 +34,13 @@ use wasmtime::{
|
||||
};
|
||||
use wasmtime_wasi::{self as wasi, WasiView};
|
||||
use wit::Extension;
|
||||
pub use wit::ExtensionProject;
|
||||
|
||||
pub struct WasmHost {
|
||||
engine: Engine,
|
||||
release_channel: ReleaseChannel,
|
||||
http_client: Arc<dyn HttpClient>,
|
||||
node_runtime: NodeRuntime,
|
||||
pub registration_hooks: Arc<dyn ExtensionRegistrationHooks>,
|
||||
pub(crate) proxy: Arc<ExtensionHostProxy>,
|
||||
fs: Arc<dyn Fs>,
|
||||
pub work_dir: PathBuf,
|
||||
_main_thread_message_task: Task<()>,
|
||||
@@ -238,6 +237,25 @@ impl extension::Extension for WasmExtension {
|
||||
.await
|
||||
}
|
||||
|
||||
async fn context_server_command(
|
||||
&self,
|
||||
context_server_id: Arc<str>,
|
||||
project: Arc<dyn ProjectDelegate>,
|
||||
) -> Result<Command> {
|
||||
self.call(|extension, store| {
|
||||
async move {
|
||||
let project_resource = store.data_mut().table().push(project)?;
|
||||
let command = extension
|
||||
.call_context_server_command(store, context_server_id.clone(), project_resource)
|
||||
.await?
|
||||
.map_err(|err| anyhow!("{err}"))?;
|
||||
anyhow::Ok(command.into())
|
||||
}
|
||||
.boxed()
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
async fn suggest_docs_packages(&self, provider: Arc<str>) -> Result<Vec<String>> {
|
||||
self.call(|extension, store| {
|
||||
async move {
|
||||
@@ -312,7 +330,7 @@ impl WasmHost {
|
||||
fs: Arc<dyn Fs>,
|
||||
http_client: Arc<dyn HttpClient>,
|
||||
node_runtime: NodeRuntime,
|
||||
registration_hooks: Arc<dyn ExtensionRegistrationHooks>,
|
||||
proxy: Arc<ExtensionHostProxy>,
|
||||
work_dir: PathBuf,
|
||||
cx: &mut AppContext,
|
||||
) -> Arc<Self> {
|
||||
@@ -328,7 +346,7 @@ impl WasmHost {
|
||||
work_dir,
|
||||
http_client,
|
||||
node_runtime,
|
||||
registration_hooks,
|
||||
proxy,
|
||||
release_channel: ReleaseChannel::global(cx),
|
||||
_main_thread_message_task: task,
|
||||
main_thread_message_tx: tx,
|
||||
|
||||
@@ -3,7 +3,7 @@ use crate::wasm_host::wit::since_v0_0_4;
|
||||
use crate::wasm_host::WasmState;
|
||||
use anyhow::Result;
|
||||
use async_trait::async_trait;
|
||||
use extension::WorktreeDelegate;
|
||||
use extension::{ExtensionLanguageServerProxy, WorktreeDelegate};
|
||||
use language::LanguageServerBinaryStatus;
|
||||
use semantic_version::SemanticVersion;
|
||||
use std::sync::{Arc, OnceLock};
|
||||
@@ -149,8 +149,9 @@ impl ExtensionImports for WasmState {
|
||||
};
|
||||
|
||||
self.host
|
||||
.registration_hooks
|
||||
.update_lsp_status(lsp::LanguageServerName(server_name.into()), status);
|
||||
.proxy
|
||||
.update_language_server_status(lsp::LanguageServerName(server_name.into()), status);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ use anyhow::{anyhow, bail, Context, Result};
|
||||
use async_compression::futures::bufread::GzipDecoder;
|
||||
use async_tar::Archive;
|
||||
use async_trait::async_trait;
|
||||
use extension::{KeyValueStoreDelegate, WorktreeDelegate};
|
||||
use extension::{ExtensionLanguageServerProxy, KeyValueStoreDelegate, WorktreeDelegate};
|
||||
use futures::{io::BufReader, FutureExt as _};
|
||||
use futures::{lock::Mutex, AsyncReadExt};
|
||||
use language::LanguageName;
|
||||
@@ -495,8 +495,9 @@ impl ExtensionImports for WasmState {
|
||||
};
|
||||
|
||||
self.host
|
||||
.registration_hooks
|
||||
.update_lsp_status(::lsp::LanguageServerName(server_name.into()), status);
|
||||
.proxy
|
||||
.update_language_server_status(::lsp::LanguageServerName(server_name.into()), status);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,9 @@ use async_compression::futures::bufread::GzipDecoder;
|
||||
use async_tar::Archive;
|
||||
use async_trait::async_trait;
|
||||
use context_servers::manager::ContextServerSettings;
|
||||
use extension::{KeyValueStoreDelegate, WorktreeDelegate};
|
||||
use extension::{
|
||||
ExtensionLanguageServerProxy, KeyValueStoreDelegate, ProjectDelegate, WorktreeDelegate,
|
||||
};
|
||||
use futures::{io::BufReader, FutureExt as _};
|
||||
use futures::{lock::Mutex, AsyncReadExt};
|
||||
use language::{language_settings::AllLanguageSettings, LanguageName, LanguageServerBinaryStatus};
|
||||
@@ -44,13 +46,10 @@ 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 struct ExtensionProject {
|
||||
pub worktree_ids: Vec<u64>,
|
||||
}
|
||||
|
||||
pub fn linker() -> &'static Linker<WasmState> {
|
||||
static LINKER: OnceLock<Linker<WasmState>> = OnceLock::new();
|
||||
LINKER.get_or_init(|| super::new_linker(Extension::add_to_linker))
|
||||
@@ -273,7 +272,7 @@ impl HostProject for WasmState {
|
||||
project: Resource<ExtensionProject>,
|
||||
) -> wasmtime::Result<Vec<u64>> {
|
||||
let project = self.table.get(&project)?;
|
||||
Ok(project.worktree_ids.clone())
|
||||
Ok(project.worktree_ids())
|
||||
}
|
||||
|
||||
fn drop(&mut self, _project: Resource<Project>) -> Result<()> {
|
||||
@@ -685,8 +684,9 @@ impl ExtensionImports for WasmState {
|
||||
};
|
||||
|
||||
self.host
|
||||
.registration_hooks
|
||||
.update_lsp_status(::lsp::LanguageServerName(server_name.into()), status);
|
||||
.proxy
|
||||
.update_language_server_status(::lsp::LanguageServerName(server_name.into()), status);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -13,21 +13,15 @@ path = "src/extensions_ui.rs"
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
assistant_slash_command.workspace = true
|
||||
client.workspace = true
|
||||
collections.workspace = true
|
||||
context_servers.workspace = true
|
||||
db.workspace = true
|
||||
editor.workspace = true
|
||||
extension.workspace = true
|
||||
extension_host.workspace = true
|
||||
fs.workspace = true
|
||||
fuzzy.workspace = true
|
||||
gpui.workspace = true
|
||||
indexed_docs.workspace = true
|
||||
language.workspace = true
|
||||
log.workspace = true
|
||||
lsp.workspace = true
|
||||
num-format.workspace = true
|
||||
picker.workspace = true
|
||||
project.workspace = true
|
||||
@@ -36,12 +30,10 @@ semantic_version.workspace = true
|
||||
serde.workspace = true
|
||||
settings.workspace = true
|
||||
smallvec.workspace = true
|
||||
snippet_provider.workspace = true
|
||||
theme.workspace = true
|
||||
ui.workspace = true
|
||||
util.workspace = true
|
||||
vim_mode_setting.workspace = true
|
||||
wasmtime-wasi.workspace = true
|
||||
workspace.workspace = true
|
||||
zed_actions.workspace = true
|
||||
|
||||
|
||||
@@ -1,220 +0,0 @@
|
||||
use std::{path::PathBuf, sync::Arc};
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use assistant_slash_command::{ExtensionSlashCommand, SlashCommandRegistry};
|
||||
use context_servers::manager::ServerCommand;
|
||||
use context_servers::ContextServerFactoryRegistry;
|
||||
use db::smol::future::FutureExt as _;
|
||||
use extension::Extension;
|
||||
use extension_host::wasm_host::ExtensionProject;
|
||||
use extension_host::{extension_lsp_adapter::ExtensionLspAdapter, wasm_host};
|
||||
use fs::Fs;
|
||||
use gpui::{AppContext, BackgroundExecutor, Model, Task};
|
||||
use indexed_docs::{ExtensionIndexedDocsProvider, IndexedDocsRegistry, ProviderId};
|
||||
use language::{LanguageName, LanguageRegistry, LanguageServerBinaryStatus, LoadedLanguage};
|
||||
use lsp::LanguageServerName;
|
||||
use snippet_provider::SnippetRegistry;
|
||||
use theme::{ThemeRegistry, ThemeSettings};
|
||||
use ui::SharedString;
|
||||
use wasmtime_wasi::WasiView as _;
|
||||
|
||||
pub struct ConcreteExtensionRegistrationHooks {
|
||||
slash_command_registry: Arc<SlashCommandRegistry>,
|
||||
theme_registry: Arc<ThemeRegistry>,
|
||||
indexed_docs_registry: Arc<IndexedDocsRegistry>,
|
||||
snippet_registry: Arc<SnippetRegistry>,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
context_server_factory_registry: Model<ContextServerFactoryRegistry>,
|
||||
executor: BackgroundExecutor,
|
||||
}
|
||||
|
||||
impl ConcreteExtensionRegistrationHooks {
|
||||
pub fn new(
|
||||
theme_registry: Arc<ThemeRegistry>,
|
||||
slash_command_registry: Arc<SlashCommandRegistry>,
|
||||
indexed_docs_registry: Arc<IndexedDocsRegistry>,
|
||||
snippet_registry: Arc<SnippetRegistry>,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
context_server_factory_registry: Model<ContextServerFactoryRegistry>,
|
||||
cx: &AppContext,
|
||||
) -> Arc<dyn extension_host::ExtensionRegistrationHooks> {
|
||||
Arc::new(Self {
|
||||
theme_registry,
|
||||
slash_command_registry,
|
||||
indexed_docs_registry,
|
||||
snippet_registry,
|
||||
language_registry,
|
||||
context_server_factory_registry,
|
||||
executor: cx.background_executor().clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl extension_host::ExtensionRegistrationHooks for ConcreteExtensionRegistrationHooks {
|
||||
fn remove_user_themes(&self, themes: Vec<SharedString>) {
|
||||
self.theme_registry.remove_user_themes(&themes);
|
||||
}
|
||||
|
||||
fn load_user_theme(&self, theme_path: PathBuf, fs: Arc<dyn fs::Fs>) -> Task<Result<()>> {
|
||||
let theme_registry = self.theme_registry.clone();
|
||||
self.executor
|
||||
.spawn(async move { theme_registry.load_user_theme(&theme_path, fs).await })
|
||||
}
|
||||
|
||||
fn register_slash_command(
|
||||
&self,
|
||||
extension: Arc<dyn Extension>,
|
||||
command: extension::SlashCommand,
|
||||
) {
|
||||
self.slash_command_registry
|
||||
.register_command(ExtensionSlashCommand::new(extension, command), false)
|
||||
}
|
||||
|
||||
fn register_context_server(
|
||||
&self,
|
||||
id: Arc<str>,
|
||||
extension: wasm_host::WasmExtension,
|
||||
cx: &mut AppContext,
|
||||
) {
|
||||
self.context_server_factory_registry
|
||||
.update(cx, |registry, _| {
|
||||
registry.register_server_factory(
|
||||
id.clone(),
|
||||
Arc::new({
|
||||
move |project, cx| {
|
||||
log::info!(
|
||||
"loading command for context server {id} from extension {}",
|
||||
extension.manifest.id
|
||||
);
|
||||
|
||||
let id = id.clone();
|
||||
let extension = extension.clone();
|
||||
cx.spawn(|mut cx| async move {
|
||||
let extension_project =
|
||||
project.update(&mut cx, |project, cx| ExtensionProject {
|
||||
worktree_ids: project
|
||||
.visible_worktrees(cx)
|
||||
.map(|worktree| worktree.read(cx).id().to_proto())
|
||||
.collect(),
|
||||
})?;
|
||||
|
||||
let command = extension
|
||||
.call({
|
||||
let id = id.clone();
|
||||
|extension, store| {
|
||||
async move {
|
||||
let project = store
|
||||
.data_mut()
|
||||
.table()
|
||||
.push(extension_project)?;
|
||||
let command = extension
|
||||
.call_context_server_command(
|
||||
store,
|
||||
id.clone(),
|
||||
project,
|
||||
)
|
||||
.await?
|
||||
.map_err(|e| anyhow!("{}", e))?;
|
||||
anyhow::Ok(command)
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
})
|
||||
.await?;
|
||||
|
||||
log::info!("loaded command for context server {id}: {command:?}");
|
||||
|
||||
Ok(ServerCommand {
|
||||
path: command.command,
|
||||
args: command.args,
|
||||
env: Some(command.env.into_iter().collect()),
|
||||
})
|
||||
})
|
||||
}
|
||||
}),
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
fn register_docs_provider(&self, extension: Arc<dyn Extension>, provider_id: Arc<str>) {
|
||||
self.indexed_docs_registry
|
||||
.register_provider(Box::new(ExtensionIndexedDocsProvider::new(
|
||||
extension,
|
||||
ProviderId(provider_id),
|
||||
)));
|
||||
}
|
||||
|
||||
fn register_snippets(&self, path: &PathBuf, snippet_contents: &str) -> Result<()> {
|
||||
self.snippet_registry
|
||||
.register_snippets(path, snippet_contents)
|
||||
}
|
||||
|
||||
fn update_lsp_status(
|
||||
&self,
|
||||
server_name: lsp::LanguageServerName,
|
||||
status: LanguageServerBinaryStatus,
|
||||
) {
|
||||
self.language_registry
|
||||
.update_lsp_status(server_name, status);
|
||||
}
|
||||
|
||||
fn register_lsp_adapter(
|
||||
&self,
|
||||
extension: Arc<dyn Extension>,
|
||||
language_server_id: LanguageServerName,
|
||||
language: LanguageName,
|
||||
) {
|
||||
self.language_registry.register_lsp_adapter(
|
||||
language.clone(),
|
||||
Arc::new(ExtensionLspAdapter::new(
|
||||
extension,
|
||||
language_server_id,
|
||||
language,
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
||||
fn remove_lsp_adapter(
|
||||
&self,
|
||||
language_name: &language::LanguageName,
|
||||
server_name: &lsp::LanguageServerName,
|
||||
) {
|
||||
self.language_registry
|
||||
.remove_lsp_adapter(language_name, server_name);
|
||||
}
|
||||
|
||||
fn remove_languages(
|
||||
&self,
|
||||
languages_to_remove: &[language::LanguageName],
|
||||
grammars_to_remove: &[Arc<str>],
|
||||
) {
|
||||
self.language_registry
|
||||
.remove_languages(&languages_to_remove, &grammars_to_remove);
|
||||
}
|
||||
|
||||
fn register_wasm_grammars(&self, grammars: Vec<(Arc<str>, PathBuf)>) {
|
||||
self.language_registry.register_wasm_grammars(grammars)
|
||||
}
|
||||
|
||||
fn register_language(
|
||||
&self,
|
||||
language: language::LanguageName,
|
||||
grammar: Option<Arc<str>>,
|
||||
matcher: language::LanguageMatcher,
|
||||
load: Arc<dyn Fn() -> Result<LoadedLanguage> + 'static + Send + Sync>,
|
||||
) {
|
||||
self.language_registry
|
||||
.register_language(language, grammar, matcher, load)
|
||||
}
|
||||
|
||||
fn reload_current_theme(&self, cx: &mut AppContext) {
|
||||
ThemeSettings::reload_current_theme(cx)
|
||||
}
|
||||
|
||||
fn list_theme_names(&self, path: PathBuf, fs: Arc<dyn Fs>) -> Task<Result<Vec<String>>> {
|
||||
self.executor.spawn(async move {
|
||||
let themes = theme::read_user_theme(&path, fs).await?;
|
||||
Ok(themes.themes.into_iter().map(|theme| theme.name).collect())
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,7 @@
|
||||
mod components;
|
||||
mod extension_registration_hooks;
|
||||
mod extension_suggest;
|
||||
mod extension_version_selector;
|
||||
|
||||
pub use extension_registration_hooks::ConcreteExtensionRegistrationHooks;
|
||||
|
||||
use std::ops::DerefMut;
|
||||
use std::sync::OnceLock;
|
||||
use std::time::Duration;
|
||||
|
||||
@@ -71,8 +71,16 @@ impl Match {
|
||||
fn project_path(&self, project: &Project, cx: &WindowContext) -> Option<ProjectPath> {
|
||||
let worktree_id = if let Some(path_match) = &self.path_match {
|
||||
WorktreeId::from_usize(path_match.worktree_id)
|
||||
} else if let Some(worktree) = project.visible_worktrees(cx).find(|worktree| {
|
||||
worktree
|
||||
.read(cx)
|
||||
.root_entry()
|
||||
.is_some_and(|entry| entry.is_dir())
|
||||
}) {
|
||||
worktree.read(cx).id()
|
||||
} else {
|
||||
project.worktrees(cx).next()?.read(cx).id()
|
||||
// todo(): we should find_or_create a workspace.
|
||||
return None;
|
||||
};
|
||||
|
||||
let path = PathBuf::from(self.relative_path());
|
||||
|
||||
@@ -61,4 +61,4 @@ In addition to the systems above, GPUI provides a range of smaller services that
|
||||
|
||||
- The `[gpui::test]` macro provides a convenient way to write tests for your GPUI applications. Tests also have their own kind of context, a `TestAppContext` which provides ways of simulating common platform input. See `app::test_context` and `test` modules for more details.
|
||||
|
||||
Currently, the best way to learn about these APIs is to read the Zed source code, ask us about it at a fireside hack, or drop a question in the [Zed Discord](https://discord.gg/zed-community). We're working on improving the documentation, creating more examples, and will be publishing more guides to GPUI on our [blog](https://zed.dev/blog).
|
||||
Currently, the best way to learn about these APIs is to read the Zed source code, ask us about it at a fireside hack, or drop a question in the [Zed Discord](https://zed.dev/community-links). We're working on improving the documentation, creating more examples, and will be publishing more guides to GPUI on our [blog](https://zed.dev/blog).
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
//! and [`test`] modules for more details.
|
||||
//!
|
||||
//! Currently, the best way to learn about these APIs is to read the Zed source code, ask us about it at a fireside hack, or drop
|
||||
//! a question in the [Zed Discord](https://discord.gg/zed-community). We're working on improving the documentation, creating more examples,
|
||||
//! a question in the [Zed Discord](https://zed.dev/community-links). We're working on improving the documentation, creating more examples,
|
||||
//! and will be publishing more guides to GPUI on our [blog](https://zed.dev/blog).
|
||||
|
||||
#![deny(missing_docs)]
|
||||
|
||||
@@ -46,6 +46,7 @@ use smallvec::SmallVec;
|
||||
use std::borrow::Cow;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::io::Cursor;
|
||||
use std::ops;
|
||||
use std::time::{Duration, Instant};
|
||||
use std::{
|
||||
fmt::{self, Debug},
|
||||
@@ -561,6 +562,42 @@ pub(crate) trait PlatformAtlas: Send + Sync {
|
||||
key: &AtlasKey,
|
||||
build: &mut dyn FnMut() -> Result<Option<(Size<DevicePixels>, Cow<'a, [u8]>)>>,
|
||||
) -> Result<Option<AtlasTile>>;
|
||||
fn remove(&self, key: &AtlasKey);
|
||||
}
|
||||
|
||||
struct AtlasTextureList<T> {
|
||||
textures: Vec<Option<T>>,
|
||||
free_list: Vec<usize>,
|
||||
}
|
||||
|
||||
impl<T> Default for AtlasTextureList<T> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
textures: Vec::default(),
|
||||
free_list: Vec::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ops::Index<usize> for AtlasTextureList<T> {
|
||||
type Output = Option<T>;
|
||||
|
||||
fn index(&self, index: usize) -> &Self::Output {
|
||||
&self.textures[index]
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> AtlasTextureList<T> {
|
||||
#[allow(unused)]
|
||||
fn drain(&mut self) -> std::vec::Drain<Option<T>> {
|
||||
self.free_list.clear();
|
||||
self.textures.drain(..)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn iter_mut(&mut self) -> impl DoubleEndedIterator<Item = &mut T> {
|
||||
self.textures.iter_mut().flatten()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
AtlasKey, AtlasTextureId, AtlasTextureKind, AtlasTile, Bounds, DevicePixels, PlatformAtlas,
|
||||
Point, Size,
|
||||
platform::AtlasTextureList, AtlasKey, AtlasTextureId, AtlasTextureKind, AtlasTile, Bounds,
|
||||
DevicePixels, PlatformAtlas, Point, Size,
|
||||
};
|
||||
use anyhow::Result;
|
||||
use blade_graphics as gpu;
|
||||
@@ -67,7 +67,7 @@ impl BladeAtlas {
|
||||
pub(crate) fn clear_textures(&self, texture_kind: AtlasTextureKind) {
|
||||
let mut lock = self.0.lock();
|
||||
let textures = &mut lock.storage[texture_kind];
|
||||
for texture in textures {
|
||||
for texture in textures.iter_mut() {
|
||||
texture.clear();
|
||||
}
|
||||
}
|
||||
@@ -130,19 +130,48 @@ impl PlatformAtlas for BladeAtlas {
|
||||
Ok(Some(tile))
|
||||
}
|
||||
}
|
||||
|
||||
fn remove(&self, key: &AtlasKey) {
|
||||
let mut lock = self.0.lock();
|
||||
|
||||
let Some(id) = lock.tiles_by_key.remove(key).map(|tile| tile.texture_id) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(texture_slot) = lock.storage[id.kind].textures.get_mut(id.index as usize) else {
|
||||
return;
|
||||
};
|
||||
|
||||
if let Some(mut texture) = texture_slot.take() {
|
||||
texture.decrement_ref_count();
|
||||
if texture.is_unreferenced() {
|
||||
lock.storage[id.kind]
|
||||
.free_list
|
||||
.push(texture.id.index as usize);
|
||||
texture.destroy(&lock.gpu);
|
||||
} else {
|
||||
*texture_slot = Some(texture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BladeAtlasState {
|
||||
fn allocate(&mut self, size: Size<DevicePixels>, texture_kind: AtlasTextureKind) -> AtlasTile {
|
||||
let textures = &mut self.storage[texture_kind];
|
||||
textures
|
||||
.iter_mut()
|
||||
.rev()
|
||||
.find_map(|texture| texture.allocate(size))
|
||||
.unwrap_or_else(|| {
|
||||
let texture = self.push_texture(size, texture_kind);
|
||||
texture.allocate(size).unwrap()
|
||||
})
|
||||
{
|
||||
let textures = &mut self.storage[texture_kind];
|
||||
|
||||
if let Some(tile) = textures
|
||||
.iter_mut()
|
||||
.rev()
|
||||
.find_map(|texture| texture.allocate(size))
|
||||
{
|
||||
return tile;
|
||||
}
|
||||
}
|
||||
|
||||
let texture = self.push_texture(size, texture_kind);
|
||||
texture.allocate(size).unwrap()
|
||||
}
|
||||
|
||||
fn push_texture(
|
||||
@@ -198,21 +227,30 @@ impl BladeAtlasState {
|
||||
},
|
||||
);
|
||||
|
||||
let textures = &mut self.storage[kind];
|
||||
let texture_list = &mut self.storage[kind];
|
||||
let index = texture_list.free_list.pop();
|
||||
|
||||
let atlas_texture = BladeAtlasTexture {
|
||||
id: AtlasTextureId {
|
||||
index: textures.len() as u32,
|
||||
index: index.unwrap_or(texture_list.textures.len()) as u32,
|
||||
kind,
|
||||
},
|
||||
allocator: etagere::BucketedAtlasAllocator::new(size.into()),
|
||||
format,
|
||||
raw,
|
||||
raw_view,
|
||||
live_atlas_keys: 0,
|
||||
};
|
||||
|
||||
self.initializations.push(atlas_texture.id);
|
||||
textures.push(atlas_texture);
|
||||
textures.last_mut().unwrap()
|
||||
|
||||
if let Some(ix) = index {
|
||||
texture_list.textures[ix] = Some(atlas_texture);
|
||||
texture_list.textures.get_mut(ix).unwrap().as_mut().unwrap()
|
||||
} else {
|
||||
texture_list.textures.push(Some(atlas_texture));
|
||||
texture_list.textures.last_mut().unwrap().as_mut().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
fn upload_texture(&mut self, id: AtlasTextureId, bounds: Bounds<DevicePixels>, bytes: &[u8]) {
|
||||
@@ -258,13 +296,13 @@ impl BladeAtlasState {
|
||||
|
||||
#[derive(Default)]
|
||||
struct BladeAtlasStorage {
|
||||
monochrome_textures: Vec<BladeAtlasTexture>,
|
||||
polychrome_textures: Vec<BladeAtlasTexture>,
|
||||
path_textures: Vec<BladeAtlasTexture>,
|
||||
monochrome_textures: AtlasTextureList<BladeAtlasTexture>,
|
||||
polychrome_textures: AtlasTextureList<BladeAtlasTexture>,
|
||||
path_textures: AtlasTextureList<BladeAtlasTexture>,
|
||||
}
|
||||
|
||||
impl ops::Index<AtlasTextureKind> for BladeAtlasStorage {
|
||||
type Output = Vec<BladeAtlasTexture>;
|
||||
type Output = AtlasTextureList<BladeAtlasTexture>;
|
||||
fn index(&self, kind: AtlasTextureKind) -> &Self::Output {
|
||||
match kind {
|
||||
crate::AtlasTextureKind::Monochrome => &self.monochrome_textures,
|
||||
@@ -292,19 +330,19 @@ impl ops::Index<AtlasTextureId> for BladeAtlasStorage {
|
||||
crate::AtlasTextureKind::Polychrome => &self.polychrome_textures,
|
||||
crate::AtlasTextureKind::Path => &self.path_textures,
|
||||
};
|
||||
&textures[id.index as usize]
|
||||
textures[id.index as usize].as_ref().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl BladeAtlasStorage {
|
||||
fn destroy(&mut self, gpu: &gpu::Context) {
|
||||
for mut texture in self.monochrome_textures.drain(..) {
|
||||
for mut texture in self.monochrome_textures.drain().flatten() {
|
||||
texture.destroy(gpu);
|
||||
}
|
||||
for mut texture in self.polychrome_textures.drain(..) {
|
||||
for mut texture in self.polychrome_textures.drain().flatten() {
|
||||
texture.destroy(gpu);
|
||||
}
|
||||
for mut texture in self.path_textures.drain(..) {
|
||||
for mut texture in self.path_textures.drain().flatten() {
|
||||
texture.destroy(gpu);
|
||||
}
|
||||
}
|
||||
@@ -316,6 +354,7 @@ struct BladeAtlasTexture {
|
||||
raw: gpu::Texture,
|
||||
raw_view: gpu::TextureView,
|
||||
format: gpu::TextureFormat,
|
||||
live_atlas_keys: u32,
|
||||
}
|
||||
|
||||
impl BladeAtlasTexture {
|
||||
@@ -334,6 +373,7 @@ impl BladeAtlasTexture {
|
||||
size,
|
||||
},
|
||||
};
|
||||
self.live_atlas_keys += 1;
|
||||
Some(tile)
|
||||
}
|
||||
|
||||
@@ -345,6 +385,14 @@ impl BladeAtlasTexture {
|
||||
fn bytes_per_pixel(&self) -> u8 {
|
||||
self.format.block_info().size
|
||||
}
|
||||
|
||||
fn decrement_ref_count(&mut self) {
|
||||
self.live_atlas_keys -= 1;
|
||||
}
|
||||
|
||||
fn is_unreferenced(&mut self) -> bool {
|
||||
self.live_atlas_keys == 0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Size<DevicePixels>> for etagere::Size {
|
||||
|
||||
@@ -776,11 +776,11 @@ impl X11Client {
|
||||
},
|
||||
};
|
||||
let window = self.get_window(event.window)?;
|
||||
window.configure(bounds);
|
||||
window.configure(bounds).unwrap();
|
||||
}
|
||||
Event::PropertyNotify(event) => {
|
||||
let window = self.get_window(event.window)?;
|
||||
window.property_notify(event);
|
||||
window.property_notify(event).unwrap();
|
||||
}
|
||||
Event::FocusIn(event) => {
|
||||
let window = self.get_window(event.event)?;
|
||||
@@ -1258,11 +1258,9 @@ impl LinuxClient for X11Client {
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(root_id, _)| {
|
||||
Some(Rc::new(X11Display::new(
|
||||
&state.xcb_connection,
|
||||
state.scale_factor,
|
||||
root_id,
|
||||
)?) as Rc<dyn PlatformDisplay>)
|
||||
Some(Rc::new(
|
||||
X11Display::new(&state.xcb_connection, state.scale_factor, root_id).ok()?,
|
||||
) as Rc<dyn PlatformDisplay>)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
@@ -1283,11 +1281,9 @@ impl LinuxClient for X11Client {
|
||||
fn display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>> {
|
||||
let state = self.0.borrow();
|
||||
|
||||
Some(Rc::new(X11Display::new(
|
||||
&state.xcb_connection,
|
||||
state.scale_factor,
|
||||
id.0 as usize,
|
||||
)?))
|
||||
Some(Rc::new(
|
||||
X11Display::new(&state.xcb_connection, state.scale_factor, id.0 as usize).ok()?,
|
||||
))
|
||||
}
|
||||
|
||||
fn open_window(
|
||||
|
||||
@@ -13,12 +13,17 @@ pub(crate) struct X11Display {
|
||||
|
||||
impl X11Display {
|
||||
pub(crate) fn new(
|
||||
xc: &XCBConnection,
|
||||
xcb: &XCBConnection,
|
||||
scale_factor: f32,
|
||||
x_screen_index: usize,
|
||||
) -> Option<Self> {
|
||||
let screen = xc.setup().roots.get(x_screen_index).unwrap();
|
||||
Some(Self {
|
||||
) -> anyhow::Result<Self> {
|
||||
let Some(screen) = xcb.setup().roots.get(x_screen_index) else {
|
||||
return Err(anyhow::anyhow!(
|
||||
"No screen found with index {}",
|
||||
x_screen_index
|
||||
));
|
||||
};
|
||||
Ok(Self {
|
||||
x_screen_index,
|
||||
bounds: Bounds {
|
||||
origin: Default::default(),
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
AtlasKey, AtlasTextureId, AtlasTextureKind, AtlasTile, Bounds, DevicePixels, PlatformAtlas,
|
||||
Point, Size,
|
||||
platform::AtlasTextureList, AtlasKey, AtlasTextureId, AtlasTextureKind, AtlasTile, Bounds,
|
||||
DevicePixels, PlatformAtlas, Point, Size,
|
||||
};
|
||||
use anyhow::{anyhow, Result};
|
||||
use collections::FxHashMap;
|
||||
@@ -42,7 +42,7 @@ impl MetalAtlas {
|
||||
AtlasTextureKind::Polychrome => &mut lock.polychrome_textures,
|
||||
AtlasTextureKind::Path => &mut lock.path_textures,
|
||||
};
|
||||
for texture in textures {
|
||||
for texture in textures.iter_mut() {
|
||||
texture.clear();
|
||||
}
|
||||
}
|
||||
@@ -50,9 +50,9 @@ impl MetalAtlas {
|
||||
|
||||
struct MetalAtlasState {
|
||||
device: AssertSend<Device>,
|
||||
monochrome_textures: Vec<MetalAtlasTexture>,
|
||||
polychrome_textures: Vec<MetalAtlasTexture>,
|
||||
path_textures: Vec<MetalAtlasTexture>,
|
||||
monochrome_textures: AtlasTextureList<MetalAtlasTexture>,
|
||||
polychrome_textures: AtlasTextureList<MetalAtlasTexture>,
|
||||
path_textures: AtlasTextureList<MetalAtlasTexture>,
|
||||
tiles_by_key: FxHashMap<AtlasKey, AtlasTile>,
|
||||
}
|
||||
|
||||
@@ -78,6 +78,38 @@ impl PlatformAtlas for MetalAtlas {
|
||||
Ok(Some(tile))
|
||||
}
|
||||
}
|
||||
|
||||
fn remove(&self, key: &AtlasKey) {
|
||||
let mut lock = self.0.lock();
|
||||
let Some(id) = lock.tiles_by_key.get(key).map(|v| v.texture_id) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let textures = match id.kind {
|
||||
AtlasTextureKind::Monochrome => &mut lock.monochrome_textures,
|
||||
AtlasTextureKind::Polychrome => &mut lock.polychrome_textures,
|
||||
AtlasTextureKind::Path => &mut lock.polychrome_textures,
|
||||
};
|
||||
|
||||
let Some(texture_slot) = textures
|
||||
.textures
|
||||
.iter_mut()
|
||||
.find(|texture| texture.as_ref().is_some_and(|v| v.id == id))
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
if let Some(mut texture) = texture_slot.take() {
|
||||
texture.decrement_ref_count();
|
||||
|
||||
if texture.is_unreferenced() {
|
||||
textures.free_list.push(id.index as usize);
|
||||
lock.tiles_by_key.remove(key);
|
||||
} else {
|
||||
*texture_slot = Some(texture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MetalAtlasState {
|
||||
@@ -86,20 +118,24 @@ impl MetalAtlasState {
|
||||
size: Size<DevicePixels>,
|
||||
texture_kind: AtlasTextureKind,
|
||||
) -> Option<AtlasTile> {
|
||||
let textures = match texture_kind {
|
||||
AtlasTextureKind::Monochrome => &mut self.monochrome_textures,
|
||||
AtlasTextureKind::Polychrome => &mut self.polychrome_textures,
|
||||
AtlasTextureKind::Path => &mut self.path_textures,
|
||||
};
|
||||
{
|
||||
let textures = match texture_kind {
|
||||
AtlasTextureKind::Monochrome => &mut self.monochrome_textures,
|
||||
AtlasTextureKind::Polychrome => &mut self.polychrome_textures,
|
||||
AtlasTextureKind::Path => &mut self.path_textures,
|
||||
};
|
||||
|
||||
textures
|
||||
.iter_mut()
|
||||
.rev()
|
||||
.find_map(|texture| texture.allocate(size))
|
||||
.or_else(|| {
|
||||
let texture = self.push_texture(size, texture_kind);
|
||||
texture.allocate(size)
|
||||
})
|
||||
if let Some(tile) = textures
|
||||
.iter_mut()
|
||||
.rev()
|
||||
.find_map(|texture| texture.allocate(size))
|
||||
{
|
||||
return Some(tile);
|
||||
}
|
||||
}
|
||||
|
||||
let texture = self.push_texture(size, texture_kind);
|
||||
texture.allocate(size)
|
||||
}
|
||||
|
||||
fn push_texture(
|
||||
@@ -140,21 +176,31 @@ impl MetalAtlasState {
|
||||
texture_descriptor.set_usage(usage);
|
||||
let metal_texture = self.device.new_texture(&texture_descriptor);
|
||||
|
||||
let textures = match kind {
|
||||
let texture_list = match kind {
|
||||
AtlasTextureKind::Monochrome => &mut self.monochrome_textures,
|
||||
AtlasTextureKind::Polychrome => &mut self.polychrome_textures,
|
||||
AtlasTextureKind::Path => &mut self.path_textures,
|
||||
};
|
||||
|
||||
let index = texture_list.free_list.pop();
|
||||
|
||||
let atlas_texture = MetalAtlasTexture {
|
||||
id: AtlasTextureId {
|
||||
index: textures.len() as u32,
|
||||
index: index.unwrap_or(texture_list.textures.len()) as u32,
|
||||
kind,
|
||||
},
|
||||
allocator: etagere::BucketedAtlasAllocator::new(size.into()),
|
||||
metal_texture: AssertSend(metal_texture),
|
||||
live_atlas_keys: 0,
|
||||
};
|
||||
textures.push(atlas_texture);
|
||||
textures.last_mut().unwrap()
|
||||
|
||||
if let Some(ix) = index {
|
||||
texture_list.textures[ix] = Some(atlas_texture);
|
||||
texture_list.textures.get_mut(ix).unwrap().as_mut().unwrap()
|
||||
} else {
|
||||
texture_list.textures.push(Some(atlas_texture));
|
||||
texture_list.textures.last_mut().unwrap().as_mut().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
fn texture(&self, id: AtlasTextureId) -> &MetalAtlasTexture {
|
||||
@@ -163,7 +209,7 @@ impl MetalAtlasState {
|
||||
crate::AtlasTextureKind::Polychrome => &self.polychrome_textures,
|
||||
crate::AtlasTextureKind::Path => &self.path_textures,
|
||||
};
|
||||
&textures[id.index as usize]
|
||||
textures[id.index as usize].as_ref().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,6 +217,7 @@ struct MetalAtlasTexture {
|
||||
id: AtlasTextureId,
|
||||
allocator: BucketedAtlasAllocator,
|
||||
metal_texture: AssertSend<metal::Texture>,
|
||||
live_atlas_keys: u32,
|
||||
}
|
||||
|
||||
impl MetalAtlasTexture {
|
||||
@@ -189,6 +236,7 @@ impl MetalAtlasTexture {
|
||||
},
|
||||
padding: 0,
|
||||
};
|
||||
self.live_atlas_keys += 1;
|
||||
Some(tile)
|
||||
}
|
||||
|
||||
@@ -215,6 +263,14 @@ impl MetalAtlasTexture {
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn decrement_ref_count(&mut self) {
|
||||
self.live_atlas_keys -= 1;
|
||||
}
|
||||
|
||||
fn is_unreferenced(&mut self) -> bool {
|
||||
self.live_atlas_keys == 0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Size<DevicePixels>> for etagere::Size {
|
||||
|
||||
@@ -339,4 +339,9 @@ impl PlatformAtlas for TestAtlas {
|
||||
|
||||
Ok(Some(state.tiles[key].clone()))
|
||||
}
|
||||
|
||||
fn remove(&self, key: &AtlasKey) {
|
||||
let mut state = self.0.lock();
|
||||
state.tiles.remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2685,6 +2685,20 @@ impl<'a> WindowContext<'a> {
|
||||
});
|
||||
}
|
||||
|
||||
/// Removes an image from the sprite atlas.
|
||||
pub fn drop_image(&mut self, data: Arc<RenderImage>) -> Result<()> {
|
||||
for frame_index in 0..data.frame_count() {
|
||||
let params = RenderImageParams {
|
||||
image_id: data.id,
|
||||
frame_index,
|
||||
};
|
||||
|
||||
self.window.sprite_atlas.remove(¶ms.clone().into());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
/// Add a node to the layout tree for the current frame. Takes the `Style` of the element for which
|
||||
/// layout is being requested, along with the layout ids of any children. This method is called during
|
||||
|
||||
@@ -3,9 +3,33 @@ use std::sync::Arc;
|
||||
|
||||
use anyhow::Result;
|
||||
use async_trait::async_trait;
|
||||
use extension::Extension;
|
||||
use extension::{Extension, ExtensionHostProxy, ExtensionIndexedDocsProviderProxy};
|
||||
use gpui::AppContext;
|
||||
|
||||
use crate::{IndexedDocsDatabase, IndexedDocsProvider, PackageName, ProviderId};
|
||||
use crate::{
|
||||
IndexedDocsDatabase, IndexedDocsProvider, IndexedDocsRegistry, PackageName, ProviderId,
|
||||
};
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
let proxy = ExtensionHostProxy::default_global(cx);
|
||||
proxy.register_indexed_docs_provider_proxy(IndexedDocsRegistryProxy {
|
||||
indexed_docs_registry: IndexedDocsRegistry::global(cx),
|
||||
});
|
||||
}
|
||||
|
||||
struct IndexedDocsRegistryProxy {
|
||||
indexed_docs_registry: Arc<IndexedDocsRegistry>,
|
||||
}
|
||||
|
||||
impl ExtensionIndexedDocsProviderProxy for IndexedDocsRegistryProxy {
|
||||
fn register_indexed_docs_provider(&self, extension: Arc<dyn Extension>, provider_id: Arc<str>) {
|
||||
self.indexed_docs_registry
|
||||
.register_provider(Box::new(ExtensionIndexedDocsProvider::new(
|
||||
extension,
|
||||
ProviderId(provider_id),
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ExtensionIndexedDocsProvider {
|
||||
extension: Arc<dyn Extension>,
|
||||
|
||||
@@ -3,7 +3,14 @@ mod providers;
|
||||
mod registry;
|
||||
mod store;
|
||||
|
||||
use gpui::AppContext;
|
||||
|
||||
pub use crate::extension_indexed_docs_provider::ExtensionIndexedDocsProvider;
|
||||
pub use crate::providers::rustdoc::*;
|
||||
pub use crate::registry::*;
|
||||
pub use crate::store::*;
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
IndexedDocsRegistry::init_global(cx);
|
||||
extension_indexed_docs_provider::init(cx);
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ impl IndexedDocsRegistry {
|
||||
GlobalIndexedDocsRegistry::global(cx).0.clone()
|
||||
}
|
||||
|
||||
pub fn init_global(cx: &mut AppContext) {
|
||||
pub(crate) fn init_global(cx: &mut AppContext) {
|
||||
GlobalIndexedDocsRegistry::set_global(
|
||||
cx,
|
||||
GlobalIndexedDocsRegistry(Arc::new(Self::new(cx.background_executor().clone()))),
|
||||
|
||||
@@ -239,12 +239,7 @@ pub async fn parse_markdown_block(
|
||||
Event::Start(tag) => match tag {
|
||||
Tag::Paragraph => new_paragraph(text, &mut list_stack),
|
||||
|
||||
Tag::Heading {
|
||||
level: _,
|
||||
id: _,
|
||||
classes: _,
|
||||
attrs: _,
|
||||
} => {
|
||||
Tag::Heading { .. } => {
|
||||
new_paragraph(text, &mut list_stack);
|
||||
bold_depth += 1;
|
||||
}
|
||||
@@ -267,12 +262,7 @@ pub async fn parse_markdown_block(
|
||||
|
||||
Tag::Strikethrough => strikethrough_depth += 1,
|
||||
|
||||
Tag::Link {
|
||||
link_type: _,
|
||||
dest_url,
|
||||
title: _,
|
||||
id: _,
|
||||
} => link_url = Some(dest_url.to_string()),
|
||||
Tag::Link { dest_url, .. } => link_url = Some(dest_url.to_string()),
|
||||
|
||||
Tag::List(number) => {
|
||||
list_stack.push((number, false));
|
||||
|
||||
25
crates/language_extension/Cargo.toml
Normal file
25
crates/language_extension/Cargo.toml
Normal file
@@ -0,0 +1,25 @@
|
||||
[package]
|
||||
name = "language_extension"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[lib]
|
||||
path = "src/language_extension.rs"
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
async-trait.workspace = true
|
||||
collections.workspace = true
|
||||
extension.workspace = true
|
||||
futures.workspace = true
|
||||
gpui.workspace = true
|
||||
language.workspace = true
|
||||
lsp.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
util.workspace = true
|
||||
1
crates/language_extension/LICENSE-GPL
Symbolic link
1
crates/language_extension/LICENSE-GPL
Symbolic link
@@ -0,0 +1 @@
|
||||
../../LICENSE-GPL
|
||||
@@ -1,22 +1,28 @@
|
||||
use std::any::Any;
|
||||
use std::ops::Range;
|
||||
use std::path::PathBuf;
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use async_trait::async_trait;
|
||||
use collections::HashMap;
|
||||
use extension::{Extension, WorktreeDelegate};
|
||||
use extension::{Extension, ExtensionLanguageServerProxy, WorktreeDelegate};
|
||||
use futures::{Future, FutureExt};
|
||||
use gpui::AsyncAppContext;
|
||||
use language::{
|
||||
CodeLabel, HighlightId, Language, LanguageName, LanguageToolchainStore, LspAdapter,
|
||||
LspAdapterDelegate,
|
||||
CodeLabel, HighlightId, Language, LanguageName, LanguageServerBinaryStatus,
|
||||
LanguageToolchainStore, LspAdapter, LspAdapterDelegate,
|
||||
};
|
||||
use lsp::{CodeActionKind, LanguageServerBinary, LanguageServerBinaryOptions, LanguageServerName};
|
||||
use serde::Serialize;
|
||||
use serde_json::Value;
|
||||
use std::ops::Range;
|
||||
use std::{any::Any, path::PathBuf, pin::Pin, sync::Arc};
|
||||
use util::{maybe, ResultExt};
|
||||
|
||||
use crate::LanguageServerRegistryProxy;
|
||||
|
||||
/// An adapter that allows an [`LspAdapterDelegate`] to be used as a [`WorktreeDelegate`].
|
||||
pub struct WorktreeDelegateAdapter(pub Arc<dyn LspAdapterDelegate>);
|
||||
struct WorktreeDelegateAdapter(pub Arc<dyn LspAdapterDelegate>);
|
||||
|
||||
#[async_trait]
|
||||
impl WorktreeDelegate for WorktreeDelegateAdapter {
|
||||
@@ -44,14 +50,50 @@ impl WorktreeDelegate for WorktreeDelegateAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ExtensionLspAdapter {
|
||||
impl ExtensionLanguageServerProxy for LanguageServerRegistryProxy {
|
||||
fn register_language_server(
|
||||
&self,
|
||||
extension: Arc<dyn Extension>,
|
||||
language_server_id: LanguageServerName,
|
||||
language: LanguageName,
|
||||
) {
|
||||
self.language_registry.register_lsp_adapter(
|
||||
language.clone(),
|
||||
Arc::new(ExtensionLspAdapter::new(
|
||||
extension,
|
||||
language_server_id,
|
||||
language,
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
||||
fn remove_language_server(
|
||||
&self,
|
||||
language: &LanguageName,
|
||||
language_server_id: &LanguageServerName,
|
||||
) {
|
||||
self.language_registry
|
||||
.remove_lsp_adapter(language, language_server_id);
|
||||
}
|
||||
|
||||
fn update_language_server_status(
|
||||
&self,
|
||||
language_server_id: LanguageServerName,
|
||||
status: LanguageServerBinaryStatus,
|
||||
) {
|
||||
self.language_registry
|
||||
.update_lsp_status(language_server_id, status);
|
||||
}
|
||||
}
|
||||
|
||||
struct ExtensionLspAdapter {
|
||||
extension: Arc<dyn Extension>,
|
||||
language_server_id: LanguageServerName,
|
||||
language_name: LanguageName,
|
||||
}
|
||||
|
||||
impl ExtensionLspAdapter {
|
||||
pub fn new(
|
||||
fn new(
|
||||
extension: Arc<dyn Extension>,
|
||||
language_server_id: LanguageServerName,
|
||||
language_name: LanguageName,
|
||||
51
crates/language_extension/src/language_extension.rs
Normal file
51
crates/language_extension/src/language_extension.rs
Normal file
@@ -0,0 +1,51 @@
|
||||
mod extension_lsp_adapter;
|
||||
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::Result;
|
||||
use extension::{ExtensionGrammarProxy, ExtensionHostProxy, ExtensionLanguageProxy};
|
||||
use language::{LanguageMatcher, LanguageName, LanguageRegistry, LoadedLanguage};
|
||||
|
||||
pub fn init(
|
||||
extension_host_proxy: Arc<ExtensionHostProxy>,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
) {
|
||||
let language_server_registry_proxy = LanguageServerRegistryProxy { language_registry };
|
||||
extension_host_proxy.register_grammar_proxy(language_server_registry_proxy.clone());
|
||||
extension_host_proxy.register_language_proxy(language_server_registry_proxy.clone());
|
||||
extension_host_proxy.register_language_server_proxy(language_server_registry_proxy);
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct LanguageServerRegistryProxy {
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
}
|
||||
|
||||
impl ExtensionGrammarProxy for LanguageServerRegistryProxy {
|
||||
fn register_grammars(&self, grammars: Vec<(Arc<str>, PathBuf)>) {
|
||||
self.language_registry.register_wasm_grammars(grammars)
|
||||
}
|
||||
}
|
||||
|
||||
impl ExtensionLanguageProxy for LanguageServerRegistryProxy {
|
||||
fn register_language(
|
||||
&self,
|
||||
language: LanguageName,
|
||||
grammar: Option<Arc<str>>,
|
||||
matcher: LanguageMatcher,
|
||||
load: Arc<dyn Fn() -> Result<LoadedLanguage> + Send + Sync + 'static>,
|
||||
) {
|
||||
self.language_registry
|
||||
.register_language(language, grammar, matcher, load);
|
||||
}
|
||||
|
||||
fn remove_languages(
|
||||
&self,
|
||||
languages_to_remove: &[LanguageName],
|
||||
grammars_to_remove: &[Arc<str>],
|
||||
) {
|
||||
self.language_registry
|
||||
.remove_languages(&languages_to_remove, &grammars_to_remove);
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ use http_client::github::{latest_github_release, GitHubLspBinaryVersion};
|
||||
use language::{LanguageRegistry, LanguageToolchainStore, LspAdapter, LspAdapterDelegate};
|
||||
use lsp::{LanguageServerBinary, LanguageServerName};
|
||||
use node_runtime::NodeRuntime;
|
||||
use project::ContextProviderWithTasks;
|
||||
use project::{lsp_store::language_server_settings, ContextProviderWithTasks};
|
||||
use serde_json::{json, Value};
|
||||
use settings::{KeymapFile, SettingsJsonSchemaParams, SettingsStore};
|
||||
use smol::{
|
||||
@@ -25,7 +25,7 @@ use std::{
|
||||
sync::{Arc, OnceLock},
|
||||
};
|
||||
use task::{TaskTemplate, TaskTemplates, VariableName};
|
||||
use util::{fs::remove_matching, maybe, ResultExt};
|
||||
use util::{fs::remove_matching, maybe, merge_json_value_into, ResultExt};
|
||||
|
||||
const SERVER_PATH: &str =
|
||||
"node_modules/vscode-langservers-extracted/bin/vscode-json-language-server";
|
||||
@@ -194,15 +194,26 @@ impl LspAdapter for JsonLspAdapter {
|
||||
|
||||
async fn workspace_configuration(
|
||||
self: Arc<Self>,
|
||||
_: &Arc<dyn LspAdapterDelegate>,
|
||||
delegate: &Arc<dyn LspAdapterDelegate>,
|
||||
_: Arc<dyn LanguageToolchainStore>,
|
||||
cx: &mut AsyncAppContext,
|
||||
) -> Result<Value> {
|
||||
cx.update(|cx| {
|
||||
let mut config = cx.update(|cx| {
|
||||
self.workspace_config
|
||||
.get_or_init(|| Self::get_workspace_config(self.languages.language_names(), cx))
|
||||
.clone()
|
||||
})
|
||||
})?;
|
||||
|
||||
let project_options = cx.update(|cx| {
|
||||
language_server_settings(delegate.as_ref(), &self.name(), cx)
|
||||
.and_then(|s| s.settings.clone())
|
||||
})?;
|
||||
|
||||
if let Some(override_options) = project_options {
|
||||
merge_json_value_into(override_options, &mut config);
|
||||
}
|
||||
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
fn language_ids(&self) -> HashMap<String, String> {
|
||||
|
||||
@@ -13,7 +13,7 @@ pub enum ParsedMarkdownElement {
|
||||
BlockQuote(ParsedMarkdownBlockQuote),
|
||||
CodeBlock(ParsedMarkdownCodeBlock),
|
||||
/// A paragraph of text and other inline elements.
|
||||
Paragraph(ParsedMarkdownText),
|
||||
Paragraph(MarkdownParagraph),
|
||||
HorizontalRule(Range<usize>),
|
||||
}
|
||||
|
||||
@@ -25,7 +25,13 @@ impl ParsedMarkdownElement {
|
||||
Self::Table(table) => table.source_range.clone(),
|
||||
Self::BlockQuote(block_quote) => block_quote.source_range.clone(),
|
||||
Self::CodeBlock(code_block) => code_block.source_range.clone(),
|
||||
Self::Paragraph(text) => text.source_range.clone(),
|
||||
Self::Paragraph(text) => match &text[0] {
|
||||
MarkdownParagraphChunk::Text(t) => t.source_range.clone(),
|
||||
MarkdownParagraphChunk::Image(image) => match image {
|
||||
Image::Web { source_range, .. } => source_range.clone(),
|
||||
Image::Path { source_range, .. } => source_range.clone(),
|
||||
},
|
||||
},
|
||||
Self::HorizontalRule(range) => range.clone(),
|
||||
}
|
||||
}
|
||||
@@ -35,6 +41,15 @@ impl ParsedMarkdownElement {
|
||||
}
|
||||
}
|
||||
|
||||
pub type MarkdownParagraph = Vec<MarkdownParagraphChunk>;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
pub enum MarkdownParagraphChunk {
|
||||
Text(ParsedMarkdownText),
|
||||
Image(Image),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
pub struct ParsedMarkdown {
|
||||
@@ -73,7 +88,7 @@ pub struct ParsedMarkdownCodeBlock {
|
||||
pub struct ParsedMarkdownHeading {
|
||||
pub source_range: Range<usize>,
|
||||
pub level: HeadingLevel,
|
||||
pub contents: ParsedMarkdownText,
|
||||
pub contents: MarkdownParagraph,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
@@ -107,7 +122,7 @@ pub enum ParsedMarkdownTableAlignment {
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
pub struct ParsedMarkdownTableRow {
|
||||
pub children: Vec<ParsedMarkdownText>,
|
||||
pub children: Vec<MarkdownParagraph>,
|
||||
}
|
||||
|
||||
impl Default for ParsedMarkdownTableRow {
|
||||
@@ -123,7 +138,7 @@ impl ParsedMarkdownTableRow {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_children(children: Vec<ParsedMarkdownText>) -> Self {
|
||||
pub fn with_children(children: Vec<MarkdownParagraph>) -> Self {
|
||||
Self { children }
|
||||
}
|
||||
}
|
||||
@@ -135,7 +150,7 @@ pub struct ParsedMarkdownBlockQuote {
|
||||
pub children: Vec<ParsedMarkdownElement>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ParsedMarkdownText {
|
||||
/// Where the text is located in the source Markdown document.
|
||||
pub source_range: Range<usize>,
|
||||
@@ -266,10 +281,112 @@ impl Display for Link {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Link::Web { url } => write!(f, "{}", url),
|
||||
Link::Path {
|
||||
display_path,
|
||||
path: _,
|
||||
} => write!(f, "{}", display_path.display()),
|
||||
Link::Path { display_path, .. } => write!(f, "{}", display_path.display()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A Markdown Image
|
||||
#[derive(Debug, Clone)]
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
pub enum Image {
|
||||
Web {
|
||||
source_range: Range<usize>,
|
||||
/// The URL of the Image.
|
||||
url: String,
|
||||
/// Link URL if exists.
|
||||
link: Option<Link>,
|
||||
/// alt text if it exists
|
||||
alt_text: Option<ParsedMarkdownText>,
|
||||
},
|
||||
/// Image path on the filesystem.
|
||||
Path {
|
||||
source_range: Range<usize>,
|
||||
/// The path as provided in the Markdown document.
|
||||
display_path: PathBuf,
|
||||
/// The absolute path to the item.
|
||||
path: PathBuf,
|
||||
/// Link URL if exists.
|
||||
link: Option<Link>,
|
||||
/// alt text if it exists
|
||||
alt_text: Option<ParsedMarkdownText>,
|
||||
},
|
||||
}
|
||||
|
||||
impl Image {
|
||||
pub fn identify(
|
||||
source_range: Range<usize>,
|
||||
file_location_directory: Option<PathBuf>,
|
||||
text: String,
|
||||
link: Option<Link>,
|
||||
) -> Option<Image> {
|
||||
if text.starts_with("http") {
|
||||
return Some(Image::Web {
|
||||
source_range,
|
||||
url: text,
|
||||
link,
|
||||
alt_text: None,
|
||||
});
|
||||
}
|
||||
let path = PathBuf::from(&text);
|
||||
if path.is_absolute() {
|
||||
return Some(Image::Path {
|
||||
source_range,
|
||||
display_path: path.clone(),
|
||||
path,
|
||||
link,
|
||||
alt_text: None,
|
||||
});
|
||||
}
|
||||
if let Some(file_location_directory) = file_location_directory {
|
||||
let display_path = path;
|
||||
let path = file_location_directory.join(text);
|
||||
return Some(Image::Path {
|
||||
source_range,
|
||||
display_path,
|
||||
path,
|
||||
link,
|
||||
alt_text: None,
|
||||
});
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn with_alt_text(&self, alt_text: ParsedMarkdownText) -> Self {
|
||||
match self {
|
||||
Image::Web {
|
||||
ref source_range,
|
||||
ref url,
|
||||
ref link,
|
||||
..
|
||||
} => Image::Web {
|
||||
source_range: source_range.clone(),
|
||||
url: url.clone(),
|
||||
link: link.clone(),
|
||||
alt_text: Some(alt_text),
|
||||
},
|
||||
Image::Path {
|
||||
ref source_range,
|
||||
ref display_path,
|
||||
ref path,
|
||||
ref link,
|
||||
..
|
||||
} => Image::Path {
|
||||
source_range: source_range.clone(),
|
||||
display_path: display_path.clone(),
|
||||
path: path.clone(),
|
||||
link: link.clone(),
|
||||
alt_text: Some(alt_text),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Image {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Image::Web { url, .. } => write!(f, "{}", url),
|
||||
Image::Path { display_path, .. } => write!(f, "{}", display_path.display()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ use collections::FxHashMap;
|
||||
use gpui::FontWeight;
|
||||
use language::LanguageRegistry;
|
||||
use pulldown_cmark::{Alignment, Event, Options, Parser, Tag, TagEnd};
|
||||
use std::{ops::Range, path::PathBuf, sync::Arc};
|
||||
use std::{ops::Range, path::PathBuf, sync::Arc, vec};
|
||||
|
||||
pub async fn parse_markdown(
|
||||
markdown_input: &str,
|
||||
@@ -101,11 +101,11 @@ impl<'a> MarkdownParser<'a> {
|
||||
| Event::Code(_)
|
||||
| Event::Html(_)
|
||||
| Event::FootnoteReference(_)
|
||||
| Event::Start(Tag::Link { link_type: _, dest_url: _, title: _, id: _ })
|
||||
| Event::Start(Tag::Link { .. })
|
||||
| Event::Start(Tag::Emphasis)
|
||||
| Event::Start(Tag::Strong)
|
||||
| Event::Start(Tag::Strikethrough)
|
||||
| Event::Start(Tag::Image { link_type: _, dest_url: _, title: _, id: _ }) => {
|
||||
| Event::Start(Tag::Image { .. }) => {
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
@@ -134,12 +134,7 @@ impl<'a> MarkdownParser<'a> {
|
||||
let text = self.parse_text(false, Some(source_range));
|
||||
Some(vec![ParsedMarkdownElement::Paragraph(text)])
|
||||
}
|
||||
Tag::Heading {
|
||||
level,
|
||||
id: _,
|
||||
classes: _,
|
||||
attrs: _,
|
||||
} => {
|
||||
Tag::Heading { level, .. } => {
|
||||
let level = *level;
|
||||
self.cursor += 1;
|
||||
let heading = self.parse_heading(level);
|
||||
@@ -194,22 +189,23 @@ impl<'a> MarkdownParser<'a> {
|
||||
&mut self,
|
||||
should_complete_on_soft_break: bool,
|
||||
source_range: Option<Range<usize>>,
|
||||
) -> ParsedMarkdownText {
|
||||
) -> MarkdownParagraph {
|
||||
let source_range = source_range.unwrap_or_else(|| {
|
||||
self.current()
|
||||
.map(|(_, range)| range.clone())
|
||||
.unwrap_or_default()
|
||||
});
|
||||
|
||||
let mut markdown_text_like = Vec::new();
|
||||
let mut text = String::new();
|
||||
let mut bold_depth = 0;
|
||||
let mut italic_depth = 0;
|
||||
let mut strikethrough_depth = 0;
|
||||
let mut link: Option<Link> = None;
|
||||
let mut image: Option<Image> = None;
|
||||
let mut region_ranges: Vec<Range<usize>> = vec![];
|
||||
let mut regions: Vec<ParsedRegion> = vec![];
|
||||
let mut highlights: Vec<(Range<usize>, MarkdownHighlight)> = vec![];
|
||||
|
||||
let mut link_urls: Vec<String> = vec![];
|
||||
let mut link_ranges: Vec<Range<usize>> = vec![];
|
||||
|
||||
@@ -225,8 +221,6 @@ impl<'a> MarkdownParser<'a> {
|
||||
if should_complete_on_soft_break {
|
||||
break;
|
||||
}
|
||||
|
||||
// `Some text\nSome more text` should be treated as a single line.
|
||||
text.push(' ');
|
||||
}
|
||||
|
||||
@@ -240,7 +234,6 @@ impl<'a> MarkdownParser<'a> {
|
||||
|
||||
Event::Text(t) => {
|
||||
text.push_str(t.as_ref());
|
||||
|
||||
let mut style = MarkdownHighlightStyle::default();
|
||||
|
||||
if bold_depth > 0 {
|
||||
@@ -299,7 +292,6 @@ impl<'a> MarkdownParser<'a> {
|
||||
url: link.as_str().to_string(),
|
||||
}),
|
||||
});
|
||||
|
||||
last_link_len = end;
|
||||
}
|
||||
last_link_len
|
||||
@@ -316,13 +308,63 @@ impl<'a> MarkdownParser<'a> {
|
||||
}
|
||||
}
|
||||
if new_highlight {
|
||||
highlights
|
||||
.push((last_run_len..text.len(), MarkdownHighlight::Style(style)));
|
||||
highlights.push((
|
||||
last_run_len..text.len(),
|
||||
MarkdownHighlight::Style(style.clone()),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(mut image) = image.clone() {
|
||||
let is_valid_image = match image.clone() {
|
||||
Image::Path { display_path, .. } => {
|
||||
gpui::ImageSource::try_from(display_path).is_ok()
|
||||
}
|
||||
Image::Web { url, .. } => gpui::ImageSource::try_from(url).is_ok(),
|
||||
};
|
||||
if is_valid_image {
|
||||
text.truncate(text.len() - t.len());
|
||||
if !t.is_empty() {
|
||||
let alt_text = ParsedMarkdownText {
|
||||
source_range: source_range.clone(),
|
||||
contents: t.to_string(),
|
||||
highlights: highlights.clone(),
|
||||
region_ranges: region_ranges.clone(),
|
||||
regions: regions.clone(),
|
||||
};
|
||||
image = image.with_alt_text(alt_text);
|
||||
} else {
|
||||
let alt_text = ParsedMarkdownText {
|
||||
source_range: source_range.clone(),
|
||||
contents: "img".to_string(),
|
||||
highlights: highlights.clone(),
|
||||
region_ranges: region_ranges.clone(),
|
||||
regions: regions.clone(),
|
||||
};
|
||||
image = image.with_alt_text(alt_text);
|
||||
}
|
||||
if !text.is_empty() {
|
||||
let parsed_regions =
|
||||
MarkdownParagraphChunk::Text(ParsedMarkdownText {
|
||||
source_range: source_range.clone(),
|
||||
contents: text.clone(),
|
||||
highlights: highlights.clone(),
|
||||
region_ranges: region_ranges.clone(),
|
||||
regions: regions.clone(),
|
||||
});
|
||||
text = String::new();
|
||||
highlights = vec![];
|
||||
region_ranges = vec![];
|
||||
regions = vec![];
|
||||
markdown_text_like.push(parsed_regions);
|
||||
}
|
||||
|
||||
// Note: This event means "inline code" and not "code block"
|
||||
let parsed_image = MarkdownParagraphChunk::Image(image.clone());
|
||||
markdown_text_like.push(parsed_image);
|
||||
style = MarkdownHighlightStyle::default();
|
||||
}
|
||||
style.underline = true;
|
||||
};
|
||||
}
|
||||
Event::Code(t) => {
|
||||
text.push_str(t.as_ref());
|
||||
region_ranges.push(prev_len..text.len());
|
||||
@@ -336,46 +378,44 @@ impl<'a> MarkdownParser<'a> {
|
||||
}),
|
||||
));
|
||||
}
|
||||
|
||||
regions.push(ParsedRegion {
|
||||
code: true,
|
||||
link: link.clone(),
|
||||
});
|
||||
}
|
||||
|
||||
Event::Start(tag) => match tag {
|
||||
Tag::Emphasis => italic_depth += 1,
|
||||
Tag::Strong => bold_depth += 1,
|
||||
Tag::Strikethrough => strikethrough_depth += 1,
|
||||
Tag::Link {
|
||||
link_type: _,
|
||||
dest_url,
|
||||
title: _,
|
||||
id: _,
|
||||
} => {
|
||||
Tag::Link { dest_url, .. } => {
|
||||
link = Link::identify(
|
||||
self.file_location_directory.clone(),
|
||||
dest_url.to_string(),
|
||||
);
|
||||
}
|
||||
Tag::Image { dest_url, .. } => {
|
||||
image = Image::identify(
|
||||
source_range.clone(),
|
||||
self.file_location_directory.clone(),
|
||||
dest_url.to_string(),
|
||||
link.clone(),
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
Event::End(tag) => match tag {
|
||||
TagEnd::Emphasis => {
|
||||
italic_depth -= 1;
|
||||
}
|
||||
TagEnd::Strong => {
|
||||
bold_depth -= 1;
|
||||
}
|
||||
TagEnd::Strikethrough => {
|
||||
strikethrough_depth -= 1;
|
||||
}
|
||||
TagEnd::Emphasis => italic_depth -= 1,
|
||||
TagEnd::Strong => bold_depth -= 1,
|
||||
TagEnd::Strikethrough => strikethrough_depth -= 1,
|
||||
TagEnd::Link => {
|
||||
link = None;
|
||||
}
|
||||
TagEnd::Image => {
|
||||
image = None;
|
||||
}
|
||||
TagEnd::Paragraph => {
|
||||
self.cursor += 1;
|
||||
break;
|
||||
@@ -384,7 +424,6 @@ impl<'a> MarkdownParser<'a> {
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
_ => {
|
||||
break;
|
||||
}
|
||||
@@ -392,14 +431,16 @@ impl<'a> MarkdownParser<'a> {
|
||||
|
||||
self.cursor += 1;
|
||||
}
|
||||
|
||||
ParsedMarkdownText {
|
||||
source_range,
|
||||
contents: text,
|
||||
highlights,
|
||||
regions,
|
||||
region_ranges,
|
||||
if !text.is_empty() {
|
||||
markdown_text_like.push(MarkdownParagraphChunk::Text(ParsedMarkdownText {
|
||||
source_range: source_range.clone(),
|
||||
contents: text,
|
||||
highlights,
|
||||
regions,
|
||||
region_ranges,
|
||||
}));
|
||||
}
|
||||
markdown_text_like
|
||||
}
|
||||
|
||||
fn parse_heading(&mut self, level: pulldown_cmark::HeadingLevel) -> ParsedMarkdownHeading {
|
||||
@@ -708,7 +749,6 @@ impl<'a> MarkdownParser<'a> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let highlights = if let Some(language) = &language {
|
||||
if let Some(registry) = &self.language_registry {
|
||||
let rope: language::Rope = code.as_str().into();
|
||||
@@ -735,10 +775,14 @@ impl<'a> MarkdownParser<'a> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use core::panic;
|
||||
|
||||
use super::*;
|
||||
|
||||
use gpui::BackgroundExecutor;
|
||||
use language::{tree_sitter_rust, HighlightId, Language, LanguageConfig, LanguageMatcher};
|
||||
use language::{
|
||||
tree_sitter_rust, HighlightId, Language, LanguageConfig, LanguageMatcher, LanguageRegistry,
|
||||
};
|
||||
use pretty_assertions::assert_eq;
|
||||
use ParsedMarkdownListItemType::*;
|
||||
|
||||
@@ -810,20 +854,29 @@ mod tests {
|
||||
assert_eq!(parsed.children.len(), 1);
|
||||
assert_eq!(
|
||||
parsed.children[0],
|
||||
ParsedMarkdownElement::Paragraph(ParsedMarkdownText {
|
||||
source_range: 0..35,
|
||||
contents: "Some bostrikethroughld text".to_string(),
|
||||
highlights: Vec::new(),
|
||||
region_ranges: Vec::new(),
|
||||
regions: Vec::new(),
|
||||
})
|
||||
ParsedMarkdownElement::Paragraph(vec![MarkdownParagraphChunk::Text(
|
||||
ParsedMarkdownText {
|
||||
source_range: 0..35,
|
||||
contents: "Some bostrikethroughld text".to_string(),
|
||||
highlights: Vec::new(),
|
||||
region_ranges: Vec::new(),
|
||||
regions: Vec::new(),
|
||||
}
|
||||
)])
|
||||
);
|
||||
|
||||
let paragraph = if let ParsedMarkdownElement::Paragraph(text) = &parsed.children[0] {
|
||||
let new_text = if let ParsedMarkdownElement::Paragraph(text) = &parsed.children[0] {
|
||||
text
|
||||
} else {
|
||||
panic!("Expected a paragraph");
|
||||
};
|
||||
|
||||
let paragraph = if let MarkdownParagraphChunk::Text(text) = &new_text[0] {
|
||||
text
|
||||
} else {
|
||||
panic!("Expected a text");
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
paragraph.highlights,
|
||||
vec![
|
||||
@@ -871,6 +924,11 @@ mod tests {
|
||||
parsed.children,
|
||||
vec![p("Checkout this https://zed.dev link", 0..34)]
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_image_links_detection() {
|
||||
let parsed = parse("").await;
|
||||
|
||||
let paragraph = if let ParsedMarkdownElement::Paragraph(text) = &parsed.children[0] {
|
||||
text
|
||||
@@ -878,25 +936,22 @@ mod tests {
|
||||
panic!("Expected a paragraph");
|
||||
};
|
||||
assert_eq!(
|
||||
paragraph.highlights,
|
||||
vec![(
|
||||
14..29,
|
||||
MarkdownHighlight::Style(MarkdownHighlightStyle {
|
||||
underline: true,
|
||||
..Default::default()
|
||||
}),
|
||||
)]
|
||||
paragraph[0],
|
||||
MarkdownParagraphChunk::Image(Image::Web {
|
||||
source_range: 0..111,
|
||||
url: "https://blog.logrocket.com/wp-content/uploads/2024/04/exploring-zed-open-source-code-editor-rust-2.png".to_string(),
|
||||
link: None,
|
||||
alt_text: Some(
|
||||
ParsedMarkdownText {
|
||||
source_range: 0..111,
|
||||
contents: "test".to_string(),
|
||||
highlights: vec![],
|
||||
region_ranges: vec![],
|
||||
regions: vec![],
|
||||
},
|
||||
),
|
||||
},)
|
||||
);
|
||||
assert_eq!(
|
||||
paragraph.regions,
|
||||
vec![ParsedRegion {
|
||||
code: false,
|
||||
link: Some(Link::Web {
|
||||
url: "https://zed.dev".to_string()
|
||||
}),
|
||||
}]
|
||||
);
|
||||
assert_eq!(paragraph.region_ranges, vec![14..29]);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
@@ -1169,7 +1224,7 @@ Some other content
|
||||
vec![
|
||||
list_item(0..8, 1, Unordered, vec![p("code", 2..8)]),
|
||||
list_item(9..19, 1, Unordered, vec![p("bold", 11..19)]),
|
||||
list_item(20..49, 1, Unordered, vec![p("link", 22..49)],)
|
||||
list_item(20..49, 1, Unordered, vec![p("link", 22..49)],),
|
||||
],
|
||||
);
|
||||
}
|
||||
@@ -1312,7 +1367,7 @@ fn main() {
|
||||
))
|
||||
}
|
||||
|
||||
fn h1(contents: ParsedMarkdownText, source_range: Range<usize>) -> ParsedMarkdownElement {
|
||||
fn h1(contents: MarkdownParagraph, source_range: Range<usize>) -> ParsedMarkdownElement {
|
||||
ParsedMarkdownElement::Heading(ParsedMarkdownHeading {
|
||||
source_range,
|
||||
level: HeadingLevel::H1,
|
||||
@@ -1320,7 +1375,7 @@ fn main() {
|
||||
})
|
||||
}
|
||||
|
||||
fn h2(contents: ParsedMarkdownText, source_range: Range<usize>) -> ParsedMarkdownElement {
|
||||
fn h2(contents: MarkdownParagraph, source_range: Range<usize>) -> ParsedMarkdownElement {
|
||||
ParsedMarkdownElement::Heading(ParsedMarkdownHeading {
|
||||
source_range,
|
||||
level: HeadingLevel::H2,
|
||||
@@ -1328,7 +1383,7 @@ fn main() {
|
||||
})
|
||||
}
|
||||
|
||||
fn h3(contents: ParsedMarkdownText, source_range: Range<usize>) -> ParsedMarkdownElement {
|
||||
fn h3(contents: MarkdownParagraph, source_range: Range<usize>) -> ParsedMarkdownElement {
|
||||
ParsedMarkdownElement::Heading(ParsedMarkdownHeading {
|
||||
source_range,
|
||||
level: HeadingLevel::H3,
|
||||
@@ -1340,14 +1395,14 @@ fn main() {
|
||||
ParsedMarkdownElement::Paragraph(text(contents, source_range))
|
||||
}
|
||||
|
||||
fn text(contents: &str, source_range: Range<usize>) -> ParsedMarkdownText {
|
||||
ParsedMarkdownText {
|
||||
fn text(contents: &str, source_range: Range<usize>) -> MarkdownParagraph {
|
||||
vec![MarkdownParagraphChunk::Text(ParsedMarkdownText {
|
||||
highlights: Vec::new(),
|
||||
region_ranges: Vec::new(),
|
||||
regions: Vec::new(),
|
||||
source_range,
|
||||
contents: contents.to_string(),
|
||||
}
|
||||
})]
|
||||
}
|
||||
|
||||
fn block_quote(
|
||||
@@ -1401,7 +1456,7 @@ fn main() {
|
||||
}
|
||||
}
|
||||
|
||||
fn row(children: Vec<ParsedMarkdownText>) -> ParsedMarkdownTableRow {
|
||||
fn row(children: Vec<MarkdownParagraph>) -> ParsedMarkdownTableRow {
|
||||
ParsedMarkdownTableRow { children }
|
||||
}
|
||||
|
||||
|
||||
@@ -1,29 +1,33 @@
|
||||
use crate::markdown_elements::{
|
||||
HeadingLevel, Link, ParsedMarkdown, ParsedMarkdownBlockQuote, ParsedMarkdownCodeBlock,
|
||||
ParsedMarkdownElement, ParsedMarkdownHeading, ParsedMarkdownListItem,
|
||||
ParsedMarkdownListItemType, ParsedMarkdownTable, ParsedMarkdownTableAlignment,
|
||||
ParsedMarkdownTableRow, ParsedMarkdownText,
|
||||
HeadingLevel, Image, Link, MarkdownParagraph, MarkdownParagraphChunk, ParsedMarkdown,
|
||||
ParsedMarkdownBlockQuote, ParsedMarkdownCodeBlock, ParsedMarkdownElement,
|
||||
ParsedMarkdownHeading, ParsedMarkdownListItem, ParsedMarkdownListItemType, ParsedMarkdownTable,
|
||||
ParsedMarkdownTableAlignment, ParsedMarkdownTableRow, ParsedMarkdownText,
|
||||
};
|
||||
use gpui::{
|
||||
div, px, rems, AbsoluteLength, AnyElement, ClipboardItem, DefiniteLength, Div, Element,
|
||||
ElementId, HighlightStyle, Hsla, InteractiveText, IntoElement, Keystroke, Length, Modifiers,
|
||||
ParentElement, SharedString, Styled, StyledText, TextStyle, WeakView, WindowContext,
|
||||
div, img, px, rems, AbsoluteLength, AnyElement, ClipboardItem, DefiniteLength, Div, Element,
|
||||
ElementId, HighlightStyle, Hsla, ImageSource, InteractiveText, IntoElement, Keystroke, Length,
|
||||
Modifiers, ParentElement, Resource, SharedString, Styled, StyledText, TextStyle, WeakView,
|
||||
WindowContext,
|
||||
};
|
||||
use settings::Settings;
|
||||
use std::{
|
||||
ops::{Mul, Range},
|
||||
path::Path,
|
||||
sync::Arc,
|
||||
vec,
|
||||
};
|
||||
use theme::{ActiveTheme, SyntaxTheme, ThemeSettings};
|
||||
use ui::{
|
||||
h_flex, relative, v_flex, Checkbox, Clickable, FluentBuilder, IconButton, IconName, IconSize,
|
||||
InteractiveElement, LinkPreview, Selection, StatefulInteractiveElement, StyledExt, Tooltip,
|
||||
VisibleOnHover,
|
||||
InteractiveElement, LinkPreview, Selection, StatefulInteractiveElement, StyledExt, StyledImage,
|
||||
Tooltip, VisibleOnHover,
|
||||
};
|
||||
use workspace::Workspace;
|
||||
|
||||
type CheckboxClickedCallback = Arc<Box<dyn Fn(bool, Range<usize>, &mut WindowContext)>>;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct RenderContext {
|
||||
workspace: Option<WeakView<Workspace>>,
|
||||
next_id: usize,
|
||||
@@ -153,7 +157,7 @@ fn render_markdown_heading(parsed: &ParsedMarkdownHeading, cx: &mut RenderContex
|
||||
.text_color(color)
|
||||
.pt(rems(0.15))
|
||||
.pb_1()
|
||||
.child(render_markdown_text(&parsed.contents, cx))
|
||||
.children(render_markdown_text(&parsed.contents, cx))
|
||||
.whitespace_normal()
|
||||
.into_any()
|
||||
}
|
||||
@@ -231,17 +235,29 @@ fn render_markdown_list_item(
|
||||
cx.with_common_p(item).into_any()
|
||||
}
|
||||
|
||||
fn paragraph_len(paragraphs: &MarkdownParagraph) -> usize {
|
||||
paragraphs
|
||||
.iter()
|
||||
.map(|paragraph| match paragraph {
|
||||
MarkdownParagraphChunk::Text(text) => text.contents.len(),
|
||||
// TODO: Scale column width based on image size
|
||||
MarkdownParagraphChunk::Image(_) => 1,
|
||||
})
|
||||
.sum()
|
||||
}
|
||||
|
||||
fn render_markdown_table(parsed: &ParsedMarkdownTable, cx: &mut RenderContext) -> AnyElement {
|
||||
let mut max_lengths: Vec<usize> = vec![0; parsed.header.children.len()];
|
||||
|
||||
for (index, cell) in parsed.header.children.iter().enumerate() {
|
||||
let length = cell.contents.len();
|
||||
let length = paragraph_len(&cell);
|
||||
max_lengths[index] = length;
|
||||
}
|
||||
|
||||
for row in &parsed.body {
|
||||
for (index, cell) in row.children.iter().enumerate() {
|
||||
let length = cell.contents.len();
|
||||
let length = paragraph_len(&cell);
|
||||
|
||||
if length > max_lengths[index] {
|
||||
max_lengths[index] = length;
|
||||
}
|
||||
@@ -307,11 +323,10 @@ fn render_markdown_table_row(
|
||||
};
|
||||
|
||||
let max_width = max_column_widths.get(index).unwrap_or(&0.0);
|
||||
|
||||
let mut cell = container
|
||||
.w(Length::Definite(relative(*max_width)))
|
||||
.h_full()
|
||||
.child(contents)
|
||||
.children(contents)
|
||||
.px_2()
|
||||
.py_1()
|
||||
.border_color(cx.border_color);
|
||||
@@ -398,18 +413,219 @@ fn render_markdown_code_block(
|
||||
.into_any()
|
||||
}
|
||||
|
||||
fn render_markdown_paragraph(parsed: &ParsedMarkdownText, cx: &mut RenderContext) -> AnyElement {
|
||||
fn render_markdown_paragraph(parsed: &MarkdownParagraph, cx: &mut RenderContext) -> AnyElement {
|
||||
cx.with_common_p(div())
|
||||
.child(render_markdown_text(parsed, cx))
|
||||
.children(render_markdown_text(parsed, cx))
|
||||
.flex()
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
fn render_markdown_text(parsed: &ParsedMarkdownText, cx: &mut RenderContext) -> AnyElement {
|
||||
let element_id = cx.next_id(&parsed.source_range);
|
||||
fn render_markdown_text(parsed_new: &MarkdownParagraph, cx: &mut RenderContext) -> Vec<AnyElement> {
|
||||
let mut any_element = vec![];
|
||||
// these values are cloned in-order satisfy borrow checker
|
||||
let syntax_theme = cx.syntax_theme.clone();
|
||||
let workspace_clone = cx.workspace.clone();
|
||||
let code_span_bg_color = cx.code_span_background_color;
|
||||
let text_style = cx.text_style.clone();
|
||||
|
||||
for parsed_region in parsed_new {
|
||||
match parsed_region {
|
||||
MarkdownParagraphChunk::Text(parsed) => {
|
||||
let element_id = cx.next_id(&parsed.source_range);
|
||||
|
||||
let highlights = gpui::combine_highlights(
|
||||
parsed.highlights.iter().filter_map(|(range, highlight)| {
|
||||
highlight
|
||||
.to_highlight_style(&syntax_theme)
|
||||
.map(|style| (range.clone(), style))
|
||||
}),
|
||||
parsed.regions.iter().zip(&parsed.region_ranges).filter_map(
|
||||
|(region, range)| {
|
||||
if region.code {
|
||||
Some((
|
||||
range.clone(),
|
||||
HighlightStyle {
|
||||
background_color: Some(code_span_bg_color),
|
||||
..Default::default()
|
||||
},
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
let mut links = Vec::new();
|
||||
let mut link_ranges = Vec::new();
|
||||
for (range, region) in parsed.region_ranges.iter().zip(&parsed.regions) {
|
||||
if let Some(link) = region.link.clone() {
|
||||
links.push(link);
|
||||
link_ranges.push(range.clone());
|
||||
}
|
||||
}
|
||||
let workspace = workspace_clone.clone();
|
||||
let element = div()
|
||||
.child(
|
||||
InteractiveText::new(
|
||||
element_id,
|
||||
StyledText::new(parsed.contents.clone())
|
||||
.with_highlights(&text_style, highlights),
|
||||
)
|
||||
.tooltip({
|
||||
let links = links.clone();
|
||||
let link_ranges = link_ranges.clone();
|
||||
move |idx, cx| {
|
||||
for (ix, range) in link_ranges.iter().enumerate() {
|
||||
if range.contains(&idx) {
|
||||
return Some(LinkPreview::new(&links[ix].to_string(), cx));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
})
|
||||
.on_click(
|
||||
link_ranges,
|
||||
move |clicked_range_ix, window_cx| match &links[clicked_range_ix] {
|
||||
Link::Web { url } => window_cx.open_url(url),
|
||||
Link::Path { path, .. } => {
|
||||
if let Some(workspace) = &workspace {
|
||||
_ = workspace.update(window_cx, |workspace, cx| {
|
||||
workspace
|
||||
.open_abs_path(path.clone(), false, cx)
|
||||
.detach();
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
)
|
||||
.into_any();
|
||||
any_element.push(element);
|
||||
}
|
||||
|
||||
MarkdownParagraphChunk::Image(image) => {
|
||||
let (link, source_range, image_source, alt_text) = match image {
|
||||
Image::Web {
|
||||
link,
|
||||
source_range,
|
||||
url,
|
||||
alt_text,
|
||||
} => (
|
||||
link,
|
||||
source_range,
|
||||
Resource::Uri(url.clone().into()),
|
||||
alt_text,
|
||||
),
|
||||
Image::Path {
|
||||
link,
|
||||
source_range,
|
||||
path,
|
||||
alt_text,
|
||||
..
|
||||
} => {
|
||||
let image_path = Path::new(path.to_str().unwrap());
|
||||
(
|
||||
link,
|
||||
source_range,
|
||||
Resource::Path(Arc::from(image_path)),
|
||||
alt_text,
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
let element_id = cx.next_id(source_range);
|
||||
|
||||
match link {
|
||||
None => {
|
||||
let fallback_workspace = workspace_clone.clone();
|
||||
let fallback_syntax_theme = syntax_theme.clone();
|
||||
let fallback_text_style = text_style.clone();
|
||||
let fallback_alt_text = alt_text.clone();
|
||||
let element_id_new = element_id.clone();
|
||||
let element = div()
|
||||
.child(img(ImageSource::Resource(image_source)).with_fallback({
|
||||
move || {
|
||||
fallback_text(
|
||||
fallback_alt_text.clone().unwrap(),
|
||||
element_id.clone(),
|
||||
&fallback_syntax_theme,
|
||||
code_span_bg_color,
|
||||
fallback_workspace.clone(),
|
||||
&fallback_text_style,
|
||||
)
|
||||
}
|
||||
}))
|
||||
.id(element_id_new)
|
||||
.into_any();
|
||||
any_element.push(element);
|
||||
}
|
||||
Some(link) => {
|
||||
let link_click = link.clone();
|
||||
let link_tooltip = link.clone();
|
||||
let fallback_workspace = workspace_clone.clone();
|
||||
let fallback_syntax_theme = syntax_theme.clone();
|
||||
let fallback_text_style = text_style.clone();
|
||||
let fallback_alt_text = alt_text.clone();
|
||||
let element_id_new = element_id.clone();
|
||||
let image_element = div()
|
||||
.child(img(ImageSource::Resource(image_source)).with_fallback({
|
||||
move || {
|
||||
fallback_text(
|
||||
fallback_alt_text.clone().unwrap(),
|
||||
element_id.clone(),
|
||||
&fallback_syntax_theme,
|
||||
code_span_bg_color,
|
||||
fallback_workspace.clone(),
|
||||
&fallback_text_style,
|
||||
)
|
||||
}
|
||||
}))
|
||||
.id(element_id_new)
|
||||
.tooltip(move |cx| LinkPreview::new(&link_tooltip.to_string(), cx))
|
||||
.on_click({
|
||||
let workspace = workspace_clone.clone();
|
||||
move |_event, window_cx| match &link_click {
|
||||
Link::Web { url } => window_cx.open_url(url),
|
||||
Link::Path { path, .. } => {
|
||||
if let Some(workspace) = &workspace {
|
||||
_ = workspace.update(window_cx, |workspace, cx| {
|
||||
workspace
|
||||
.open_abs_path(path.clone(), false, cx)
|
||||
.detach();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.into_any();
|
||||
any_element.push(image_element);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
any_element
|
||||
}
|
||||
|
||||
fn render_markdown_rule(cx: &mut RenderContext) -> AnyElement {
|
||||
let rule = div().w_full().h(px(2.)).bg(cx.border_color);
|
||||
div().pt_3().pb_3().child(rule).into_any()
|
||||
}
|
||||
|
||||
fn fallback_text(
|
||||
parsed: ParsedMarkdownText,
|
||||
source_range: ElementId,
|
||||
syntax_theme: &theme::SyntaxTheme,
|
||||
code_span_bg_color: Hsla,
|
||||
workspace: Option<WeakView<Workspace>>,
|
||||
text_style: &TextStyle,
|
||||
) -> AnyElement {
|
||||
let element_id = source_range;
|
||||
|
||||
let highlights = gpui::combine_highlights(
|
||||
parsed.highlights.iter().filter_map(|(range, highlight)| {
|
||||
let highlight = highlight.to_highlight_style(&cx.syntax_theme)?;
|
||||
let highlight = highlight.to_highlight_style(syntax_theme)?;
|
||||
Some((range.clone(), highlight))
|
||||
}),
|
||||
parsed
|
||||
@@ -421,7 +637,7 @@ fn render_markdown_text(parsed: &ParsedMarkdownText, cx: &mut RenderContext) ->
|
||||
Some((
|
||||
range.clone(),
|
||||
HighlightStyle {
|
||||
background_color: Some(cx.code_span_background_color),
|
||||
background_color: Some(code_span_bg_color),
|
||||
..Default::default()
|
||||
},
|
||||
))
|
||||
@@ -430,7 +646,6 @@ fn render_markdown_text(parsed: &ParsedMarkdownText, cx: &mut RenderContext) ->
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
let mut links = Vec::new();
|
||||
let mut link_ranges = Vec::new();
|
||||
for (range, region) in parsed.region_ranges.iter().zip(&parsed.regions) {
|
||||
@@ -439,45 +654,38 @@ fn render_markdown_text(parsed: &ParsedMarkdownText, cx: &mut RenderContext) ->
|
||||
link_ranges.push(range.clone());
|
||||
}
|
||||
}
|
||||
|
||||
let workspace = cx.workspace.clone();
|
||||
|
||||
InteractiveText::new(
|
||||
element_id,
|
||||
StyledText::new(parsed.contents.clone()).with_highlights(&cx.text_style, highlights),
|
||||
)
|
||||
.tooltip({
|
||||
let links = links.clone();
|
||||
let link_ranges = link_ranges.clone();
|
||||
move |idx, cx| {
|
||||
for (ix, range) in link_ranges.iter().enumerate() {
|
||||
if range.contains(&idx) {
|
||||
return Some(LinkPreview::new(&links[ix].to_string(), cx));
|
||||
let element = div()
|
||||
.child(
|
||||
InteractiveText::new(
|
||||
element_id,
|
||||
StyledText::new(parsed.contents.clone()).with_highlights(text_style, highlights),
|
||||
)
|
||||
.tooltip({
|
||||
let links = links.clone();
|
||||
let link_ranges = link_ranges.clone();
|
||||
move |idx, cx| {
|
||||
for (ix, range) in link_ranges.iter().enumerate() {
|
||||
if range.contains(&idx) {
|
||||
return Some(LinkPreview::new(&links[ix].to_string(), cx));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
})
|
||||
.on_click(
|
||||
link_ranges,
|
||||
move |clicked_range_ix, window_cx| match &links[clicked_range_ix] {
|
||||
Link::Web { url } => window_cx.open_url(url),
|
||||
Link::Path {
|
||||
path,
|
||||
display_path: _,
|
||||
} => {
|
||||
if let Some(workspace) = &workspace {
|
||||
_ = workspace.update(window_cx, |workspace, cx| {
|
||||
workspace.open_abs_path(path.clone(), false, cx).detach();
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
fn render_markdown_rule(cx: &mut RenderContext) -> AnyElement {
|
||||
let rule = div().w_full().h(px(2.)).bg(cx.border_color);
|
||||
div().pt_3().pb_3().child(rule).into_any()
|
||||
})
|
||||
.on_click(
|
||||
link_ranges,
|
||||
move |clicked_range_ix, window_cx| match &links[clicked_range_ix] {
|
||||
Link::Web { url } => window_cx.open_url(url),
|
||||
Link::Path { path, .. } => {
|
||||
if let Some(workspace) = &workspace {
|
||||
_ = workspace.update(window_cx, |workspace, cx| {
|
||||
workspace.open_abs_path(path.clone(), false, cx).detach();
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
)
|
||||
.into_any();
|
||||
return element;
|
||||
}
|
||||
|
||||
@@ -238,11 +238,8 @@ impl NotificationStore {
|
||||
) -> Result<()> {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
if let Some(notification) = envelope.payload.notification {
|
||||
if let Some(rpc::Notification::ChannelMessageMention {
|
||||
message_id,
|
||||
sender_id: _,
|
||||
channel_id: _,
|
||||
}) = Notification::from_proto(¬ification)
|
||||
if let Some(rpc::Notification::ChannelMessageMention { message_id, .. }) =
|
||||
Notification::from_proto(¬ification)
|
||||
{
|
||||
let fetch_message_task = this.channel_store.update(cx, |this, cx| {
|
||||
this.fetch_channel_messages(vec![message_id], cx)
|
||||
|
||||
@@ -174,6 +174,8 @@ impl Project {
|
||||
command_label: spawn_task.command_label,
|
||||
hide: spawn_task.hide,
|
||||
status: TaskStatus::Running,
|
||||
show_summary: spawn_task.show_summary,
|
||||
show_command: spawn_task.show_command,
|
||||
completion_rx,
|
||||
});
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ chrono.workspace = true
|
||||
clap.workspace = true
|
||||
client.workspace = true
|
||||
env_logger.workspace = true
|
||||
extension.workspace = true
|
||||
extension_host.workspace = true
|
||||
fs.workspace = true
|
||||
futures.workspace = true
|
||||
@@ -37,6 +38,7 @@ git_hosting_providers.workspace = true
|
||||
gpui.workspace = true
|
||||
http_client.workspace = true
|
||||
language.workspace = true
|
||||
language_extension.workspace = true
|
||||
languages.workspace = true
|
||||
log.workspace = true
|
||||
lsp.workspace = true
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
use extension::ExtensionHostProxy;
|
||||
use extension_host::headless_host::HeadlessExtensionStore;
|
||||
use fs::Fs;
|
||||
use gpui::{AppContext, AsyncAppContext, Context as _, Model, ModelContext, PromptLevel};
|
||||
@@ -47,6 +48,7 @@ pub struct HeadlessAppState {
|
||||
pub http_client: Arc<dyn HttpClient>,
|
||||
pub node_runtime: NodeRuntime,
|
||||
pub languages: Arc<LanguageRegistry>,
|
||||
pub extension_host_proxy: Arc<ExtensionHostProxy>,
|
||||
}
|
||||
|
||||
impl HeadlessProject {
|
||||
@@ -63,9 +65,11 @@ impl HeadlessProject {
|
||||
http_client,
|
||||
node_runtime,
|
||||
languages,
|
||||
extension_host_proxy: proxy,
|
||||
}: HeadlessAppState,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Self {
|
||||
language_extension::init(proxy.clone(), languages.clone());
|
||||
languages::init(languages.clone(), node_runtime.clone(), cx);
|
||||
|
||||
let worktree_store = cx.new_model(|cx| {
|
||||
@@ -152,8 +156,8 @@ impl HeadlessProject {
|
||||
let extensions = HeadlessExtensionStore::new(
|
||||
fs.clone(),
|
||||
http_client.clone(),
|
||||
languages.clone(),
|
||||
paths::remote_extensions_dir().to_path_buf(),
|
||||
proxy,
|
||||
node_runtime,
|
||||
cx,
|
||||
);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::headless_project::HeadlessProject;
|
||||
use client::{Client, UserStore};
|
||||
use clock::FakeSystemClock;
|
||||
use extension::ExtensionHostProxy;
|
||||
use fs::{FakeFs, Fs};
|
||||
use gpui::{Context, Model, SemanticVersion, TestAppContext};
|
||||
use http_client::{BlockedHttpClient, FakeHttpClient};
|
||||
@@ -1234,6 +1235,7 @@ pub async fn init_test(
|
||||
let http_client = Arc::new(BlockedHttpClient);
|
||||
let node_runtime = NodeRuntime::unavailable();
|
||||
let languages = Arc::new(LanguageRegistry::new(cx.executor()));
|
||||
let proxy = Arc::new(ExtensionHostProxy::new());
|
||||
server_cx.update(HeadlessProject::init);
|
||||
let headless = server_cx.new_model(|cx| {
|
||||
client::init_settings(cx);
|
||||
@@ -1245,6 +1247,7 @@ pub async fn init_test(
|
||||
http_client,
|
||||
node_runtime,
|
||||
languages,
|
||||
extension_host_proxy: proxy,
|
||||
},
|
||||
cx,
|
||||
)
|
||||
|
||||
@@ -3,6 +3,7 @@ use crate::HeadlessProject;
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use chrono::Utc;
|
||||
use client::{telemetry, ProxySettings};
|
||||
use extension::ExtensionHostProxy;
|
||||
use fs::{Fs, RealFs};
|
||||
use futures::channel::mpsc;
|
||||
use futures::{select, select_biased, AsyncRead, AsyncWrite, AsyncWriteExt, FutureExt, SinkExt};
|
||||
@@ -434,6 +435,9 @@ pub fn execute_run(
|
||||
GitHostingProviderRegistry::set_global(git_hosting_provider_registry, cx);
|
||||
git_hosting_providers::init(cx);
|
||||
|
||||
extension::init(cx);
|
||||
let extension_host_proxy = ExtensionHostProxy::global(cx);
|
||||
|
||||
let project = cx.new_model(|cx| {
|
||||
let fs = Arc::new(RealFs::new(Default::default(), None));
|
||||
let node_settings_rx = initialize_settings(session.clone(), fs.clone(), cx);
|
||||
@@ -466,6 +470,7 @@ pub fn execute_run(
|
||||
http_client,
|
||||
node_runtime,
|
||||
languages,
|
||||
extension_host_proxy,
|
||||
},
|
||||
cx,
|
||||
)
|
||||
|
||||
@@ -114,7 +114,7 @@ impl Cell {
|
||||
id,
|
||||
metadata,
|
||||
source,
|
||||
attachments: _,
|
||||
..
|
||||
} => {
|
||||
let source = source.join("");
|
||||
|
||||
|
||||
@@ -258,8 +258,9 @@ impl ReplStore {
|
||||
runtime_specification.kernelspec.language.to_lowercase()
|
||||
== language_at_cursor.code_fence_block_name().to_lowercase()
|
||||
}
|
||||
KernelSpecification::Remote(_) => {
|
||||
unimplemented!()
|
||||
KernelSpecification::Remote(remote_spec) => {
|
||||
remote_spec.kernelspec.language.to_lowercase()
|
||||
== language_at_cursor.code_fence_block_name().to_lowercase()
|
||||
}
|
||||
})
|
||||
.cloned()
|
||||
|
||||
@@ -310,12 +310,7 @@ pub fn render_markdown_mut(
|
||||
}
|
||||
Event::Start(tag) => match tag {
|
||||
Tag::Paragraph => new_paragraph(text, &mut list_stack),
|
||||
Tag::Heading {
|
||||
level: _,
|
||||
id: _,
|
||||
classes: _,
|
||||
attrs: _,
|
||||
} => {
|
||||
Tag::Heading { .. } => {
|
||||
new_paragraph(text, &mut list_stack);
|
||||
bold_depth += 1;
|
||||
}
|
||||
@@ -333,12 +328,7 @@ pub fn render_markdown_mut(
|
||||
Tag::Emphasis => italic_depth += 1,
|
||||
Tag::Strong => bold_depth += 1,
|
||||
Tag::Strikethrough => strikethrough_depth += 1,
|
||||
Tag::Link {
|
||||
link_type: _,
|
||||
dest_url,
|
||||
title: _,
|
||||
id: _,
|
||||
} => link_url = Some(dest_url.to_string()),
|
||||
Tag::Link { dest_url, .. } => link_url = Some(dest_url.to_string()),
|
||||
Tag::List(number) => {
|
||||
list_stack.push((number, false));
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ workspace = true
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
collections.workspace = true
|
||||
extension.workspace = true
|
||||
fs.workspace = true
|
||||
futures.workspace = true
|
||||
gpui.workspace = true
|
||||
|
||||
26
crates/snippet_provider/src/extension_snippet.rs
Normal file
26
crates/snippet_provider/src/extension_snippet.rs
Normal file
@@ -0,0 +1,26 @@
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::Result;
|
||||
use extension::{ExtensionHostProxy, ExtensionSnippetProxy};
|
||||
use gpui::AppContext;
|
||||
|
||||
use crate::SnippetRegistry;
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
let proxy = ExtensionHostProxy::default_global(cx);
|
||||
proxy.register_snippet_proxy(SnippetRegistryProxy {
|
||||
snippet_registry: SnippetRegistry::global(cx),
|
||||
});
|
||||
}
|
||||
|
||||
struct SnippetRegistryProxy {
|
||||
snippet_registry: Arc<SnippetRegistry>,
|
||||
}
|
||||
|
||||
impl ExtensionSnippetProxy for SnippetRegistryProxy {
|
||||
fn register_snippet(&self, path: &PathBuf, snippet_contents: &str) -> Result<()> {
|
||||
self.snippet_registry
|
||||
.register_snippets(path, snippet_contents)
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
mod extension_snippet;
|
||||
mod format;
|
||||
mod registry;
|
||||
|
||||
@@ -18,6 +19,7 @@ use util::ResultExt;
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
SnippetRegistry::init_global(cx);
|
||||
extension_snippet::init(cx);
|
||||
}
|
||||
|
||||
// Is `None` if the snippet file is global.
|
||||
|
||||
@@ -51,6 +51,10 @@ pub struct SpawnInTerminal {
|
||||
pub hide: HideStrategy,
|
||||
/// Which shell to use when spawning the task.
|
||||
pub shell: Shell,
|
||||
/// Whether to show the task summary line in the task output (sucess/failure).
|
||||
pub show_summary: bool,
|
||||
/// Whether to show the command line in the task output.
|
||||
pub show_command: bool,
|
||||
}
|
||||
|
||||
/// A final form of the [`TaskTemplate`], that got resolved with a particualar [`TaskContext`] and now is ready to spawn the actual task.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use std::path::PathBuf;
|
||||
use util::serde::default_true;
|
||||
|
||||
use anyhow::{bail, Context};
|
||||
use collections::{HashMap, HashSet};
|
||||
@@ -57,6 +58,12 @@ pub struct TaskTemplate {
|
||||
/// Which shell to use when spawning the task.
|
||||
#[serde(default)]
|
||||
pub shell: Shell,
|
||||
/// Whether to show the task line in the task output.
|
||||
#[serde(default = "default_true")]
|
||||
pub show_summary: bool,
|
||||
/// Whether to show the command line in the task output.
|
||||
#[serde(default = "default_true")]
|
||||
pub show_command: bool,
|
||||
}
|
||||
|
||||
/// What to do with the terminal pane and tab, after the command was started.
|
||||
@@ -230,6 +237,8 @@ impl TaskTemplate {
|
||||
reveal: self.reveal,
|
||||
hide: self.hide,
|
||||
shell: self.shell.clone(),
|
||||
show_summary: self.show_summary,
|
||||
show_command: self.show_command,
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -639,6 +639,8 @@ pub struct TaskState {
|
||||
pub status: TaskStatus,
|
||||
pub completion_rx: Receiver<()>,
|
||||
pub hide: HideStrategy,
|
||||
pub show_summary: bool,
|
||||
pub show_command: bool,
|
||||
}
|
||||
|
||||
/// A status of the current terminal tab's task.
|
||||
@@ -1760,11 +1762,22 @@ impl Terminal {
|
||||
};
|
||||
|
||||
let (finished_successfully, task_line, command_line) = task_summary(task, error_code);
|
||||
// SAFETY: the invocation happens on non `TaskStatus::Running` tasks, once,
|
||||
// after either `AlacTermEvent::Exit` or `AlacTermEvent::ChildExit` events that are spawned
|
||||
// when Zed task finishes and no more output is made.
|
||||
// After the task summary is output once, no more text is appended to the terminal.
|
||||
unsafe { append_text_to_term(&mut self.term.lock(), &[&task_line, &command_line]) };
|
||||
let mut lines_to_show = Vec::new();
|
||||
if task.show_summary {
|
||||
lines_to_show.push(task_line.as_str());
|
||||
}
|
||||
if task.show_command {
|
||||
lines_to_show.push(command_line.as_str());
|
||||
}
|
||||
|
||||
if !lines_to_show.is_empty() {
|
||||
// SAFETY: the invocation happens on non `TaskStatus::Running` tasks, once,
|
||||
// after either `AlacTermEvent::Exit` or `AlacTermEvent::ChildExit` events that are spawned
|
||||
// when Zed task finishes and no more output is made.
|
||||
// After the task summary is output once, no more text is appended to the terminal.
|
||||
unsafe { append_text_to_term(&mut self.term.lock(), &lines_to_show) };
|
||||
}
|
||||
|
||||
match task.hide {
|
||||
HideStrategy::Never => {}
|
||||
HideStrategy::Always => {
|
||||
|
||||
19
crates/theme_extension/Cargo.toml
Normal file
19
crates/theme_extension/Cargo.toml
Normal file
@@ -0,0 +1,19 @@
|
||||
[package]
|
||||
name = "theme_extension"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[lib]
|
||||
path = "src/theme_extension.rs"
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
extension.workspace = true
|
||||
fs.workspace = true
|
||||
gpui.workspace = true
|
||||
theme.workspace = true
|
||||
1
crates/theme_extension/LICENSE-GPL
Symbolic link
1
crates/theme_extension/LICENSE-GPL
Symbolic link
@@ -0,0 +1 @@
|
||||
../../LICENSE-GPL
|
||||
47
crates/theme_extension/src/theme_extension.rs
Normal file
47
crates/theme_extension/src/theme_extension.rs
Normal file
@@ -0,0 +1,47 @@
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::Result;
|
||||
use extension::{ExtensionHostProxy, ExtensionThemeProxy};
|
||||
use fs::Fs;
|
||||
use gpui::{AppContext, BackgroundExecutor, SharedString, Task};
|
||||
use theme::{ThemeRegistry, ThemeSettings};
|
||||
|
||||
pub fn init(
|
||||
extension_host_proxy: Arc<ExtensionHostProxy>,
|
||||
theme_registry: Arc<ThemeRegistry>,
|
||||
executor: BackgroundExecutor,
|
||||
) {
|
||||
extension_host_proxy.register_theme_proxy(ThemeRegistryProxy {
|
||||
theme_registry,
|
||||
executor,
|
||||
});
|
||||
}
|
||||
|
||||
struct ThemeRegistryProxy {
|
||||
theme_registry: Arc<ThemeRegistry>,
|
||||
executor: BackgroundExecutor,
|
||||
}
|
||||
|
||||
impl ExtensionThemeProxy for ThemeRegistryProxy {
|
||||
fn list_theme_names(&self, theme_path: PathBuf, fs: Arc<dyn Fs>) -> Task<Result<Vec<String>>> {
|
||||
self.executor.spawn(async move {
|
||||
let themes = theme::read_user_theme(&theme_path, fs).await?;
|
||||
Ok(themes.themes.into_iter().map(|theme| theme.name).collect())
|
||||
})
|
||||
}
|
||||
|
||||
fn remove_user_themes(&self, themes: Vec<SharedString>) {
|
||||
self.theme_registry.remove_user_themes(&themes);
|
||||
}
|
||||
|
||||
fn load_user_theme(&self, theme_path: PathBuf, fs: Arc<dyn Fs>) -> Task<Result<()>> {
|
||||
let theme_registry = self.theme_registry.clone();
|
||||
self.executor
|
||||
.spawn(async move { theme_registry.load_user_theme(&theme_path, fs).await })
|
||||
}
|
||||
|
||||
fn reload_current_theme(&self, cx: &mut AppContext) {
|
||||
ThemeSettings::reload_current_theme(cx)
|
||||
}
|
||||
}
|
||||
@@ -149,6 +149,12 @@ pub fn merge_json_value_into(source: serde_json::Value, target: &mut serde_json:
|
||||
}
|
||||
}
|
||||
|
||||
(Value::Array(source), Value::Array(target)) => {
|
||||
for value in source {
|
||||
target.push(value);
|
||||
}
|
||||
}
|
||||
|
||||
(source, target) => *target = source,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ use std::sync::Arc;
|
||||
use ui::{h_flex, ContextMenu, IconButton, Tooltip};
|
||||
use ui::{prelude::*, right_click_menu};
|
||||
|
||||
const RESIZE_HANDLE_SIZE: Pixels = Pixels(6.);
|
||||
pub(crate) const RESIZE_HANDLE_SIZE: Pixels = Pixels(6.);
|
||||
|
||||
pub enum PanelEvent {
|
||||
ZoomIn,
|
||||
@@ -574,6 +574,7 @@ impl Dock {
|
||||
pub fn resize_active_panel(&mut self, size: Option<Pixels>, cx: &mut ViewContext<Self>) {
|
||||
if let Some(entry) = self.panel_entries.get_mut(self.active_panel_index) {
|
||||
let size = size.map(|size| size.max(RESIZE_HANDLE_SIZE).round());
|
||||
|
||||
entry.panel.set_size(size, cx);
|
||||
cx.notify();
|
||||
}
|
||||
@@ -593,6 +594,15 @@ impl Dock {
|
||||
|
||||
dispatch_context
|
||||
}
|
||||
|
||||
pub fn clamp_panel_size(&mut self, max_size: Pixels, cx: &mut WindowContext) {
|
||||
let max_size = px((max_size.0 - RESIZE_HANDLE_SIZE.0).abs());
|
||||
for panel in self.panel_entries.iter().map(|entry| &entry.panel) {
|
||||
if panel.size(cx) > max_size {
|
||||
panel.set_size(Some(max_size.max(RESIZE_HANDLE_SIZE)), cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for Dock {
|
||||
|
||||
@@ -21,7 +21,7 @@ use client::{
|
||||
};
|
||||
use collections::{hash_map, HashMap, HashSet};
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use dock::{Dock, DockPosition, Panel, PanelButtons, PanelHandle};
|
||||
use dock::{Dock, DockPosition, Panel, PanelButtons, PanelHandle, RESIZE_HANDLE_SIZE};
|
||||
use futures::{
|
||||
channel::{
|
||||
mpsc::{self, UnboundedReceiver, UnboundedSender},
|
||||
@@ -4824,7 +4824,27 @@ impl Render for Workspace {
|
||||
let this = cx.view().clone();
|
||||
canvas(
|
||||
move |bounds, cx| {
|
||||
this.update(cx, |this, _cx| this.bounds = bounds)
|
||||
this.update(cx, |this, cx| {
|
||||
let bounds_changed = this.bounds != bounds;
|
||||
this.bounds = bounds;
|
||||
|
||||
if bounds_changed {
|
||||
this.left_dock.update(cx, |dock, cx| {
|
||||
dock.clamp_panel_size(bounds.size.width, cx)
|
||||
});
|
||||
|
||||
this.right_dock.update(cx, |dock, cx| {
|
||||
dock.clamp_panel_size(bounds.size.width, cx)
|
||||
});
|
||||
|
||||
this.bottom_dock.update(cx, |dock, cx| {
|
||||
dock.clamp_panel_size(
|
||||
bounds.size.height,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
}
|
||||
})
|
||||
},
|
||||
|_, _, _| {},
|
||||
)
|
||||
@@ -4836,42 +4856,27 @@ impl Render for Workspace {
|
||||
|workspace, e: &DragMoveEvent<DraggedDock>, cx| {
|
||||
match e.drag(cx).0 {
|
||||
DockPosition::Left => {
|
||||
let size = e.event.position.x
|
||||
- workspace.bounds.left();
|
||||
workspace.left_dock.update(
|
||||
resize_left_dock(
|
||||
e.event.position.x
|
||||
- workspace.bounds.left(),
|
||||
workspace,
|
||||
cx,
|
||||
|left_dock, cx| {
|
||||
left_dock.resize_active_panel(
|
||||
Some(size),
|
||||
cx,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
DockPosition::Right => {
|
||||
let size = workspace.bounds.right()
|
||||
- e.event.position.x;
|
||||
workspace.right_dock.update(
|
||||
resize_right_dock(
|
||||
workspace.bounds.right()
|
||||
- e.event.position.x,
|
||||
workspace,
|
||||
cx,
|
||||
|right_dock, cx| {
|
||||
right_dock.resize_active_panel(
|
||||
Some(size),
|
||||
cx,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
DockPosition::Bottom => {
|
||||
let size = workspace.bounds.bottom()
|
||||
- e.event.position.y;
|
||||
workspace.bottom_dock.update(
|
||||
resize_bottom_dock(
|
||||
workspace.bounds.bottom()
|
||||
- e.event.position.y,
|
||||
workspace,
|
||||
cx,
|
||||
|bottom_dock, cx| {
|
||||
bottom_dock.resize_active_panel(
|
||||
Some(size),
|
||||
cx,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -4959,6 +4964,40 @@ impl Render for Workspace {
|
||||
}
|
||||
}
|
||||
|
||||
fn resize_bottom_dock(
|
||||
new_size: Pixels,
|
||||
workspace: &mut Workspace,
|
||||
cx: &mut ViewContext<'_, Workspace>,
|
||||
) {
|
||||
let size = new_size.min(workspace.bounds.bottom() - RESIZE_HANDLE_SIZE);
|
||||
workspace.bottom_dock.update(cx, |bottom_dock, cx| {
|
||||
bottom_dock.resize_active_panel(Some(size), cx);
|
||||
});
|
||||
}
|
||||
|
||||
fn resize_right_dock(
|
||||
new_size: Pixels,
|
||||
workspace: &mut Workspace,
|
||||
cx: &mut ViewContext<'_, Workspace>,
|
||||
) {
|
||||
let size = new_size.max(workspace.bounds.left() - RESIZE_HANDLE_SIZE);
|
||||
workspace.right_dock.update(cx, |right_dock, cx| {
|
||||
right_dock.resize_active_panel(Some(size), cx);
|
||||
});
|
||||
}
|
||||
|
||||
fn resize_left_dock(
|
||||
new_size: Pixels,
|
||||
workspace: &mut Workspace,
|
||||
cx: &mut ViewContext<'_, Workspace>,
|
||||
) {
|
||||
let size = new_size.min(workspace.bounds.right() - RESIZE_HANDLE_SIZE);
|
||||
|
||||
workspace.left_dock.update(cx, |left_dock, cx| {
|
||||
left_dock.resize_active_panel(Some(size), cx);
|
||||
});
|
||||
}
|
||||
|
||||
impl WorkspaceStore {
|
||||
pub fn new(client: Arc<Client>, cx: &mut ModelContext<Self>) -> Self {
|
||||
Self {
|
||||
|
||||
@@ -19,7 +19,6 @@ activity_indicator.workspace = true
|
||||
anyhow.workspace = true
|
||||
assets.workspace = true
|
||||
assistant.workspace = true
|
||||
assistant_slash_command.workspace = true
|
||||
async-watch.workspace = true
|
||||
audio.workspace = true
|
||||
auto_update.workspace = true
|
||||
@@ -36,12 +35,12 @@ collab_ui.workspace = true
|
||||
collections.workspace = true
|
||||
command_palette.workspace = true
|
||||
command_palette_hooks.workspace = true
|
||||
context_servers.workspace = true
|
||||
copilot.workspace = true
|
||||
db.workspace = true
|
||||
diagnostics.workspace = true
|
||||
editor.workspace = true
|
||||
env_logger.workspace = true
|
||||
extension.workspace = true
|
||||
extension_host.workspace = true
|
||||
extensions_ui.workspace = true
|
||||
feature_flags.workspace = true
|
||||
@@ -56,11 +55,11 @@ go_to_line.workspace = true
|
||||
gpui = { workspace = true, features = ["wayland", "x11", "font-kit"] }
|
||||
http_client.workspace = true
|
||||
image_viewer.workspace = true
|
||||
indexed_docs.workspace = true
|
||||
inline_completion_button.workspace = true
|
||||
install_cli.workspace = true
|
||||
journal.workspace = true
|
||||
language.workspace = true
|
||||
language_extension.workspace = true
|
||||
language_model.workspace = true
|
||||
language_models.workspace = true
|
||||
language_selector.workspace = true
|
||||
@@ -109,6 +108,7 @@ tasks_ui.workspace = true
|
||||
telemetry_events.workspace = true
|
||||
terminal_view.workspace = true
|
||||
theme.workspace = true
|
||||
theme_extension.workspace = true
|
||||
theme_selector.workspace = true
|
||||
time.workspace = true
|
||||
toolchain_selector.workspace = true
|
||||
|
||||
@@ -5,16 +5,15 @@ mod reliability;
|
||||
mod zed;
|
||||
|
||||
use anyhow::{anyhow, Context as _, Result};
|
||||
use assistant_slash_command::SlashCommandRegistry;
|
||||
use chrono::Offset;
|
||||
use clap::{command, Parser};
|
||||
use cli::FORCE_CLI_MODE_ENV_VAR_NAME;
|
||||
use client::{parse_zed_link, Client, ProxySettings, UserStore};
|
||||
use collab_ui::channel_view::ChannelView;
|
||||
use context_servers::ContextServerFactoryRegistry;
|
||||
use db::kvp::{GLOBAL_KEY_VALUE_STORE, KEY_VALUE_STORE};
|
||||
use editor::Editor;
|
||||
use env_logger::Builder;
|
||||
use extension::ExtensionHostProxy;
|
||||
use fs::{Fs, RealFs};
|
||||
use futures::{future, StreamExt};
|
||||
use git::GitHostingProviderRegistry;
|
||||
@@ -23,7 +22,6 @@ use gpui::{
|
||||
VisualContext,
|
||||
};
|
||||
use http_client::{read_proxy_from_env, Uri};
|
||||
use indexed_docs::IndexedDocsRegistry;
|
||||
use language::LanguageRegistry;
|
||||
use log::LevelFilter;
|
||||
use reqwest_client::ReqwestClient;
|
||||
@@ -40,7 +38,6 @@ use settings::{
|
||||
};
|
||||
use simplelog::ConfigBuilder;
|
||||
use smol::process::Command;
|
||||
use snippet_provider::SnippetRegistry;
|
||||
use std::{
|
||||
env,
|
||||
fs::OpenOptions,
|
||||
@@ -284,6 +281,9 @@ fn main() {
|
||||
|
||||
OpenListener::set_global(cx, open_listener.clone());
|
||||
|
||||
extension::init(cx);
|
||||
let extension_host_proxy = ExtensionHostProxy::global(cx);
|
||||
|
||||
let client = Client::production(cx);
|
||||
cx.set_http_client(client.http_client().clone());
|
||||
let mut languages = LanguageRegistry::new(cx.background_executor().clone());
|
||||
@@ -317,6 +317,7 @@ fn main() {
|
||||
let node_runtime = NodeRuntime::new(client.http_client(), rx);
|
||||
|
||||
language::init(cx);
|
||||
language_extension::init(extension_host_proxy.clone(), languages.clone());
|
||||
languages::init(languages.clone(), node_runtime.clone(), cx);
|
||||
let user_store = cx.new_model(|cx| UserStore::new(client.clone(), cx));
|
||||
let workspace_store = cx.new_model(|cx| WorkspaceStore::new(client.clone(), cx));
|
||||
@@ -326,7 +327,6 @@ fn main() {
|
||||
zed::init(cx);
|
||||
project::Project::init(&client, cx);
|
||||
client::init(&client, cx);
|
||||
language::init(cx);
|
||||
let telemetry = client.telemetry();
|
||||
telemetry.start(
|
||||
system_id.as_ref().map(|id| id.to_string()),
|
||||
@@ -376,6 +376,11 @@ fn main() {
|
||||
|
||||
SystemAppearance::init(cx);
|
||||
theme::init(theme::LoadThemes::All(Box::new(Assets)), cx);
|
||||
theme_extension::init(
|
||||
extension_host_proxy.clone(),
|
||||
ThemeRegistry::global(cx),
|
||||
cx.background_executor().clone(),
|
||||
);
|
||||
command_palette::init(cx);
|
||||
let copilot_language_server_id = app_state.languages.next_language_server_id();
|
||||
copilot::init(
|
||||
@@ -407,17 +412,8 @@ fn main() {
|
||||
app_state.client.telemetry().clone(),
|
||||
cx,
|
||||
);
|
||||
let api = extensions_ui::ConcreteExtensionRegistrationHooks::new(
|
||||
ThemeRegistry::global(cx),
|
||||
SlashCommandRegistry::global(cx),
|
||||
IndexedDocsRegistry::global(cx),
|
||||
SnippetRegistry::global(cx),
|
||||
app_state.languages.clone(),
|
||||
ContextServerFactoryRegistry::global(cx),
|
||||
cx,
|
||||
);
|
||||
extension_host::init(
|
||||
api,
|
||||
extension_host_proxy,
|
||||
app_state.fs.clone(),
|
||||
app_state.client.clone(),
|
||||
app_state.node_runtime.clone(),
|
||||
|
||||
@@ -5,9 +5,22 @@ Dart support is available through the [Dart extension](https://github.com/zed-ex
|
||||
- Tree Sitter: [UserNobody14/tree-sitter-dart](https://github.com/UserNobody14/tree-sitter-dart)
|
||||
- Language Server: [dart language-server](https://github.com/dart-lang/sdk)
|
||||
|
||||
## Pre-requisites
|
||||
|
||||
You will need to install the Dart SDK.
|
||||
|
||||
You can install dart from [dart.dev/get-dart](https://dart.dev/get-dart) or via the [Flutter Version Management CLI (fvm)](https://fvm.app/documentation/getting-started/installation)
|
||||
|
||||
## Configuration
|
||||
|
||||
The `dart` binary can be configured in a Zed settings file with:
|
||||
The dart extension requires no configuration if you have `dart` in your path:
|
||||
|
||||
```sh
|
||||
which dart
|
||||
dart --version
|
||||
```
|
||||
|
||||
If you would like to use a specific dart binary or use dart via FVM you can specify the `dart` binary in your Zed settings.jsons file:
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -22,7 +35,4 @@ The `dart` binary can be configured in a Zed settings file with:
|
||||
}
|
||||
```
|
||||
|
||||
<!--
|
||||
TBD: Document Dart. pubspec.yaml
|
||||
- https://github.com/dart-lang/sdk/blob/main/pkg/analysis_server/tool/lsp_spec/README.md
|
||||
-->
|
||||
Please see the Dart documentation for more information on [dart language-server capabilities](https://github.com/dart-lang/sdk/blob/main/pkg/analysis_server/tool/lsp_spec/README.md).
|
||||
|
||||
@@ -125,7 +125,7 @@ Each connection tries to run the development server in proxy mode. This mode wil
|
||||
|
||||
In the case that reconnecting fails, the daemon will not be re-used. That said, unsaved changes are by default persisted locally, so that you do not lose work. You can always reconnect to the project at a later date and Zed will restore unsaved changes.
|
||||
|
||||
If you are struggling with connection issues, you should be able to see more information in the Zed log `cmd-shift-p Open Log`. If you are seeing things that are unexpected, please file a [GitHub issue](https://github.com/zed-industries/zed/issues/new) or reach out in the #remoting-feedback channel in the [Zed Discord](https://discord.gg/zed-community).
|
||||
If you are struggling with connection issues, you should be able to see more information in the Zed log `cmd-shift-p Open Log`. If you are seeing things that are unexpected, please file a [GitHub issue](https://github.com/zed-industries/zed/issues/new) or reach out in the #remoting-feedback channel in the [Zed Discord](https://zed.dev/community-links).
|
||||
|
||||
## Supported SSH Options
|
||||
|
||||
@@ -152,4 +152,4 @@ Note that we deliberately disallow some options (for example `-t` or `-T`) that
|
||||
|
||||
## Feedback
|
||||
|
||||
Please join the #remoting-feedback channel in the [Zed Discord](https://discord.gg/zed-community).
|
||||
Please join the #remoting-feedback channel in the [Zed Discord](https://zed.dev/community-links).
|
||||
|
||||
@@ -41,7 +41,11 @@ Zed supports ways to spawn (and rerun) commands using its integrated terminal to
|
||||
// "args": ["--login"]
|
||||
// }
|
||||
// }
|
||||
"shell": "system"
|
||||
"shell": "system",
|
||||
// Whether to show the task line in the output of the spawned task, defaults to `true`.
|
||||
"show_summary": true,
|
||||
// Whether to show the command line in the output of the spawned task, defaults to `true`.
|
||||
"show_output": true
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
@@ -10,4 +10,4 @@ Zed Employees are not currently working on the Windows build.
|
||||
However, we welcome contributions from the community to improve Windows support.
|
||||
|
||||
- [GitHub Issues with 'Windows' label](https://github.com/zed-industries/zed/issues?q=is%3Aissue+is%3Aopen+label%3Awindows)
|
||||
- [Zed Community Discord](https://discord.gg/zed-community) -> `#windows-port`
|
||||
- [Zed Community Discord](https://zed.dev/community-links) -> `#windows-port`
|
||||
|
||||
Reference in New Issue
Block a user