Compare commits

...

6 Commits

Author SHA1 Message Date
Marshall Bowers
c822188a53 Replace license with symlink 2024-09-14 15:53:02 -04:00
Marshall Bowers
32fe23a182 Remove blank line 2024-09-14 15:52:22 -04:00
Marshall Bowers
b337f419d3 Merge branch 'main' into buffer-extensions 2024-09-14 15:51:33 -04:00
Conrad Irwin
548878d8a0 Add support for extensions that edit buffers
Co-Authored-by: Zach <zachary.tc.@gmail.com>
Co-Authored-by: Marshall <marshall@zed.dev>
2024-09-13 15:55:57 -04:00
Conrad Irwin
cdb88f52b7 temp 2024-09-13 13:44:58 -04:00
Conrad Irwin
14b4dc9a3a it compiles 2024-09-13 13:15:32 -04:00
33 changed files with 633 additions and 161 deletions

10
Cargo.lock generated
View File

@@ -4060,6 +4060,7 @@ dependencies = [
"client",
"collections",
"ctor",
"editor",
"env_logger",
"fs",
"futures 0.3.30",
@@ -4070,6 +4071,7 @@ dependencies = [
"language",
"log",
"lsp",
"multi_buffer",
"node_runtime",
"parking_lot",
"paths",
@@ -14555,6 +14557,14 @@ dependencies = [
"zed_extension_api 0.1.0",
]
[[package]]
name = "zed_uppercase"
version = "0.0.5"
dependencies = [
"serde_json",
"zed_extension_api 0.2.0",
]
[[package]]
name = "zed_vue"
version = "0.1.0"

View File

@@ -159,6 +159,7 @@ members = [
"extensions/terraform",
"extensions/test-extension",
"extensions/toml",
"extensions/uppercase",
"extensions/uiua",
"extensions/vue",
"extensions/zig",

View File

@@ -210,10 +210,9 @@ impl CommandPaletteDelegate {
positions,
}) = intercept_result
{
if let Some(idx) = matches
.iter()
.position(|m| commands[m.candidate_id].action.type_id() == action.type_id())
{
if let Some(idx) = matches.iter().position(|m| {
commands[m.candidate_id].action.action_type_id() == action.action_type_id()
}) {
matches.remove(idx);
}
commands.push(Command {

View File

@@ -2,11 +2,9 @@
#![deny(missing_docs)]
use std::any::TypeId;
use collections::HashSet;
use derive_more::{Deref, DerefMut};
use gpui::{Action, AppContext, BorrowAppContext, Global};
use gpui::{Action, ActionTypeId, AppContext, BorrowAppContext, Global};
/// Initializes the command palette hooks.
pub fn init(cx: &mut AppContext) {
@@ -18,7 +16,7 @@ pub fn init(cx: &mut AppContext) {
#[derive(Default)]
pub struct CommandPaletteFilter {
hidden_namespaces: HashSet<&'static str>,
hidden_action_types: HashSet<TypeId>,
hidden_action_types: HashSet<ActionTypeId>,
}
#[derive(Deref, DerefMut, Default)]
@@ -52,7 +50,7 @@ impl CommandPaletteFilter {
let namespace = name.split("::").next().unwrap_or("malformed action name");
self.hidden_namespaces.contains(namespace)
|| self.hidden_action_types.contains(&action.type_id())
|| self.hidden_action_types.contains(&action.action_type_id())
}
/// Hides all actions in the given namespace.
@@ -66,12 +64,13 @@ impl CommandPaletteFilter {
}
/// Hides all actions with the given types.
pub fn hide_action_types(&mut self, action_types: &[TypeId]) {
self.hidden_action_types.extend(action_types);
pub fn hide_action_types(&mut self, action_types: &[ActionTypeId]) {
self.hidden_action_types
.extend(action_types.iter().cloned());
}
/// Shows all actions with the given types.
pub fn show_action_types<'a>(&mut self, action_types: impl Iterator<Item = &'a TypeId>) {
pub fn show_action_types<'a>(&mut self, action_types: impl Iterator<Item = &'a ActionTypeId>) {
for action_type in action_types {
self.hidden_action_types.remove(action_type);
}

View File

@@ -11,8 +11,8 @@ use collections::{HashMap, HashSet};
use command_palette_hooks::CommandPaletteFilter;
use futures::{channel::oneshot, future::Shared, Future, FutureExt, TryFutureExt};
use gpui::{
actions, AppContext, AsyncAppContext, Context, Entity, EntityId, EventEmitter, Global, Model,
ModelContext, Task, WeakModel,
actions, ActionTypeId, AppContext, AsyncAppContext, Context, Entity, EntityId, EventEmitter,
Global, Model, ModelContext, Task, WeakModel,
};
use http_client::github::latest_github_release;
use http_client::HttpClient;
@@ -69,13 +69,13 @@ pub fn init(
Copilot::set_global(copilot.clone(), cx);
cx.observe(&copilot, |handle, cx| {
let copilot_action_types = [
TypeId::of::<Suggest>(),
TypeId::of::<NextSuggestion>(),
TypeId::of::<PreviousSuggestion>(),
TypeId::of::<Reinstall>(),
ActionTypeId::of::<Suggest>(),
ActionTypeId::of::<NextSuggestion>(),
ActionTypeId::of::<PreviousSuggestion>(),
ActionTypeId::of::<Reinstall>(),
];
let copilot_auth_action_types = [TypeId::of::<SignOut>()];
let copilot_no_auth_action_types = [TypeId::of::<SignIn>()];
let copilot_auth_action_types = [ActionTypeId::of::<SignOut>()];
let copilot_no_auth_action_types = [ActionTypeId::of::<SignIn>()];
let status = handle.read(cx).status();
let filter = CommandPaletteFilter::global_mut(cx);

View File

@@ -72,14 +72,14 @@ use fuzzy::{StringMatch, StringMatchCandidate};
use git::blame::GitBlame;
use git::diff_hunk_to_display;
use gpui::{
div, impl_actions, point, prelude::*, px, relative, size, uniform_list, Action, AnyElement,
AppContext, AsyncWindowContext, AvailableSpace, BackgroundExecutor, Bounds, ClipboardEntry,
ClipboardItem, Context, DispatchPhase, ElementId, EntityId, EventEmitter, FocusHandle,
FocusOutEvent, FocusableView, FontId, FontWeight, HighlightStyle, Hsla, InteractiveText,
KeyContext, ListSizingBehavior, Model, MouseButton, PaintQuad, ParentElement, Pixels, Render,
SharedString, Size, StrikethroughStyle, Styled, StyledText, Subscription, Task, TextStyle,
UTF16Selection, UnderlineStyle, UniformListScrollHandle, View, ViewContext, ViewInputHandler,
VisualContext, WeakFocusHandle, WeakView, WindowContext,
div, impl_actions, point, prelude::*, px, relative, size, uniform_list, Action, ActionTypeId,
AnyElement, AppContext, AsyncWindowContext, AvailableSpace, BackgroundExecutor, Bounds,
ClipboardEntry, ClipboardItem, Context, DispatchPhase, ElementId, EntityId, EventEmitter,
FocusHandle, FocusOutEvent, FocusableView, FontId, FontWeight, HighlightStyle, Hsla,
InteractiveText, KeyContext, ListSizingBehavior, Model, MouseButton, PaintQuad, ParentElement,
Pixels, Render, SharedString, Size, StrikethroughStyle, Styled, StyledText, Subscription, Task,
TextStyle, UTF16Selection, UnderlineStyle, UniformListScrollHandle, View, ViewContext,
ViewInputHandler, VisualContext, WeakFocusHandle, WeakView, WindowContext,
};
use highlight_matching_bracket::refresh_matching_bracket_highlights;
use hover_popover::{hide_hover, HoverState};
@@ -128,6 +128,7 @@ use serde::{Deserialize, Serialize};
use settings::{update_settings_file, Settings, SettingsLocation, SettingsStore};
use smallvec::SmallVec;
use snippet::Snippet;
use std::any::Any;
use std::{
any::TypeId,
borrow::Cow,
@@ -12051,10 +12052,12 @@ impl Editor {
cx.notify();
}
pub fn register_action<A: Action>(
pub fn register_runtime_action(
&mut self,
listener: impl Fn(&A, &mut WindowContext) + 'static,
action_type_id: ActionTypeId,
listener: impl Fn(&dyn Any, &mut WindowContext) + 'static,
) -> Subscription {
dbg!("register runtime action", action_type_id.clone());
let id = self.next_editor_action_id.post_inc();
let listener = Arc::new(listener);
self.editor_actions.borrow_mut().insert(
@@ -12062,8 +12065,8 @@ impl Editor {
Box::new(move |cx| {
let cx = cx.window_context();
let listener = listener.clone();
cx.on_action(TypeId::of::<A>(), move |action, phase, cx| {
let action = action.downcast_ref().unwrap();
cx.on_action(action_type_id.clone(), move |action, phase, cx| {
eprintln!("Action fired; phase is {phase:?}");
if phase == DispatchPhase::Bubble {
listener(action, cx)
}
@@ -12077,6 +12080,16 @@ impl Editor {
})
}
pub fn register_action<A: Action>(
&mut self,
listener: impl Fn(&A, &mut WindowContext) + 'static,
) -> Subscription {
self.register_runtime_action(ActionTypeId::of::<A>(), move |action, cx| {
let action = action.downcast_ref().unwrap();
listener(action, cx)
})
}
pub fn file_header_size(&self) -> u32 {
self.file_header_size
}

View File

@@ -29,7 +29,6 @@ use crate::{
use client::ParticipantIndex;
use collections::{BTreeMap, HashMap};
use git::{blame::BlameEntry, diff::DiffHunkStatus, Oid};
use gpui::Subscription;
use gpui::{
anchored, deferred, div, fill, outline, point, px, quad, relative, size, svg,
transparent_black, Action, AnchorCorner, AnyElement, AvailableSpace, Bounds, ClipboardItem,
@@ -40,6 +39,7 @@ use gpui::{
StatefulInteractiveElement, Style, Styled, TextRun, TextStyle, TextStyleRefinement, View,
ViewContext, WeakView, WindowContext,
};
use gpui::{ActionTypeId, Subscription};
use itertools::Itertools;
use language::{
language_settings::{
@@ -6258,7 +6258,7 @@ pub fn register_action<T: Action>(
listener: impl Fn(&mut Editor, &T, &mut ViewContext<Editor>) + 'static,
) {
let view = view.clone();
cx.on_action(TypeId::of::<T>(), move |action, phase, cx| {
cx.on_action(ActionTypeId::of::<T>(), move |action, phase, cx| {
let action = action.downcast_ref().unwrap();
if phase == DispatchPhase::Bubble {
view.update(cx, |editor, cx| {

View File

@@ -24,6 +24,7 @@ async-trait.workspace = true
client.workspace = true
collections.workspace = true
fs.workspace = true
editor.workspace = true
futures.workspace = true
gpui.workspace = true
http_client.workspace = true
@@ -32,7 +33,9 @@ isahc.workspace = true
language.workspace = true
log.workspace = true
lsp.workspace = true
multi_buffer.workspace = true
node_runtime.workspace = true
parking_lot.workspace = true
paths.workspace = true
project.workspace = true
release_channel.workspace = true

View File

@@ -80,6 +80,8 @@ pub struct ExtensionManifest {
pub indexed_docs_providers: BTreeMap<Arc<str>, IndexedDocsProviderEntry>,
#[serde(default)]
pub snippets: Option<PathBuf>,
#[serde(default)]
pub editor_actions: BTreeMap<Arc<str>, EditorActionManifestEntry>,
}
#[derive(Clone, Default, PartialEq, Eq, Debug, Deserialize, Serialize)]
@@ -116,6 +118,9 @@ pub struct LanguageServerManifestEntry {
pub code_action_kinds: Option<Vec<lsp::CodeActionKind>>,
}
#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]
pub struct ActionManifestEntry {}
impl LanguageServerManifestEntry {
/// Returns the list of languages for the language server.
///
@@ -140,6 +145,11 @@ pub struct SlashCommandManifestEntry {
pub requires_argument: bool,
}
#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]
pub struct EditorActionManifestEntry {
pub name: String, // <extension_id>:: "Name"
}
#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]
pub struct IndexedDocsProviderEntry {}
@@ -187,6 +197,7 @@ fn manifest_from_old_manifest(
authors: manifest_json.authors,
schema_version: SchemaVersion::ZERO,
lib: Default::default(),
editor_actions: Default::default(),
themes: {
let mut themes = manifest_json.themes.into_values().collect::<Vec<_>>();
themes.sort();

View File

@@ -18,7 +18,8 @@ use assistant_slash_command::SlashCommandRegistry;
use async_compression::futures::bufread::GzipDecoder;
use async_tar::Archive;
use client::{telemetry::Telemetry, Client, ExtensionMetadata, GetExtensionsResponse};
use collections::{btree_map, BTreeMap, HashSet};
use collections::{btree_map, BTreeMap, HashMap, HashSet};
use editor::Editor;
use extension_builder::{CompileExtensionOptions, ExtensionBuilder};
use fs::{Fs, RemoveOptions};
use futures::{
@@ -30,8 +31,9 @@ use futures::{
select_biased, AsyncReadExt as _, Future, FutureExt as _, StreamExt as _,
};
use gpui::{
actions, AppContext, AsyncAppContext, Context, EventEmitter, Global, Model, ModelContext, Task,
WeakModel,
actions, Action, ActionTypeId, AnyWindowHandle, AppContext, AsyncAppContext,
BackgroundExecutor, Context, EventEmitter, Global, Model, ModelContext, Subscription, Task,
WeakModel, WeakView, WindowHandle,
};
use http_client::{AsyncBody, HttpClient, HttpClientWithUrl};
use indexed_docs::{IndexedDocsRegistry, ProviderId};
@@ -48,6 +50,7 @@ use settings::Settings;
use snippet_provider::SnippetRegistry;
use std::ops::RangeInclusive;
use std::str::FromStr;
use std::sync::LazyLock;
use std::{
cmp::Ordering,
path::{self, Path, PathBuf},
@@ -55,6 +58,7 @@ use std::{
time::{Duration, Instant},
};
use theme::{ThemeRegistry, ThemeSettings};
use ui::SharedString;
use url::Url;
use util::{maybe, ResultExt};
use wasm_host::{
@@ -102,6 +106,143 @@ pub fn is_version_compatible(
true
}
#[derive(Clone, PartialEq, Eq)]
pub struct ExtensionAction {
name: Arc<str>,
action_type_id: ActionTypeId,
}
static NAME_TO_TYPE_ID: LazyLock<Arc<parking_lot::Mutex<HashMap<String, ActionTypeId>>>> =
LazyLock::new(|| Default::default());
impl gpui::Action for ExtensionAction {
fn boxed_clone(&self) -> Box<dyn gpui::Action> {
Box::new(self.clone())
}
fn as_any(&self) -> &dyn std::any::Any {
self as _
}
fn partial_eq(&self, action: &dyn gpui::Action) -> bool {
let Some(other) = action.as_any().downcast_ref::<ExtensionAction>() else {
return false;
};
other == self
}
fn name(&self) -> &str {
&self.name
}
fn debug_name() -> &'static str
where
Self: Sized,
{
"extension::ActionDebugName"
}
fn action_type_id(&self) -> gpui::ActionTypeId {
self.action_type_id.clone()
}
fn build(name: &str, value: serde_json::Value) -> Result<Box<dyn gpui::Action>>
where
Self: Sized,
{
let Some(action_type_id) = NAME_TO_TYPE_ID.lock().get(name).cloned() else {
anyhow::bail!("lol")
};
Ok(Box::new(Self {
name: name.into(),
action_type_id,
}))
}
}
pub struct EditorActionRegistry {
executor: BackgroundExecutor,
wasm_host: Arc<WasmHost>,
editors: Vec<(AnyWindowHandle, WeakView<Editor>)>,
actions: Vec<String>,
}
impl EditorActionRegistry {
fn new(wasm_host: Arc<WasmHost>, cx: &mut AppContext) -> Self {
EditorActionRegistry {
executor: cx.background_executor().clone(),
wasm_host: wasm_host.clone(),
editors: Default::default(),
actions: Default::default(),
}
}
fn add_editor(
&mut self,
editor: &mut Editor,
handle: WeakView<Editor>,
window: AnyWindowHandle,
cx: &mut AppContext,
) {
self.editors.push((window, handle));
dbg!(&self.actions);
for action in self.actions.iter() {
let Some(type_id) = NAME_TO_TYPE_ID.lock().get(action).cloned() else {
panic!("yikes");
};
dbg!("Registering runtime action");
editor
.register_runtime_action(type_id.clone(), |any, cx| {
dbg!("oh wow!");
})
.detach();
}
}
fn add_actions(&mut self, actions: Vec<String>) {
self.actions.extend(actions.clone());
for action in actions.into_iter() {
let editors = self.editors.clone();
let task = self.wasm_host.on_main_thread(|cx| {
{
async move {
cx.update(|cx| {
let type_id = NAME_TO_TYPE_ID
.lock()
.entry(action.clone())
.or_insert_with(|| {
cx.register_action_type(action.into(), ExtensionAction::build)
})
.clone();
dbg!(&type_id, &editors.len());
for (window, editor) in editors {
window
.update(cx, |_, cx| {
editor.update(cx, |editor, cx| {
editor
.register_runtime_action(
type_id.clone(),
|any, cx| {
dbg!("oh wow!");
},
)
.detach()
})
})
.log_err();
}
})
.log_err()
}
}
.boxed_local()
});
self.executor.spawn(task).detach();
}
}
}
pub struct ExtensionStore {
builder: Arc<ExtensionBuilder>,
extension_index: ExtensionIndex,
@@ -114,6 +255,7 @@ pub struct ExtensionStore {
outstanding_operations: BTreeMap<Arc<str>, ExtensionOperation>,
index_path: PathBuf,
language_registry: Arc<LanguageRegistry>,
editor_actions_registry: EditorActionRegistry,
theme_registry: Arc<ThemeRegistry>,
slash_command_registry: Arc<SlashCommandRegistry>,
indexed_docs_registry: Arc<IndexedDocsRegistry>,
@@ -239,11 +381,37 @@ impl ExtensionStore {
let installed_dir = extensions_dir.join("installed");
let index_path = extensions_dir.join("index.json");
let handle = cx.handle();
cx.observe_new_views(move |editor: &mut Editor, cx| {
let editor_handle = cx.view().downgrade();
let window = cx.window_handle();
handle.update(cx, |extension_store, cx| {
dbg!("registering!");
extension_store.editor_actions_registry.add_editor(
editor,
editor_handle,
window,
cx,
)
})
})
.detach();
let wasm_host = WasmHost::new(
fs.clone(),
http_client.clone(),
node_runtime,
language_registry.clone(),
work_dir,
cx,
);
let (reload_tx, mut reload_rx) = unbounded();
let mut this = Self {
extension_index: Default::default(),
installed_dir,
index_path,
editor_actions_registry: EditorActionRegistry::new(wasm_host.clone(), cx),
builder: Arc::new(ExtensionBuilder::new(
// Construct a real HTTP client for the extension builder, as we
// don't want to use a fake one in the tests.
@@ -253,15 +421,8 @@ impl ExtensionStore {
outstanding_operations: Default::default(),
modified_extensions: Default::default(),
reload_complete_senders: Vec::new(),
wasm_host: WasmHost::new(
fs.clone(),
http_client.clone(),
node_runtime,
language_registry.clone(),
work_dir,
cx,
),
wasm_extensions: Vec::new(),
wasm_host,
fs,
http_client,
telemetry,
@@ -939,7 +1100,7 @@ impl ExtensionStore {
///
/// First, this unloads any themes, languages, or grammars that are
/// no longer in the manifest, or whose files have changed on disk.
/// Then it loads any themes, languages, or grammars that are newly
/// Then it loads any themes, languages, edits, or grammars that are newly
/// added to the manifest, or whose files have changed on disk.
fn extensions_updated(
&mut self,
@@ -1064,6 +1225,7 @@ impl ExtensionStore {
let mut grammars_to_add = Vec::new();
let mut themes_to_add = Vec::new();
let mut snippets_to_add = Vec::new();
let mut actions_to_add = Vec::new();
for extension_id in &extensions_to_load {
let Some(extension) = new_index.extensions.get(extension_id) else {
continue;
@@ -1086,8 +1248,25 @@ impl ExtensionStore {
path.extend([Path::new(extension_id.as_ref()), snippets_path.as_path()]);
path
}));
actions_to_add.extend(
extension
.manifest
.editor_actions
.iter()
.map(|(k, v)| dbg!(format!("{}::{}", extension_id, v.name))),
);
actions_to_add.extend(
extension
.manifest
.editor_actions
.iter()
.map(|(k, v)| dbg!(format!("editor::{}", v.name))),
);
}
dbg!("hello!", &actions_to_add);
self.editor_actions_registry.add_actions(actions_to_add);
self.language_registry
.register_wasm_grammars(grammars_to_add);

View File

@@ -145,6 +145,7 @@ async fn test_extension_store(cx: &mut TestAppContext) {
id: "zed-ruby".into(),
name: "Zed Ruby".into(),
version: "1.0.0".into(),
editor_actions: Default::default(),
schema_version: SchemaVersion::ZERO,
description: None,
authors: Vec::new(),
@@ -170,6 +171,7 @@ async fn test_extension_store(cx: &mut TestAppContext) {
"zed-monokai".into(),
ExtensionIndexEntry {
manifest: Arc::new(ExtensionManifest {
editor_actions: Default::default(),
id: "zed-monokai".into(),
name: "Zed Monokai".into(),
version: "2.0.0".into(),
@@ -336,6 +338,7 @@ async fn test_extension_store(cx: &mut TestAppContext) {
"zed-gruvbox".into(),
ExtensionIndexEntry {
manifest: Arc::new(ExtensionManifest {
editor_actions: Default::default(),
id: "zed-gruvbox".into(),
name: "Zed Gruvbox".into(),
version: "1.0.0".into(),

View File

@@ -104,6 +104,25 @@ impl WasmHost {
})
}
pub fn on_main_thread<T, Fn>(&self, f: Fn) -> impl 'static + Future<Output = T>
where
T: 'static + Send,
Fn: 'static + Send + for<'a> FnOnce(&'a mut AsyncAppContext) -> LocalBoxFuture<'a, T>,
{
let (return_tx, return_rx) = oneshot::channel();
self.main_thread_message_tx
.clone()
.unbounded_send(Box::new(move |cx| {
async {
let result = f(cx).await;
return_tx.send(result).ok();
}
.boxed_local()
}))
.expect("main thread message channel should not be closed yet");
async move { return_rx.await.expect("main thread message channel") }
}
pub fn load_extension(
self: &Arc<Self>,
wasm_bytes: Vec<u8>,
@@ -270,19 +289,7 @@ impl WasmState {
T: 'static + Send,
Fn: 'static + Send + for<'a> FnOnce(&'a mut AsyncAppContext) -> LocalBoxFuture<'a, T>,
{
let (return_tx, return_rx) = oneshot::channel();
self.host
.main_thread_message_tx
.clone()
.unbounded_send(Box::new(move |cx| {
async {
let result = f(cx).await;
return_tx.send(result).ok();
}
.boxed_local()
}))
.expect("main thread message channel should not be closed yet");
async move { return_rx.await.expect("main thread message channel") }
self.host.on_main_thread(f)
}
fn work_dir(&self) -> PathBuf {

View File

@@ -5,7 +5,7 @@ mod since_v0_1_0;
mod since_v0_2_0;
use indexed_docs::IndexedDocsDatabase;
use release_channel::ReleaseChannel;
use since_v0_2_0 as latest;
use since_v0_2_0::{self as latest, ExtensionBuffer};
use super::{wasm_engine, WasmState};
use anyhow::{anyhow, Context, Result};
@@ -139,6 +139,21 @@ impl Extension {
}
}
pub async fn call_run_editor_action(
&self,
store: &mut Store<WasmState>,
action_name: &str,
resource: Resource<ExtensionBuffer>,
) -> Result<()> {
match self {
Extension::V020(ext) => {
ext.call_run_editor_action(store, action_name, resource)
.await
}
_ => anyhow::bail!("not implemented"),
}
}
pub async fn call_language_server_command(
&self,
store: &mut Store<WasmState>,

View File

@@ -5,6 +5,7 @@ use anyhow::{anyhow, bail, Context, Result};
use async_compression::futures::bufread::GzipDecoder;
use async_tar::Archive;
use async_trait::async_trait;
use editor::MultiBufferSnapshot;
use futures::{io::BufReader, FutureExt as _};
use futures::{lock::Mutex, AsyncReadExt};
use indexed_docs::IndexedDocsDatabase;
@@ -32,17 +33,47 @@ wasmtime::component::bindgen!({
path: "../extension_api/wit/since_v0.2.0",
with: {
"worktree": ExtensionWorktree,
"buffer": ExtensionBuffer,
"char-iterator": ExtensionCharIterator,
"key-value-store": ExtensionKeyValueStore,
"zed:extension/http-client/http-response-stream": ExtensionHttpResponseStream
},
});
pub struct BoopCharIterator {
snapshot: multi_buffer::MultiBufferSnapshot,
offset: usize,
}
impl Iterator for BoopCharIterator {
type Item = (char, u64);
fn next(&mut self) -> Option<Self::Item> {
let offset = self.offset;
let char = self.snapshot.chars_at(self.offset).next()?;
self.offset = offset + char.len_utf8();
Some((char, offset as u64))
}
}
pub use self::zed::extension::*;
mod settings {
include!(concat!(env!("OUT_DIR"), "/since_v0.2.0/settings.rs"));
}
pub struct ExtensionBuffer {
snapshot: MultiBufferSnapshot,
selections: Vec<Selection>,
}
impl ExtensionBuffer {
fn selections(&self) -> Vec<Selection> {
self.selections.clone()
}
}
pub type ExtensionCharIterator = Arc<Mutex<BoopCharIterator>>;
pub type ExtensionWorktree = Arc<dyn LspAdapterDelegate>;
pub type ExtensionKeyValueStore = Arc<IndexedDocsDatabase>;
pub type ExtensionHttpResponseStream = Arc<Mutex<::http_client::Response<AsyncBody>>>;
@@ -549,3 +580,53 @@ impl ExtensionImports for WasmState {
Ok(Ok(()))
}
}
#[async_trait]
impl HostBuffer for WasmState {
async fn selections(&mut self, buffer: Resource<Buffer>) -> wasmtime::Result<Vec<Selection>> {
let buffer = self.table.get(&buffer)?;
// Assuming Buffer has a selections() method that returns Vec<Selection>
Ok(buffer.selections())
}
async fn chars_at(
&mut self,
buffer: Resource<Buffer>,
offset: u64,
) -> wasmtime::Result<Resource<CharIterator>> {
todo!("Implement chars_at")
}
async fn chars_before(
&mut self,
buffer: Resource<Buffer>,
offset: u64,
) -> wasmtime::Result<Resource<CharIterator>> {
todo!("Implement chars_before")
}
async fn edits(&mut self, buffer: Resource<Buffer>) -> wasmtime::Result<Vec<Edit>> {
todo!("Implement edits")
}
fn drop(&mut self, buffer: Resource<Buffer>) -> wasmtime::Result<()> {
todo!("Implement drop for Buffer")
}
}
#[async_trait]
impl HostCharIterator for WasmState {
async fn next(
&mut self,
iterator: Resource<CharIterator>,
) -> wasmtime::Result<Option<(char, u64)>> {
let char_iterator = self.table.get(&iterator)?;
let mut state = char_iterator.lock().await;
Ok(state.next())
}
fn drop(&mut self, _: Resource<CharIterator>) -> wasmtime::Result<()> {
Ok(())
}
}

View File

@@ -27,7 +27,7 @@ pub use wit::{
zed::extension::slash_command::{
SlashCommand, SlashCommandArgumentCompletion, SlashCommandOutput, SlashCommandOutputSection,
},
CodeLabel, CodeLabelSpan, CodeLabelSpanLiteral, Command, DownloadedFileType, EnvVars,
Buffer, CodeLabel, CodeLabelSpan, CodeLabelSpanLiteral, Command, DownloadedFileType, EnvVars,
KeyValueStore, LanguageServerInstallationStatus, Range, Worktree,
};
@@ -57,6 +57,9 @@ pub fn set_language_server_installation_status(
wit::set_language_server_installation_status(&language_server_id.0, status)
}
// regsister editor action
// handle action (editor)
/// A Zed extension.
pub trait Extension: Send + Sync {
/// Returns a new instance of the extension.
@@ -147,6 +150,9 @@ pub trait Extension: Send + Sync {
) -> Result<(), String> {
Err("`index_docs` not implemented".to_string())
}
/// Returns the output from running the provided slash command.
fn run_editor_action(&self, _name: String, buffer: Buffer) {}
}
/// Registers the provided type as a Zed extension.
@@ -281,6 +287,10 @@ impl wit::Guest for Component {
) -> Result<(), String> {
extension().index_docs(provider, package, database)
}
fn run_editor_action(
name: String,buffer: Buffe extension().run_editor_action(name, buffer)
}
}
/// The ID of a language server.

View File

@@ -83,12 +83,36 @@ world extension {
shell-env: func() -> env-vars;
}
resource buffer {
selections: func() -> list<selection>;
chars-at: func(offset: u64) -> char-iterator;
chars-before: func(offset: u64) -> char-iterator;
edits: func() -> list<edit>;
}
record edit {
range: range,
text: string,
}
resource char-iterator {
next: func() -> option<tuple<char, u64>>;
}
record selection {
range: range,
reversed: bool,
}
/// A key-value store.
resource key-value-store {
/// Inserts an entry under the specified key.
insert: func(key: string, value: string) -> result<_, string>;
}
/// Runs the editor action
export run-editor-action: func(name: string, buffer: buffer);
/// Returns the command used to start up the language server.
export language-server-command: func(language-server-id: string, worktree: borrow<worktree>) -> result<command, string>;

View File

@@ -3,7 +3,10 @@ use anyhow::{anyhow, Context, Result};
use collections::HashMap;
pub use no_action::NoAction;
use serde_json::json;
use std::any::{Any, TypeId};
use std::{
any::{Any, TypeId},
sync::atomic::AtomicUsize,
};
/// Actions are used to implement keyboard-driven UI.
/// When you declare an action, you can bind keys to the action in the keymap and
@@ -52,6 +55,9 @@ pub trait Action: 'static + Send {
/// Get the name of this action, for displaying in UI
fn name(&self) -> &str;
/// Get the type_id of this action (to uniquely identify it)
fn action_type_id(&self) -> ActionTypeId;
/// Get the name of this action for debugging
fn debug_name() -> &'static str
where
@@ -59,7 +65,7 @@ pub trait Action: 'static + Send {
/// Build this action from a JSON value. This is used to construct actions from the keymap.
/// A value of `{}` will be passed for actions that don't have any parameters.
fn build(value: serde_json::Value) -> Result<Box<dyn Action>>
fn build(name: &str, value: serde_json::Value) -> Result<Box<dyn Action>>
where
Self: Sized;
}
@@ -72,19 +78,33 @@ impl std::fmt::Debug for dyn Action {
}
}
impl dyn Action {
/// Get the type id of this action
pub fn type_id(&self) -> TypeId {
self.as_any().type_id()
}
}
type ActionBuilder = fn(json: serde_json::Value) -> anyhow::Result<Box<dyn Action>>;
/// ActionBuilder is a constructor
pub type ActionBuilder = fn(name: &str, json: serde_json::Value) -> anyhow::Result<Box<dyn Action>>;
pub(crate) struct ActionRegistry {
builders_by_name: HashMap<SharedString, ActionBuilder>,
names_by_type_id: HashMap<TypeId, SharedString>,
names_by_type_id: HashMap<ActionTypeId, SharedString>,
all_names: Vec<SharedString>, // So we can return a static slice.
next_action_type_id: AtomicUsize,
}
#[derive(Hash, PartialEq, Eq, PartialOrd, Ord, Clone, Debug)]
/// ActionTypeId uniquely identifies an action
pub enum ActionTypeId {
/// TypeId is for staticly defined ones
TypeId(TypeId),
/// Runtime is for runtime defined ones
Runtime(usize),
}
impl ActionTypeId {
/// of returns the static type id for an action type
pub fn of<T>() -> Self
where
T: 'static,
{
ActionTypeId::TypeId(std::any::TypeId::of::<T>())
}
}
impl Default for ActionRegistry {
@@ -93,6 +113,7 @@ impl Default for ActionRegistry {
builders_by_name: Default::default(),
names_by_type_id: Default::default(),
all_names: Default::default(),
next_action_type_id: Default::default(),
};
this.load_actions();
@@ -111,7 +132,7 @@ pub type MacroActionBuilder = fn() -> ActionData;
#[doc(hidden)]
pub struct ActionData {
pub name: &'static str,
pub type_id: TypeId,
pub type_id: ActionTypeId,
pub build: ActionBuilder,
}
@@ -122,6 +143,13 @@ pub struct ActionData {
pub static __GPUI_ACTIONS: [MacroActionBuilder];
impl ActionRegistry {
pub fn next_action_type_id(&self) -> ActionTypeId {
let action_id = self
.next_action_type_id
.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
ActionTypeId::Runtime(action_id)
}
/// Load all registered actions into the registry.
pub(crate) fn load_actions(&mut self) {
for builder in __GPUI_ACTIONS {
@@ -134,7 +162,7 @@ impl ActionRegistry {
pub(crate) fn load_action<A: Action>(&mut self) {
self.insert_action(ActionData {
name: A::debug_name(),
type_id: TypeId::of::<A>(),
type_id: ActionTypeId::TypeId(TypeId::of::<A>()),
build: A::build,
});
}
@@ -146,8 +174,19 @@ impl ActionRegistry {
self.all_names.push(name);
}
pub fn register_action_type(
&mut self,
name: SharedString,
build: ActionBuilder,
) -> ActionTypeId {
let type_id = self.next_action_type_id();
self.names_by_type_id.insert(type_id.clone(), name.clone());
self.builders_by_name.insert(name, build);
type_id
}
/// Construct an action based on its name and optional JSON parameters sourced from the keymap.
pub fn build_action_type(&self, type_id: &TypeId) -> Result<Box<dyn Action>> {
pub fn build_action_type(&self, type_id: &ActionTypeId) -> Result<Box<dyn Action>> {
let name = self
.names_by_type_id
.get(type_id)
@@ -167,12 +206,12 @@ impl ActionRegistry {
.builders_by_name
.get(name)
.ok_or_else(|| anyhow!("no action type registered for {}", name))?;
(build_action)(params.unwrap_or_else(|| json!({})))
(build_action)(name, params.unwrap_or_else(|| json!({})))
.with_context(|| format!("Attempting to build action {}", name))
}
pub fn all_action_names(&self) -> &[SharedString] {
self.all_names.as_slice()
pub fn all_action_names(&self) -> Vec<SharedString> {
self.all_names.clone()
}
}
@@ -190,7 +229,7 @@ macro_rules! actions {
pub struct $name;
gpui::__impl_action!($namespace, $name, $name,
fn build(_: gpui::private::serde_json::Value) -> gpui::Result<::std::boxed::Box<dyn gpui::Action>> {
fn build(_: &str,_: gpui::private::serde_json::Value) -> gpui::Result<::std::boxed::Box<dyn gpui::Action>> {
Ok(Box::new(Self))
}
);
@@ -226,6 +265,7 @@ macro_rules! action_as {
$name,
$visual_name,
fn build(
_: &str,
_: gpui::private::serde_json::Value,
) -> gpui::Result<::std::boxed::Box<dyn gpui::Action>> {
Ok(Box::new(Self))
@@ -242,7 +282,7 @@ macro_rules! impl_actions {
($namespace:path, [ $($name:ident),* $(,)? ]) => {
$(
gpui::__impl_action!($namespace, $name, $name,
fn build(value: gpui::private::serde_json::Value) -> gpui::Result<::std::boxed::Box<dyn gpui::Action>> {
fn build(_: &str, value: gpui::private::serde_json::Value) -> gpui::Result<::std::boxed::Box<dyn gpui::Action>> {
Ok(std::boxed::Box::new(gpui::private::serde_json::from_value::<Self>(value)?))
}
);
@@ -262,6 +302,7 @@ macro_rules! impl_action_as {
$name,
$visual_name,
fn build(
_: &str,
value: gpui::private::serde_json::Value,
) -> gpui::Result<::std::boxed::Box<dyn gpui::Action>> {
Ok(std::boxed::Box::new(
@@ -315,6 +356,10 @@ macro_rules! __impl_action {
fn as_any(&self) -> &dyn ::std::any::Any {
self
}
fn action_type_id(&self) -> gpui::ActionTypeId {
gpui::ActionTypeId::TypeId(self.as_any().type_id())
}
}
};
}

View File

@@ -28,13 +28,14 @@ pub use test_context::*;
use util::ResultExt;
use crate::{
current_platform, hash, init_app_menus, Action, ActionRegistry, Any, AnyView, AnyWindowHandle,
Asset, AssetSource, BackgroundExecutor, ClipboardItem, Context, DispatchPhase, DisplayId,
Entity, EventEmitter, ForegroundExecutor, Global, KeyBinding, Keymap, Keystroke, LayoutId,
Menu, MenuItem, OwnedMenu, PathPromptOptions, Pixels, Platform, PlatformDisplay, Point,
PromptBuilder, PromptHandle, PromptLevel, Render, RenderablePromptHandle, Reservation,
SharedString, SubscriberSet, Subscription, SvgRenderer, Task, TextSystem, View, ViewContext,
Window, WindowAppearance, WindowContext, WindowHandle, WindowId,
current_platform, hash, init_app_menus, Action, ActionBuilder, ActionRegistry, ActionTypeId,
Any, AnyView, AnyWindowHandle, Asset, AssetSource, BackgroundExecutor, ClipboardItem, Context,
DispatchPhase, DisplayId, Entity, EventEmitter, ForegroundExecutor, Global, KeyBinding, Keymap,
Keystroke, LayoutId, MacroActionBuilder, Menu, MenuItem, OwnedMenu, PathPromptOptions, Pixels,
Platform, PlatformDisplay, Point, PromptBuilder, PromptHandle, PromptLevel, Render,
RenderablePromptHandle, Reservation, SharedString, SubscriberSet, Subscription, SvgRenderer,
Task, TextSystem, View, ViewContext, Window, WindowAppearance, WindowContext, WindowHandle,
WindowId,
};
mod async_context;
@@ -218,7 +219,7 @@ pub struct AppContext {
text_system: Arc<TextSystem>,
flushing_effects: bool,
pending_updates: usize,
pub(crate) actions: Rc<ActionRegistry>,
pub(crate) actions: Rc<RefCell<ActionRegistry>>,
pub(crate) active_drag: Option<AnyDrag>,
pub(crate) background_executor: BackgroundExecutor,
pub(crate) foreground_executor: ForegroundExecutor,
@@ -233,7 +234,7 @@ pub struct AppContext {
pub(crate) window_handles: FxHashMap<WindowId, AnyWindowHandle>,
pub(crate) keymap: Rc<RefCell<Keymap>>,
pub(crate) global_action_listeners:
FxHashMap<TypeId, Vec<Rc<dyn Fn(&dyn Any, DispatchPhase, &mut Self)>>>,
FxHashMap<ActionTypeId, Vec<Rc<dyn Fn(&dyn Any, DispatchPhase, &mut Self)>>>,
pending_effects: VecDeque<Effect>,
pub(crate) pending_notifications: FxHashSet<EntityId>,
pub(crate) pending_global_notifications: FxHashSet<TypeId>,
@@ -271,7 +272,7 @@ impl AppContext {
this: this.clone(),
platform: platform.clone(),
text_system,
actions: Rc::new(ActionRegistry::default()),
actions: Rc::new(RefCell::new(ActionRegistry::default())),
flushing_effects: false,
pending_updates: 0,
active_drag: None,
@@ -1078,7 +1079,7 @@ impl AppContext {
/// Register a global listener for actions invoked via the keyboard.
pub fn on_action<A: Action>(&mut self, listener: impl Fn(&A, &mut Self) + 'static) {
self.global_action_listeners
.entry(TypeId::of::<A>())
.entry(ActionTypeId::TypeId(TypeId::of::<A>()))
.or_default()
.push(Rc::new(move |action, phase, cx| {
if phase == DispatchPhase::Bubble {
@@ -1088,6 +1089,15 @@ impl AppContext {
}));
}
/// yeah
pub fn register_action_type(
&mut self,
name: SharedString,
build: ActionBuilder,
) -> ActionTypeId {
self.actions.borrow_mut().register_action_type(name, build)
}
/// Event handlers propagate events by default. Call this method to stop dispatching to
/// event handlers with a lower z-index (mouse) or higher in the tree (keyboard). This is
/// the opposite of [`Self::propagate`]. It's also possible to cancel a call to [`Self::propagate`] by
@@ -1110,15 +1120,15 @@ impl AppContext {
name: &str,
data: Option<serde_json::Value>,
) -> Result<Box<dyn Action>> {
self.actions.build_action(name, data)
self.actions.borrow().build_action(name, data)
}
/// Get a list of all action names that have been registered.
/// in the application. Note that registration only allows for
/// actions to be built dynamically, and is unrelated to binding
/// actions in the element tree.
pub fn all_action_names(&self) -> &[SharedString] {
self.actions.all_action_names()
pub fn all_action_names(&self) -> Vec<SharedString> {
self.actions.borrow().all_action_names()
}
/// Register a callback to be invoked when the application is about to quit.
@@ -1166,7 +1176,7 @@ impl AppContext {
action_available
|| self
.global_action_listeners
.contains_key(&action.as_any().type_id())
.contains_key(&action.action_type_id())
}
/// Sets the menu bar for this application. This will replace any existing menu bar.
@@ -1209,7 +1219,7 @@ impl AppContext {
if let Some(mut global_listeners) = self
.global_action_listeners
.remove(&action.as_any().type_id())
.remove(&action.action_type_id())
{
for listener in &global_listeners {
listener(action.as_any(), DispatchPhase::Capture, self);
@@ -1220,18 +1230,18 @@ impl AppContext {
global_listeners.extend(
self.global_action_listeners
.remove(&action.as_any().type_id())
.remove(&action.action_type_id())
.unwrap_or_default(),
);
self.global_action_listeners
.insert(action.as_any().type_id(), global_listeners);
.insert(action.action_type_id(), global_listeners);
}
if self.propagate_event {
if let Some(mut global_listeners) = self
.global_action_listeners
.remove(&action.as_any().type_id())
.remove(&action.action_type_id())
{
for listener in global_listeners.iter().rev() {
listener(action.as_any(), DispatchPhase::Bubble, self);
@@ -1242,12 +1252,12 @@ impl AppContext {
global_listeners.extend(
self.global_action_listeners
.remove(&action.as_any().type_id())
.remove(&action.action_type_id())
.unwrap_or_default(),
);
self.global_action_listeners
.insert(action.as_any().type_id(), global_listeners);
.insert(action.action_type_id(), global_listeners);
}
}
}

View File

@@ -16,9 +16,9 @@
//! constructed by combining these two systems into an all-in-one element.
use crate::{
point, px, size, Action, AnyDrag, AnyElement, AnyTooltip, AnyView, AppContext, Bounds,
ClickEvent, DispatchPhase, Element, ElementId, FocusHandle, Global, GlobalElementId, Hitbox,
HitboxId, IntoElement, IsZero, KeyContext, KeyDownEvent, KeyUpEvent, LayoutId,
point, px, size, Action, ActionTypeId, AnyDrag, AnyElement, AnyTooltip, AnyView, AppContext,
Bounds, ClickEvent, DispatchPhase, Element, ElementId, FocusHandle, Global, GlobalElementId,
Hitbox, HitboxId, IntoElement, IsZero, KeyContext, KeyDownEvent, KeyUpEvent, LayoutId,
ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent,
ParentElement, Pixels, Point, Render, ScrollWheelEvent, SharedString, Size, Style,
StyleRefinement, Styled, Task, TooltipId, View, Visibility, WindowContext,
@@ -286,7 +286,7 @@ impl Interactivity {
listener: impl Fn(&A, &mut WindowContext) + 'static,
) {
self.action_listeners.push((
TypeId::of::<A>(),
ActionTypeId::TypeId(TypeId::of::<A>()),
Box::new(move |action, phase, cx| {
let action = action.downcast_ref().unwrap();
if phase == DispatchPhase::Capture {
@@ -304,7 +304,7 @@ impl Interactivity {
/// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to a view's state from this callback.
pub fn on_action<A: Action>(&mut self, listener: impl Fn(&A, &mut WindowContext) + 'static) {
self.action_listeners.push((
TypeId::of::<A>(),
ActionTypeId::TypeId(TypeId::of::<A>()),
Box::new(move |action, phase, cx| {
let action = action.downcast_ref().unwrap();
if phase == DispatchPhase::Bubble {
@@ -327,7 +327,7 @@ impl Interactivity {
) {
let action = action.boxed_clone();
self.action_listeners.push((
(*action).type_id(),
(*action).action_type_id(),
Box::new(move |_, phase, cx| {
if phase == DispatchPhase::Bubble {
(listener)(&*action, cx)
@@ -1268,7 +1268,7 @@ pub struct Interactivity {
pub(crate) key_down_listeners: Vec<KeyDownListener>,
pub(crate) key_up_listeners: Vec<KeyUpListener>,
pub(crate) modifiers_changed_listeners: Vec<ModifiersChangedListener>,
pub(crate) action_listeners: Vec<(TypeId, ActionListener)>,
pub(crate) action_listeners: Vec<(ActionTypeId, ActionListener)>,
pub(crate) drop_listeners: Vec<(TypeId, DropListener)>,
pub(crate) can_drop_predicate: Option<CanDropPredicate>,
pub(crate) click_listeners: Vec<ClickListener>,

View File

@@ -50,18 +50,13 @@
/// KeyBinding::new("cmd-k left", pane::SplitLeft, Some("Pane"))
///
use crate::{
Action, ActionRegistry, DispatchPhase, EntityId, FocusId, KeyBinding, KeyContext, Keymap,
Keystroke, ModifiersChangedEvent, WindowContext,
Action, ActionRegistry, ActionTypeId, DispatchPhase, EntityId, FocusId, KeyBinding, KeyContext,
Keymap, Keystroke, ModifiersChangedEvent, WindowContext,
};
use collections::FxHashMap;
use smallvec::SmallVec;
use std::{
any::{Any, TypeId},
cell::RefCell,
mem,
ops::Range,
rc::Rc,
};
use std::{any::Any, cell::RefCell, mem, ops::Range, rc::Rc};
use util::ResultExt;
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
pub(crate) struct DispatchNodeId(usize);
@@ -74,7 +69,7 @@ pub(crate) struct DispatchTree {
focusable_node_ids: FxHashMap<FocusId, DispatchNodeId>,
view_node_ids: FxHashMap<EntityId, DispatchNodeId>,
keymap: Rc<RefCell<Keymap>>,
action_registry: Rc<ActionRegistry>,
action_registry: Rc<RefCell<ActionRegistry>>,
}
#[derive(Default)]
@@ -128,12 +123,12 @@ type ModifiersChangedListener = Rc<dyn Fn(&ModifiersChangedEvent, &mut WindowCon
#[derive(Clone)]
pub(crate) struct DispatchActionListener {
pub(crate) action_type: TypeId,
pub(crate) action_type: ActionTypeId,
pub(crate) listener: Rc<dyn Fn(&dyn Any, DispatchPhase, &mut WindowContext)>,
}
impl DispatchTree {
pub fn new(keymap: Rc<RefCell<Keymap>>, action_registry: Rc<ActionRegistry>) -> Self {
pub fn new(keymap: Rc<RefCell<Keymap>>, action_registry: Rc<RefCell<ActionRegistry>>) -> Self {
Self {
node_stack: Vec::new(),
context_stack: Vec::new(),
@@ -332,7 +327,7 @@ impl DispatchTree {
pub fn on_action(
&mut self,
action_type: TypeId,
action_type: ActionTypeId,
listener: Rc<dyn Fn(&dyn Any, DispatchPhase, &mut WindowContext)>,
) {
self.active_node()
@@ -364,12 +359,16 @@ impl DispatchTree {
let mut actions = Vec::<Box<dyn Action>>::new();
for node_id in self.dispatch_path(target) {
let node = &self.nodes[node_id.0];
dbg!(&node.context, &node.action_listeners.len());
for DispatchActionListener { action_type, .. } in &node.action_listeners {
if let Err(ix) = actions.binary_search_by_key(action_type, |a| a.as_any().type_id())
{
if let Err(ix) = actions.binary_search_by_key(action_type, |a| a.action_type_id()) {
// Intentionally silence these errors without logging.
// If an action cannot be built by default, it's not available.
let action = self.action_registry.build_action_type(action_type).ok();
let action = self
.action_registry
.borrow()
.build_action_type(action_type)
.log_err();
if let Some(action) = action {
actions.insert(ix, action);
}
@@ -385,7 +384,7 @@ impl DispatchTree {
if node
.action_listeners
.iter()
.any(|listener| listener.action_type == action.as_any().type_id())
.any(|listener| listener.action_type == action.action_type_id())
{
return true;
}
@@ -574,7 +573,9 @@ impl DispatchTree {
mod tests {
use std::{cell::RefCell, rc::Rc};
use crate::{Action, ActionRegistry, DispatchTree, KeyBinding, KeyContext, Keymap};
use crate::{
Action, ActionRegistry, ActionTypeId, DispatchTree, KeyBinding, KeyContext, Keymap,
};
#[derive(PartialEq, Eq)]
struct TestAction;
@@ -606,7 +607,14 @@ mod tests {
self
}
fn build(_value: serde_json::Value) -> anyhow::Result<Box<dyn Action>>
fn action_type_id(&self) -> crate::ActionTypeId {
ActionTypeId::TypeId(std::any::TypeId::of::<TestAction>())
}
fn build(
_type_id: ActionTypeId,
_value: serde_json::Value,
) -> anyhow::Result<Box<dyn Action>>
where
Self: Sized,
{

View File

@@ -18,7 +18,7 @@ pub struct KeymapVersion(usize);
#[derive(Default)]
pub struct Keymap {
bindings: Vec<KeyBinding>,
binding_indices_by_action_id: HashMap<TypeId, SmallVec<[usize; 3]>>,
binding_indices_by_action_id: HashMap<crate::ActionTypeId, SmallVec<[usize; 3]>>,
version: KeymapVersion,
}
@@ -38,7 +38,7 @@ impl Keymap {
/// Add more bindings to the keymap.
pub fn add_bindings<T: IntoIterator<Item = KeyBinding>>(&mut self, bindings: T) {
for binding in bindings {
let action_id = binding.action().as_any().type_id();
let action_id = binding.action().action_type_id();
self.binding_indices_by_action_id
.entry(action_id)
.or_default()
@@ -66,7 +66,7 @@ impl Keymap {
&'a self,
action: &'a dyn Action,
) -> impl 'a + DoubleEndedIterator<Item = &'a KeyBinding> {
let action_id = action.type_id();
let action_id = action.action_type_id();
self.binding_indices_by_action_id
.get(&action_id)
.map_or(&[] as _, SmallVec::as_slice)
@@ -122,7 +122,7 @@ impl Keymap {
let bindings = bindings
.into_iter()
.map_while(|(binding, _)| {
if binding.action.as_any().type_id() == (NoAction {}).type_id() {
if binding.action.action_type_id() == (NoAction {}).action_type_id() {
None
} else {
Some(binding)

View File

@@ -1,7 +1,7 @@
use crate::{
point, prelude::*, px, size, transparent_black, Action, AnyDrag, AnyElement, AnyTooltip,
AnyView, AppContext, Arena, Asset, AsyncWindowContext, AvailableSpace, Bounds, BoxShadow,
Context, Corners, CursorStyle, Decorations, DevicePixels, DispatchActionListener,
point, prelude::*, px, size, transparent_black, Action, ActionTypeId, AnyDrag, AnyElement,
AnyTooltip, AnyView, AppContext, Arena, Asset, AsyncWindowContext, AvailableSpace, Bounds,
BoxShadow, Context, Corners, CursorStyle, Decorations, DevicePixels, DispatchActionListener,
DispatchNodeId, DispatchTree, DisplayId, Edges, Effect, Entity, EntityId, EventEmitter,
FileDropEvent, Flatten, FontId, GPUSpecs, Global, GlobalElementId, GlyphId, Hsla, InputHandler,
IsZero, KeyBinding, KeyContext, KeyDownEvent, KeyEvent, Keystroke, KeystrokeEvent, LayoutId,
@@ -3488,7 +3488,7 @@ impl<'a> WindowContext<'a> {
self.propagate_event = true;
if let Some(mut global_listeners) = self
.global_action_listeners
.remove(&action.as_any().type_id())
.remove(&action.action_type_id())
{
for listener in &global_listeners {
listener(action.as_any(), DispatchPhase::Capture, self);
@@ -3499,12 +3499,12 @@ impl<'a> WindowContext<'a> {
global_listeners.extend(
self.global_action_listeners
.remove(&action.as_any().type_id())
.remove(&action.action_type_id())
.unwrap_or_default(),
);
self.global_action_listeners
.insert(action.as_any().type_id(), global_listeners);
.insert(action.action_type_id(), global_listeners);
}
if !self.propagate_event {
@@ -3519,9 +3519,8 @@ impl<'a> WindowContext<'a> {
listener,
} in node.action_listeners.clone()
{
let any_action = action.as_any();
if action_type == any_action.type_id() {
listener(any_action, DispatchPhase::Capture, self);
if action_type == action.action_type_id() {
listener(action.as_any(), DispatchPhase::Capture, self);
if !self.propagate_event {
return;
@@ -3538,10 +3537,9 @@ impl<'a> WindowContext<'a> {
listener,
} in node.action_listeners.clone()
{
let any_action = action.as_any();
if action_type == any_action.type_id() {
if action_type == action.action_type_id() {
self.propagate_event = false; // Actions stop propagation by default during the bubble phase
listener(any_action, DispatchPhase::Bubble, self);
listener(action.as_any(), DispatchPhase::Bubble, self);
if !self.propagate_event {
return;
@@ -3553,7 +3551,7 @@ impl<'a> WindowContext<'a> {
// Bubble phase for global actions.
if let Some(mut global_listeners) = self
.global_action_listeners
.remove(&action.as_any().type_id())
.remove(&action.action_type_id())
{
for listener in global_listeners.iter().rev() {
self.propagate_event = false; // Actions stop propagation by default during the bubble phase
@@ -3566,12 +3564,12 @@ impl<'a> WindowContext<'a> {
global_listeners.extend(
self.global_action_listeners
.remove(&action.as_any().type_id())
.remove(&action.action_type_id())
.unwrap_or_default(),
);
self.global_action_listeners
.insert(action.as_any().type_id(), global_listeners);
.insert(action.action_type_id(), global_listeners);
}
}
@@ -3684,8 +3682,10 @@ impl<'a> WindowContext<'a> {
.dispatch_tree
.available_actions(node_id);
for action_type in self.global_action_listeners.keys() {
if let Err(ix) = actions.binary_search_by_key(action_type, |a| a.as_any().type_id()) {
let action = self.actions.build_action_type(action_type).ok();
if let Err(ix) = actions.binary_search_by_key(action_type, |a| a.action_type_id()) {
let action = RefCell::borrow(&self.actions)
.build_action_type(action_type)
.log_err();
if let Some(action) = action {
actions.insert(ix, action);
}
@@ -3765,7 +3765,7 @@ impl<'a> WindowContext<'a> {
/// a specific need to register a global listener.
pub fn on_action(
&mut self,
action_type: TypeId,
action_type: ActionTypeId,
listener: impl Fn(&dyn Any, DispatchPhase, &mut WindowContext) + 'static,
) {
self.window
@@ -4420,7 +4420,7 @@ impl<'a, V: 'static> ViewContext<'a, V> {
/// Register a callback to be invoked when the given Action type is dispatched to the window.
pub fn on_action(
&mut self,
action_type: TypeId,
action_type: ActionTypeId,
listener: impl Fn(&mut V, &dyn Any, DispatchPhase, &mut ViewContext<V>) + 'static,
) {
let handle = self.view().clone();

View File

@@ -46,5 +46,9 @@ fn test_action_macros() {
{
unimplemented!()
}
fn action_type_id(&self) -> gpui::ActionTypeId {
gpui::ActionTypeId::TypeId(std::any::TypeId::of::<Self>())
}
}
}

View File

@@ -32,7 +32,7 @@ pub(crate) fn register_action(type_name: &Ident) -> proc_macro2::TokenStream {
fn #action_builder_fn_name() -> gpui::ActionData {
gpui::ActionData {
name: <#type_name as gpui::Action>::debug_name(),
type_id: ::std::any::TypeId::of::<#type_name>(),
type_id: gpui::ActionTypeId::TypeId(::std::any::TypeId::of::<#type_name>()),
build: <#type_name as gpui::Action>::build,
}
}

View File

@@ -141,6 +141,9 @@ pub trait ToLspPosition {
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)]
pub struct LanguageServerName(pub Arc<str>);
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)]
pub struct ActionName(Arc<str>);
impl LanguageServerName {
pub fn from_proto(s: String) -> Self {
Self(Arc::from(s))

View File

@@ -3,7 +3,7 @@ pub mod parser;
use crate::parser::CodeBlockKind;
use futures::FutureExt;
use gpui::{
actions, point, quad, AnyElement, AppContext, Bounds, ClipboardItem, CursorStyle,
actions, point, quad, ActionTypeId, AnyElement, AppContext, Bounds, ClipboardItem, CursorStyle,
DispatchPhase, Edges, FocusHandle, FocusableView, FontStyle, FontWeight, GlobalElementId,
Hitbox, Hsla, KeyContext, Length, MouseDownEvent, MouseEvent, MouseMoveEvent, MouseUpEvent,
Point, Render, StrikethroughStyle, StyleRefinement, StyledText, Task, TextLayout, TextRun,
@@ -761,7 +761,7 @@ impl Element for MarkdownElement {
context.add("Markdown");
cx.set_key_context(context);
let view = self.markdown.clone();
cx.on_action(std::any::TypeId::of::<crate::Copy>(), {
cx.on_action(ActionTypeId::of::<crate::Copy>(), {
let text = rendered_markdown.text.clone();
move |_, phase, cx| {
let text = text.clone();

View File

@@ -119,7 +119,7 @@ impl KeymapFile {
Ok(())
}
pub fn generate_json_schema(action_names: &[SharedString]) -> serde_json::Value {
pub fn generate_json_schema(action_names: Vec<SharedString>) -> serde_json::Value {
let mut root_schema = SchemaSettings::draft07()
.with(|settings| settings.option_add_null_type = false)
.into_generator()

View File

@@ -1,11 +1,9 @@
mod appearance_settings_controls;
use std::any::TypeId;
use command_palette_hooks::CommandPaletteFilter;
use editor::EditorSettingsControls;
use feature_flags::{FeatureFlag, FeatureFlagViewExt};
use gpui::{actions, AppContext, EventEmitter, FocusHandle, FocusableView, View};
use gpui::{actions, ActionTypeId, AppContext, EventEmitter, FocusHandle, FocusableView, View};
use ui::prelude::*;
use workspace::item::{Item, ItemEvent};
use workspace::Workspace;
@@ -37,7 +35,7 @@ pub fn init(cx: &mut AppContext) {
}
});
let settings_ui_actions = [TypeId::of::<OpenSettingsEditor>()];
let settings_ui_actions = [ActionTypeId::of::<OpenSettingsEditor>()];
CommandPaletteFilter::update_global(cx, |filter, _cx| {
filter.hide_action_types(&settings_ui_actions);

View File

@@ -17,8 +17,9 @@ use command_palette_hooks::CommandPaletteFilter;
use editor::{scroll::Autoscroll, Editor, MultiBuffer};
use feature_flags::FeatureFlagAppExt;
use gpui::{
actions, point, px, AppContext, AsyncAppContext, Context, FocusableView, MenuItem, PromptLevel,
ReadGlobal, TitlebarOptions, View, ViewContext, VisualContext, WindowKind, WindowOptions,
actions, point, px, ActionTypeId, AppContext, AsyncAppContext, Context, FocusableView,
MenuItem, PromptLevel, ReadGlobal, TitlebarOptions, View, ViewContext, VisualContext,
WindowKind, WindowOptions,
};
pub use open_listener::*;
@@ -552,7 +553,7 @@ pub fn initialize_workspace(
}
fn feature_gate_zed_pro_actions(cx: &mut AppContext) {
let zed_pro_actions = [TypeId::of::<OpenAccountSettings>()];
let zed_pro_actions = [ActionTypeId::of::<OpenAccountSettings>()];
CommandPaletteFilter::update_global(cx, |filter, _cx| {
filter.hide_action_types(&zed_pro_actions);

View File

@@ -0,0 +1,17 @@
[package]
name = "zed_uppercase"
version = "0.0.5"
edition = "2021"
publish = false
license = "Apache-2.0"
[lints]
workspace = true
[lib]
path = "src/uppercase.rs"
crate-type = ["cdylib"]
[dependencies]
zed_extension_api = {path= "../../crates/extension_api"}
serde_json = "1.0"

View File

@@ -0,0 +1 @@
../../LICENSE-APACHE

View File

@@ -0,0 +1,10 @@
id = "uppercase"
name = "Uppercase"
description = "Make sentences uppercase"
version = "0.0.5"
schema_version = 1
authors = []
repository = "https://github.com/zed-industries/zed"
[editor_actions.uppercase]
name = "UppercaseSentence"

View File

@@ -0,0 +1,20 @@
use serde_json::json;
use std::fs;
use zed::LanguageServerId;
use zed_extension_api::{self as zed, settings::LspSettings, Buffer, Result};
struct UppercaseExtension {}
impl UppercaseExtension {}
impl zed::Extension for UppercaseExtension {
fn new() -> Self {
Self {}
}
fn run_editor_action(&self, name: String, buffer: Buffer) {
dbg!("hi!");
}
}
zed::register_extension!(UppercaseExtension);