Detailed changes
@@ -436,6 +436,7 @@ dependencies = [
"derive_more",
"futures 0.3.28",
"gpui",
+ "language",
"parking_lot",
]
@@ -3784,6 +3785,7 @@ name = "extension"
version = "0.1.0"
dependencies = [
"anyhow",
+ "assistant_slash_command",
"async-compression",
"async-tar",
"async-trait",
@@ -13249,7 +13251,7 @@ dependencies = [
name = "zed_gleam"
version = "0.1.3"
dependencies = [
- "zed_extension_api 0.0.6",
+ "zed_extension_api 0.0.7",
]
[[package]]
@@ -237,6 +237,7 @@ pub fn init(client: Arc<Client>, cx: &mut AppContext) {
cx.set_global(Assistant::default());
AssistantSettings::register(cx);
completion_provider::init(client, cx);
+ assistant_slash_command::init(cx);
assistant_panel::init(cx);
CommandPaletteFilter::update_global(cx, |filter, _cx| {
@@ -43,13 +43,14 @@ use gpui::{
UniformListScrollHandle, View, ViewContext, VisualContext, WeakModel, WeakView, WhiteSpace,
WindowContext,
};
+use language::LspAdapterDelegate;
use language::{
language_settings::SoftWrap, AutoindentMode, Buffer, BufferSnapshot, LanguageRegistry,
OffsetRangeExt as _, Point, ToOffset as _, ToPoint as _,
};
use multi_buffer::MultiBufferRow;
use parking_lot::Mutex;
-use project::{Project, ProjectTransaction};
+use project::{Project, ProjectLspAdapterDelegate, ProjectTransaction};
use search::{buffer_search::DivRegistrar, BufferSearchBar};
use settings::Settings;
use std::{
@@ -205,8 +206,7 @@ impl AssistantPanel {
})
.detach();
- let slash_command_registry = SlashCommandRegistry::new();
-
+ let slash_command_registry = SlashCommandRegistry::global(cx);
let window = cx.window_handle().downcast::<Workspace>();
slash_command_registry.register_command(file_command::FileSlashCommand::new(
@@ -1129,6 +1129,13 @@ impl AssistantPanel {
let slash_commands = self.slash_commands.clone();
let languages = self.languages.clone();
let telemetry = self.telemetry.clone();
+
+ let lsp_adapter_delegate = workspace
+ .update(cx, |workspace, cx| {
+ make_lsp_adapter_delegate(workspace.project(), cx)
+ })
+ .log_err();
+
cx.spawn(|this, mut cx| async move {
let saved_conversation = SavedConversation::load(&path, fs.as_ref()).await?;
let model = this.update(&mut cx, |this, _| this.model.clone())?;
@@ -1139,6 +1146,7 @@ impl AssistantPanel {
languages,
slash_commands,
Some(telemetry),
+ lsp_adapter_delegate,
&mut cx,
)
.await?;
@@ -1484,6 +1492,7 @@ pub struct Conversation {
telemetry: Option<Arc<Telemetry>>,
slash_command_registry: Arc<SlashCommandRegistry>,
language_registry: Arc<LanguageRegistry>,
+ lsp_adapter_delegate: Option<Arc<dyn LspAdapterDelegate>>,
}
impl EventEmitter<ConversationEvent> for Conversation {}
@@ -1494,6 +1503,7 @@ impl Conversation {
language_registry: Arc<LanguageRegistry>,
slash_command_registry: Arc<SlashCommandRegistry>,
telemetry: Option<Arc<Telemetry>>,
+ lsp_adapter_delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut ModelContext<Self>,
) -> Self {
let buffer = cx.new_model(|cx| {
@@ -1526,6 +1536,7 @@ impl Conversation {
telemetry,
slash_command_registry,
language_registry,
+ lsp_adapter_delegate,
};
let message = MessageAnchor {
@@ -1569,6 +1580,7 @@ impl Conversation {
}
}
+ #[allow(clippy::too_many_arguments)]
async fn deserialize(
saved_conversation: SavedConversation,
model: LanguageModel,
@@ -1576,6 +1588,7 @@ impl Conversation {
language_registry: Arc<LanguageRegistry>,
slash_command_registry: Arc<SlashCommandRegistry>,
telemetry: Option<Arc<Telemetry>>,
+ lsp_adapter_delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut AsyncAppContext,
) -> Result<Model<Self>> {
let id = match saved_conversation.id {
@@ -1635,6 +1648,7 @@ impl Conversation {
telemetry,
language_registry,
slash_command_registry,
+ lsp_adapter_delegate,
};
this.set_language(cx);
this.reparse_edit_suggestions(cx);
@@ -1850,7 +1864,13 @@ impl Conversation {
buffer.anchor_after(offset)..buffer.anchor_before(line_end_offset);
let argument = call.argument.map(|range| &line[range]);
- let invocation = command.run(argument, cx);
+ let invocation = command.run(
+ argument,
+ this.lsp_adapter_delegate
+ .clone()
+ .expect("no LspAdapterDelegate present when invoking command"),
+ cx,
+ );
new_calls.push(SlashCommandCall {
name,
@@ -2728,12 +2748,16 @@ impl ConversationEditor {
cx: &mut ViewContext<Self>,
) -> Self {
let telemetry = workspace.read(cx).client().telemetry().clone();
+ let project = workspace.read(cx).project().clone();
+ let lsp_adapter_delegate = make_lsp_adapter_delegate(&project, cx);
+
let conversation = cx.new_model(|cx| {
Conversation::new(
model,
language_registry,
slash_command_registry,
Some(telemetry),
+ Some(lsp_adapter_delegate),
cx,
)
});
@@ -3907,6 +3931,20 @@ fn merge_ranges(ranges: &mut Vec<Range<Anchor>>, buffer: &MultiBufferSnapshot) {
}
}
+fn make_lsp_adapter_delegate(
+ project: &Model<Project>,
+ cx: &mut AppContext,
+) -> Arc<dyn LspAdapterDelegate> {
+ project.update(cx, |project, cx| {
+ // TODO: Find the right worktree.
+ let worktree = project
+ .worktrees()
+ .next()
+ .expect("expected at least one worktree");
+ ProjectLspAdapterDelegate::new(project, &worktree, cx)
+ })
+}
+
#[cfg(test)]
mod tests {
use std::{cell::RefCell, path::Path, rc::Rc};
@@ -3935,6 +3973,7 @@ mod tests {
registry,
Default::default(),
None,
+ None,
cx,
)
});
@@ -4074,6 +4113,7 @@ mod tests {
registry,
Default::default(),
None,
+ None,
cx,
)
});
@@ -4180,6 +4220,7 @@ mod tests {
registry,
Default::default(),
None,
+ None,
cx,
)
});
@@ -4292,6 +4333,15 @@ mod tests {
prompt_library.clone(),
));
+ let lsp_adapter_delegate = project.update(cx, |project, cx| {
+ // TODO: Find the right worktree.
+ let worktree = project
+ .worktrees()
+ .next()
+ .expect("expected at least one worktree");
+ ProjectLspAdapterDelegate::new(project, &worktree, cx)
+ });
+
let registry = Arc::new(LanguageRegistry::test(cx.executor()));
let conversation = cx.new_model(|cx| {
Conversation::new(
@@ -4299,6 +4349,7 @@ mod tests {
registry.clone(),
slash_command_registry,
None,
+ Some(lsp_adapter_delegate),
cx,
)
});
@@ -4599,6 +4650,7 @@ mod tests {
registry.clone(),
Default::default(),
None,
+ None,
cx,
)
});
@@ -4642,6 +4694,7 @@ mod tests {
registry.clone(),
Default::default(),
None,
+ None,
&mut cx.to_async(),
)
.await
@@ -1,3 +1,4 @@
+use std::sync::Arc;
use std::{borrow::Cow, cell::Cell, rc::Rc};
use anyhow::{anyhow, Result};
@@ -5,6 +6,7 @@ use collections::HashMap;
use editor::Editor;
use futures::channel::oneshot;
use gpui::{AppContext, Entity, Subscription, Task, WindowHandle};
+use language::LspAdapterDelegate;
use workspace::{Event as WorkspaceEvent, Workspace};
use super::{SlashCommand, SlashCommandCleanup, SlashCommandInvocation};
@@ -41,7 +43,12 @@ impl SlashCommand for CurrentFileSlashCommand {
false
}
- fn run(&self, _argument: Option<&str>, cx: &mut AppContext) -> SlashCommandInvocation {
+ fn run(
+ self: Arc<Self>,
+ _argument: Option<&str>,
+ _delegate: Arc<dyn LspAdapterDelegate>,
+ cx: &mut AppContext,
+ ) -> SlashCommandInvocation {
let (invalidate_tx, invalidate_rx) = oneshot::channel();
let invalidate_tx = Rc::new(Cell::new(Some(invalidate_tx)));
let mut subscriptions: Vec<Subscription> = Vec::new();
@@ -3,6 +3,7 @@ use anyhow::Result;
use futures::channel::oneshot;
use fuzzy::PathMatch;
use gpui::{AppContext, Model, Task};
+use language::LspAdapterDelegate;
use project::{PathMatchCandidateSet, Project};
use std::{
path::Path,
@@ -96,7 +97,12 @@ impl SlashCommand for FileSlashCommand {
})
}
- fn run(&self, argument: Option<&str>, cx: &mut AppContext) -> SlashCommandInvocation {
+ fn run(
+ self: Arc<Self>,
+ argument: Option<&str>,
+ _delegate: Arc<dyn LspAdapterDelegate>,
+ cx: &mut AppContext,
+ ) -> SlashCommandInvocation {
let project = self.project.read(cx);
let Some(argument) = argument else {
return SlashCommandInvocation {
@@ -4,6 +4,7 @@ use anyhow::{anyhow, Context, Result};
use futures::channel::oneshot;
use fuzzy::StringMatchCandidate;
use gpui::{AppContext, Task};
+use language::LspAdapterDelegate;
use std::sync::{atomic::AtomicBool, Arc};
pub(crate) struct PromptSlashCommand {
@@ -65,7 +66,12 @@ impl SlashCommand for PromptSlashCommand {
})
}
- fn run(&self, title: Option<&str>, cx: &mut AppContext) -> SlashCommandInvocation {
+ fn run(
+ self: Arc<Self>,
+ title: Option<&str>,
+ _delegate: Arc<dyn LspAdapterDelegate>,
+ cx: &mut AppContext,
+ ) -> SlashCommandInvocation {
let Some(title) = title else {
return SlashCommandInvocation {
output: Task::ready(Err(anyhow!("missing prompt name"))),
@@ -17,4 +17,5 @@ collections.workspace = true
derive_more.workspace = true
futures.workspace = true
gpui.workspace = true
+language.workspace = true
parking_lot.workspace = true
@@ -6,6 +6,7 @@ use std::sync::Arc;
use anyhow::Result;
use futures::channel::oneshot;
use gpui::{AppContext, Task};
+use language::LspAdapterDelegate;
pub use slash_command_registry::*;
@@ -23,7 +24,17 @@ pub trait SlashCommand: 'static + Send + Sync {
cx: &mut AppContext,
) -> Task<Result<Vec<String>>>;
fn requires_argument(&self) -> bool;
- fn run(&self, argument: Option<&str>, cx: &mut AppContext) -> SlashCommandInvocation;
+ fn run(
+ self: Arc<Self>,
+ argument: Option<&str>,
+ // TODO: We're just using the `LspAdapterDelegate` here because that is
+ // what the extension API is already expecting.
+ //
+ // It may be that `LspAdapterDelegate` needs a more general name, or
+ // perhaps another kind of delegate is needed here.
+ delegate: Arc<dyn LspAdapterDelegate>,
+ cx: &mut AppContext,
+ ) -> SlashCommandInvocation;
}
pub struct SlashCommandInvocation {
@@ -14,6 +14,7 @@ doctest = false
[dependencies]
anyhow.workspace = true
+assistant_slash_command.workspace = true
async-compression.workspace = true
async-tar.workspace = true
async-trait.workspace = true
@@ -74,6 +74,8 @@ pub struct ExtensionManifest {
pub grammars: BTreeMap<Arc<str>, GrammarManifestEntry>,
#[serde(default)]
pub language_servers: BTreeMap<LanguageServerName, LanguageServerManifestEntry>,
+ #[serde(default)]
+ pub slash_commands: BTreeMap<Arc<str>, SlashCommandManifestEntry>,
}
#[derive(Clone, Default, PartialEq, Eq, Debug, Deserialize, Serialize)]
@@ -128,6 +130,12 @@ impl LanguageServerManifestEntry {
}
}
+#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]
+pub struct SlashCommandManifestEntry {
+ pub description: String,
+ pub requires_argument: bool,
+}
+
impl ExtensionManifest {
pub async fn load(fs: Arc<dyn Fs>, extension_dir: &Path) -> Result<Self> {
let extension_name = extension_dir
@@ -190,5 +198,6 @@ fn manifest_from_old_manifest(
.map(|grammar_name| (grammar_name, Default::default()))
.collect(),
language_servers: Default::default(),
+ slash_commands: BTreeMap::default(),
}
}
@@ -0,0 +1,85 @@
+use std::sync::atomic::AtomicBool;
+use std::sync::Arc;
+
+use anyhow::{anyhow, Result};
+use assistant_slash_command::{SlashCommand, SlashCommandCleanup, SlashCommandInvocation};
+use futures::channel::oneshot;
+use futures::FutureExt;
+use gpui::{AppContext, Task};
+use language::LspAdapterDelegate;
+use wasmtime_wasi::WasiView;
+
+use crate::wasm_host::{WasmExtension, WasmHost};
+
+pub struct ExtensionSlashCommand {
+ pub(crate) extension: WasmExtension,
+ #[allow(unused)]
+ pub(crate) host: Arc<WasmHost>,
+ pub(crate) command: crate::wit::SlashCommand,
+}
+
+impl SlashCommand for ExtensionSlashCommand {
+ fn name(&self) -> String {
+ self.command.name.clone()
+ }
+
+ fn description(&self) -> String {
+ self.command.description.clone()
+ }
+
+ fn requires_argument(&self) -> bool {
+ self.command.requires_argument
+ }
+
+ fn complete_argument(
+ &self,
+ _query: String,
+ _cancel: Arc<AtomicBool>,
+ _cx: &mut AppContext,
+ ) -> Task<Result<Vec<String>>> {
+ Task::ready(Ok(Vec::new()))
+ }
+
+ fn run(
+ self: Arc<Self>,
+ argument: Option<&str>,
+ delegate: Arc<dyn LspAdapterDelegate>,
+ cx: &mut AppContext,
+ ) -> SlashCommandInvocation {
+ let argument = argument.map(|arg| arg.to_string());
+
+ let output = cx.background_executor().spawn(async move {
+ let output = self
+ .extension
+ .call({
+ let this = self.clone();
+ move |extension, store| {
+ async move {
+ let resource = store.data_mut().table().push(delegate)?;
+ let output = extension
+ .call_run_slash_command(
+ store,
+ &this.command,
+ argument.as_deref(),
+ resource,
+ )
+ .await?
+ .map_err(|e| anyhow!("{}", e))?;
+
+ anyhow::Ok(output)
+ }
+ .boxed()
+ }
+ })
+ .await?;
+
+ output.ok_or_else(|| anyhow!("no output from command: {}", self.command.name))
+ });
+
+ SlashCommandInvocation {
+ output,
+ invalidated: oneshot::channel().1,
+ cleanup: SlashCommandCleanup::default(),
+ }
+ }
+}
@@ -2,14 +2,17 @@ pub mod extension_builder;
mod extension_lsp_adapter;
mod extension_manifest;
mod extension_settings;
+mod extension_slash_command;
mod wasm_host;
#[cfg(test)]
mod extension_store_test;
use crate::extension_manifest::SchemaVersion;
+use crate::extension_slash_command::ExtensionSlashCommand;
use crate::{extension_lsp_adapter::ExtensionLspAdapter, wasm_host::wit};
use anyhow::{anyhow, bail, Context as _, Result};
+use assistant_slash_command::SlashCommandRegistry;
use async_compression::futures::bufread::GzipDecoder;
use async_tar::Archive;
use client::{telemetry::Telemetry, Client, ExtensionMetadata, GetExtensionsResponse};
@@ -107,6 +110,7 @@ pub struct ExtensionStore {
index_path: PathBuf,
language_registry: Arc<LanguageRegistry>,
theme_registry: Arc<ThemeRegistry>,
+ slash_command_registry: Arc<SlashCommandRegistry>,
modified_extensions: HashSet<Arc<str>>,
wasm_host: Arc<WasmHost>,
wasm_extensions: Vec<(Arc<ExtensionManifest>, WasmExtension)>,
@@ -183,6 +187,7 @@ pub fn init(
node_runtime,
language_registry,
theme_registry,
+ SlashCommandRegistry::global(cx),
cx,
)
});
@@ -215,6 +220,7 @@ impl ExtensionStore {
node_runtime: Arc<dyn NodeRuntime>,
language_registry: Arc<LanguageRegistry>,
theme_registry: Arc<ThemeRegistry>,
+ slash_command_registry: Arc<SlashCommandRegistry>,
cx: &mut ModelContext<Self>,
) -> Self {
let work_dir = extensions_dir.join("work");
@@ -245,6 +251,7 @@ impl ExtensionStore {
telemetry,
language_registry,
theme_registry,
+ slash_command_registry,
reload_tx,
tasks: Vec::new(),
};
@@ -1169,6 +1176,19 @@ impl ExtensionStore {
);
}
}
+
+ for (slash_command_name, slash_command) in &manifest.slash_commands {
+ this.slash_command_registry
+ .register_command(ExtensionSlashCommand {
+ command: crate::wit::SlashCommand {
+ name: slash_command_name.to_string(),
+ description: slash_command.description.to_string(),
+ requires_argument: slash_command.requires_argument,
+ },
+ extension: wasm_extension.clone(),
+ host: this.wasm_host.clone(),
+ });
+ }
}
this.wasm_extensions.extend(wasm_extensions);
ThemeSettings::reload_current_theme(cx)
@@ -5,6 +5,7 @@ use crate::{
ExtensionIndexThemeEntry, ExtensionManifest, ExtensionStore, GrammarManifestEntry,
RELOAD_DEBOUNCE_DURATION,
};
+use assistant_slash_command::SlashCommandRegistry;
use async_compression::futures::bufread::GzipEncoder;
use collections::BTreeMap;
use fs::{FakeFs, Fs, RealFs};
@@ -156,6 +157,7 @@ async fn test_extension_store(cx: &mut TestAppContext) {
.into_iter()
.collect(),
language_servers: BTreeMap::default(),
+ slash_commands: BTreeMap::default(),
}),
dev: false,
},
@@ -179,6 +181,7 @@ async fn test_extension_store(cx: &mut TestAppContext) {
languages: Default::default(),
grammars: BTreeMap::default(),
language_servers: BTreeMap::default(),
+ slash_commands: BTreeMap::default(),
}),
dev: false,
},
@@ -250,6 +253,7 @@ async fn test_extension_store(cx: &mut TestAppContext) {
let language_registry = Arc::new(LanguageRegistry::test(cx.executor()));
let theme_registry = Arc::new(ThemeRegistry::new(Box::new(())));
+ let slash_command_registry = SlashCommandRegistry::new();
let node_runtime = FakeNodeRuntime::new();
let store = cx.new_model(|cx| {
@@ -262,6 +266,7 @@ async fn test_extension_store(cx: &mut TestAppContext) {
node_runtime.clone(),
language_registry.clone(),
theme_registry.clone(),
+ slash_command_registry.clone(),
cx,
)
});
@@ -333,6 +338,7 @@ async fn test_extension_store(cx: &mut TestAppContext) {
languages: Default::default(),
grammars: BTreeMap::default(),
language_servers: BTreeMap::default(),
+ slash_commands: BTreeMap::default(),
}),
dev: false,
},
@@ -382,6 +388,7 @@ async fn test_extension_store(cx: &mut TestAppContext) {
node_runtime.clone(),
language_registry.clone(),
theme_registry.clone(),
+ slash_command_registry,
cx,
)
});
@@ -460,6 +467,7 @@ async fn test_extension_store_with_gleam_extension(cx: &mut TestAppContext) {
let language_registry = project.read_with(cx, |project, _cx| project.languages().clone());
let theme_registry = Arc::new(ThemeRegistry::new(Box::new(())));
+ let slash_command_registry = SlashCommandRegistry::new();
let node_runtime = FakeNodeRuntime::new();
let mut status_updates = language_registry.language_server_binary_statuses();
@@ -541,6 +549,7 @@ async fn test_extension_store_with_gleam_extension(cx: &mut TestAppContext) {
node_runtime,
language_registry.clone(),
theme_registry.clone(),
+ slash_command_registry,
cx,
)
});
@@ -19,7 +19,7 @@ use wasmtime::{
pub use latest::CodeLabelSpanLiteral;
pub use latest::{
zed::extension::lsp::{Completion, CompletionKind, InsertTextFormat, Symbol, SymbolKind},
- CodeLabel, CodeLabelSpan, Command, Range,
+ CodeLabel, CodeLabelSpan, Command, Range, SlashCommand,
};
pub use since_v0_0_4::LanguageServerConfig;
@@ -255,6 +255,22 @@ impl Extension {
Extension::V001(_) | Extension::V004(_) => Ok(Ok(Vec::new())),
}
}
+
+ pub async fn call_run_slash_command(
+ &self,
+ store: &mut Store<WasmState>,
+ command: &SlashCommand,
+ argument: Option<&str>,
+ resource: Resource<Arc<dyn LspAdapterDelegate>>,
+ ) -> Result<Result<Option<String>, String>> {
+ match self {
+ Extension::V007(ext) => {
+ ext.call_run_slash_command(store, command, argument, resource)
+ .await
+ }
+ Extension::V001(_) | Extension::V004(_) | Extension::V006(_) => Ok(Ok(None)),
+ }
+ }
}
trait ToWasmtimeResult<T> {
@@ -222,6 +222,9 @@ impl platform::Host for WasmState {
}
}
+#[async_trait]
+impl slash_command::Host for WasmState {}
+
#[async_trait]
impl ExtensionImports for WasmState {
async fn get_settings(
@@ -24,6 +24,7 @@ pub use wit::{
npm_package_latest_version,
},
zed::extension::platform::{current_platform, Architecture, Os},
+ zed::extension::slash_command::SlashCommand,
CodeLabel, CodeLabelSpan, CodeLabelSpanLiteral, Command, DownloadedFileType, EnvVars,
LanguageServerInstallationStatus, Range, Worktree,
};
@@ -104,6 +105,16 @@ pub trait Extension: Send + Sync {
) -> Option<CodeLabel> {
None
}
+
+ /// Runs the given slash command.
+ fn run_slash_command(
+ &self,
+ _command: SlashCommand,
+ _argument: Option<String>,
+ _worktree: &Worktree,
+ ) -> Result<Option<String>, String> {
+ Ok(None)
+ }
}
/// Registers the provided type as a Zed extension.
@@ -139,6 +150,8 @@ static mut EXTENSION: Option<Box<dyn Extension>> = None;
pub static ZED_API_VERSION: [u8; 6] = *include_bytes!(concat!(env!("OUT_DIR"), "/version_bytes"));
mod wit {
+ #![allow(clippy::too_many_arguments)]
+
wit_bindgen::generate!({
skip: ["init-extension"],
path: "./wit/since_v0.0.7",
@@ -209,6 +222,14 @@ impl wit::Guest for Component {
}
Ok(labels)
}
+
+ fn run_slash_command(
+ command: SlashCommand,
+ argument: Option<String>,
+ worktree: &Worktree,
+ ) -> Result<Option<String>, String> {
+ extension().run_slash_command(command, argument, worktree)
+ }
}
/// The ID of a language server.
@@ -6,6 +6,7 @@ world extension {
import nodejs;
use lsp.{completion, symbol};
+ use slash-command.{slash-command};
/// Initializes the extension.
export init-extension: func();
@@ -127,4 +128,7 @@ world extension {
export labels-for-completions: func(language-server-id: string, completions: list<completion>) -> result<list<option<code-label>>, string>;
export labels-for-symbols: func(language-server-id: string, symbols: list<symbol>) -> result<list<option<code-label>>, string>;
+
+ /// Runs the provided slash command.
+ export run-slash-command: func(command: slash-command, argument: option<string>, worktree: borrow<worktree>) -> result<option<string>, string>;
}
@@ -0,0 +1,11 @@
+interface slash-command {
+ /// A slash command for use in the Assistant.
+ record slash-command {
+ /// The name of the slash command.
+ name: string,
+ /// The description of the slash command.
+ description: string,
+ /// Whether this slash command requires an argument.
+ requires-argument: bool,
+ }
+}
@@ -11175,7 +11175,7 @@ impl<P: AsRef<Path>> From<(WorktreeId, P)> for ProjectPath {
}
}
-struct ProjectLspAdapterDelegate {
+pub struct ProjectLspAdapterDelegate {
project: WeakModel<Project>,
worktree: worktree::Snapshot,
fs: Arc<dyn Fs>,
@@ -11185,7 +11185,11 @@ struct ProjectLspAdapterDelegate {
}
impl ProjectLspAdapterDelegate {
- fn new(project: &Project, worktree: &Model<Worktree>, cx: &ModelContext<Project>) -> Arc<Self> {
+ pub fn new(
+ project: &Project,
+ worktree: &Model<Worktree>,
+ cx: &ModelContext<Project>,
+ ) -> Arc<Self> {
Arc::new(Self {
project: cx.weak_model(),
worktree: worktree.read(cx).snapshot(),
@@ -13,4 +13,4 @@ path = "src/gleam.rs"
crate-type = ["cdylib"]
[dependencies]
-zed_extension_api = "0.0.6"
+zed_extension_api = { path = "../../crates/extension_api" }
@@ -13,3 +13,7 @@ language = "Gleam"
[grammars.gleam]
repository = "https://github.com/gleam-lang/tree-sitter-gleam"
commit = "8432ffe32ccd360534837256747beb5b1c82fca1"
+
+[slash_commands.gleam-project]
+description = "Returns information about the current Gleam project."
+requires_argument = false
@@ -1,6 +1,6 @@
use std::fs;
use zed::lsp::CompletionKind;
-use zed::{CodeLabel, CodeLabelSpan, LanguageServerId};
+use zed::{CodeLabel, CodeLabelSpan, LanguageServerId, SlashCommand};
use zed_extension_api::{self as zed, Result};
struct GleamExtension {
@@ -142,6 +142,28 @@ impl zed::Extension for GleamExtension {
code,
})
}
+
+ fn run_slash_command(
+ &self,
+ command: SlashCommand,
+ _argument: Option<String>,
+ worktree: &zed::Worktree,
+ ) -> Result<Option<String>, String> {
+ match command.name.as_str() {
+ "gleam-project" => {
+ let mut message = String::new();
+ message.push_str("You are in a Gleam project.\n");
+
+ if let Some(gleam_toml) = worktree.read_text_file("gleam.toml").ok() {
+ message.push_str("The `gleam.toml` is as follows:\n");
+ message.push_str(&gleam_toml);
+ }
+
+ Ok(Some(message))
+ }
+ command => Err(format!("unknown slash command: \"{command}\"")),
+ }
+ }
}
zed::register_extension!(GleamExtension);