Compare commits
6 Commits
another
...
buffer-ext
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c822188a53 | ||
|
|
32fe23a182 | ||
|
|
b337f419d3 | ||
|
|
548878d8a0 | ||
|
|
cdb88f52b7 | ||
|
|
14b4dc9a3a |
10
Cargo.lock
generated
10
Cargo.lock
generated
@@ -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"
|
||||
|
||||
@@ -159,6 +159,7 @@ members = [
|
||||
"extensions/terraform",
|
||||
"extensions/test-extension",
|
||||
"extensions/toml",
|
||||
"extensions/uppercase",
|
||||
"extensions/uiua",
|
||||
"extensions/vue",
|
||||
"extensions/zig",
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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| {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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>;
|
||||
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -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,
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -46,5 +46,9 @@ fn test_action_macros() {
|
||||
{
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn action_type_id(&self) -> gpui::ActionTypeId {
|
||||
gpui::ActionTypeId::TypeId(std::any::TypeId::of::<Self>())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
17
extensions/uppercase/Cargo.toml
Normal file
17
extensions/uppercase/Cargo.toml
Normal 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"
|
||||
1
extensions/uppercase/LICENSE-APACHE
Symbolic link
1
extensions/uppercase/LICENSE-APACHE
Symbolic link
@@ -0,0 +1 @@
|
||||
../../LICENSE-APACHE
|
||||
10
extensions/uppercase/extension.toml
Normal file
10
extensions/uppercase/extension.toml
Normal 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"
|
||||
20
extensions/uppercase/src/uppercase.rs
Normal file
20
extensions/uppercase/src/uppercase.rs
Normal 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);
|
||||
Reference in New Issue
Block a user