Compare commits
6 Commits
git-clone
...
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",
|
"client",
|
||||||
"collections",
|
"collections",
|
||||||
"ctor",
|
"ctor",
|
||||||
|
"editor",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"fs",
|
"fs",
|
||||||
"futures 0.3.30",
|
"futures 0.3.30",
|
||||||
@@ -4070,6 +4071,7 @@ dependencies = [
|
|||||||
"language",
|
"language",
|
||||||
"log",
|
"log",
|
||||||
"lsp",
|
"lsp",
|
||||||
|
"multi_buffer",
|
||||||
"node_runtime",
|
"node_runtime",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
"paths",
|
"paths",
|
||||||
@@ -14555,6 +14557,14 @@ dependencies = [
|
|||||||
"zed_extension_api 0.1.0",
|
"zed_extension_api 0.1.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zed_uppercase"
|
||||||
|
version = "0.0.5"
|
||||||
|
dependencies = [
|
||||||
|
"serde_json",
|
||||||
|
"zed_extension_api 0.2.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zed_vue"
|
name = "zed_vue"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|||||||
@@ -159,6 +159,7 @@ members = [
|
|||||||
"extensions/terraform",
|
"extensions/terraform",
|
||||||
"extensions/test-extension",
|
"extensions/test-extension",
|
||||||
"extensions/toml",
|
"extensions/toml",
|
||||||
|
"extensions/uppercase",
|
||||||
"extensions/uiua",
|
"extensions/uiua",
|
||||||
"extensions/vue",
|
"extensions/vue",
|
||||||
"extensions/zig",
|
"extensions/zig",
|
||||||
|
|||||||
@@ -210,10 +210,9 @@ impl CommandPaletteDelegate {
|
|||||||
positions,
|
positions,
|
||||||
}) = intercept_result
|
}) = intercept_result
|
||||||
{
|
{
|
||||||
if let Some(idx) = matches
|
if let Some(idx) = matches.iter().position(|m| {
|
||||||
.iter()
|
commands[m.candidate_id].action.action_type_id() == action.action_type_id()
|
||||||
.position(|m| commands[m.candidate_id].action.type_id() == action.type_id())
|
}) {
|
||||||
{
|
|
||||||
matches.remove(idx);
|
matches.remove(idx);
|
||||||
}
|
}
|
||||||
commands.push(Command {
|
commands.push(Command {
|
||||||
|
|||||||
@@ -2,11 +2,9 @@
|
|||||||
|
|
||||||
#![deny(missing_docs)]
|
#![deny(missing_docs)]
|
||||||
|
|
||||||
use std::any::TypeId;
|
|
||||||
|
|
||||||
use collections::HashSet;
|
use collections::HashSet;
|
||||||
use derive_more::{Deref, DerefMut};
|
use derive_more::{Deref, DerefMut};
|
||||||
use gpui::{Action, AppContext, BorrowAppContext, Global};
|
use gpui::{Action, ActionTypeId, AppContext, BorrowAppContext, Global};
|
||||||
|
|
||||||
/// Initializes the command palette hooks.
|
/// Initializes the command palette hooks.
|
||||||
pub fn init(cx: &mut AppContext) {
|
pub fn init(cx: &mut AppContext) {
|
||||||
@@ -18,7 +16,7 @@ pub fn init(cx: &mut AppContext) {
|
|||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct CommandPaletteFilter {
|
pub struct CommandPaletteFilter {
|
||||||
hidden_namespaces: HashSet<&'static str>,
|
hidden_namespaces: HashSet<&'static str>,
|
||||||
hidden_action_types: HashSet<TypeId>,
|
hidden_action_types: HashSet<ActionTypeId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deref, DerefMut, Default)]
|
#[derive(Deref, DerefMut, Default)]
|
||||||
@@ -52,7 +50,7 @@ impl CommandPaletteFilter {
|
|||||||
let namespace = name.split("::").next().unwrap_or("malformed action name");
|
let namespace = name.split("::").next().unwrap_or("malformed action name");
|
||||||
|
|
||||||
self.hidden_namespaces.contains(namespace)
|
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.
|
/// Hides all actions in the given namespace.
|
||||||
@@ -66,12 +64,13 @@ impl CommandPaletteFilter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Hides all actions with the given types.
|
/// Hides all actions with the given types.
|
||||||
pub fn hide_action_types(&mut self, action_types: &[TypeId]) {
|
pub fn hide_action_types(&mut self, action_types: &[ActionTypeId]) {
|
||||||
self.hidden_action_types.extend(action_types);
|
self.hidden_action_types
|
||||||
|
.extend(action_types.iter().cloned());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Shows all actions with the given types.
|
/// 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 {
|
for action_type in action_types {
|
||||||
self.hidden_action_types.remove(action_type);
|
self.hidden_action_types.remove(action_type);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ use collections::{HashMap, HashSet};
|
|||||||
use command_palette_hooks::CommandPaletteFilter;
|
use command_palette_hooks::CommandPaletteFilter;
|
||||||
use futures::{channel::oneshot, future::Shared, Future, FutureExt, TryFutureExt};
|
use futures::{channel::oneshot, future::Shared, Future, FutureExt, TryFutureExt};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, AppContext, AsyncAppContext, Context, Entity, EntityId, EventEmitter, Global, Model,
|
actions, ActionTypeId, AppContext, AsyncAppContext, Context, Entity, EntityId, EventEmitter,
|
||||||
ModelContext, Task, WeakModel,
|
Global, Model, ModelContext, Task, WeakModel,
|
||||||
};
|
};
|
||||||
use http_client::github::latest_github_release;
|
use http_client::github::latest_github_release;
|
||||||
use http_client::HttpClient;
|
use http_client::HttpClient;
|
||||||
@@ -69,13 +69,13 @@ pub fn init(
|
|||||||
Copilot::set_global(copilot.clone(), cx);
|
Copilot::set_global(copilot.clone(), cx);
|
||||||
cx.observe(&copilot, |handle, cx| {
|
cx.observe(&copilot, |handle, cx| {
|
||||||
let copilot_action_types = [
|
let copilot_action_types = [
|
||||||
TypeId::of::<Suggest>(),
|
ActionTypeId::of::<Suggest>(),
|
||||||
TypeId::of::<NextSuggestion>(),
|
ActionTypeId::of::<NextSuggestion>(),
|
||||||
TypeId::of::<PreviousSuggestion>(),
|
ActionTypeId::of::<PreviousSuggestion>(),
|
||||||
TypeId::of::<Reinstall>(),
|
ActionTypeId::of::<Reinstall>(),
|
||||||
];
|
];
|
||||||
let copilot_auth_action_types = [TypeId::of::<SignOut>()];
|
let copilot_auth_action_types = [ActionTypeId::of::<SignOut>()];
|
||||||
let copilot_no_auth_action_types = [TypeId::of::<SignIn>()];
|
let copilot_no_auth_action_types = [ActionTypeId::of::<SignIn>()];
|
||||||
let status = handle.read(cx).status();
|
let status = handle.read(cx).status();
|
||||||
let filter = CommandPaletteFilter::global_mut(cx);
|
let filter = CommandPaletteFilter::global_mut(cx);
|
||||||
|
|
||||||
|
|||||||
@@ -72,14 +72,14 @@ use fuzzy::{StringMatch, StringMatchCandidate};
|
|||||||
use git::blame::GitBlame;
|
use git::blame::GitBlame;
|
||||||
use git::diff_hunk_to_display;
|
use git::diff_hunk_to_display;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
div, impl_actions, point, prelude::*, px, relative, size, uniform_list, Action, AnyElement,
|
div, impl_actions, point, prelude::*, px, relative, size, uniform_list, Action, ActionTypeId,
|
||||||
AppContext, AsyncWindowContext, AvailableSpace, BackgroundExecutor, Bounds, ClipboardEntry,
|
AnyElement, AppContext, AsyncWindowContext, AvailableSpace, BackgroundExecutor, Bounds,
|
||||||
ClipboardItem, Context, DispatchPhase, ElementId, EntityId, EventEmitter, FocusHandle,
|
ClipboardEntry, ClipboardItem, Context, DispatchPhase, ElementId, EntityId, EventEmitter,
|
||||||
FocusOutEvent, FocusableView, FontId, FontWeight, HighlightStyle, Hsla, InteractiveText,
|
FocusHandle, FocusOutEvent, FocusableView, FontId, FontWeight, HighlightStyle, Hsla,
|
||||||
KeyContext, ListSizingBehavior, Model, MouseButton, PaintQuad, ParentElement, Pixels, Render,
|
InteractiveText, KeyContext, ListSizingBehavior, Model, MouseButton, PaintQuad, ParentElement,
|
||||||
SharedString, Size, StrikethroughStyle, Styled, StyledText, Subscription, Task, TextStyle,
|
Pixels, Render, SharedString, Size, StrikethroughStyle, Styled, StyledText, Subscription, Task,
|
||||||
UTF16Selection, UnderlineStyle, UniformListScrollHandle, View, ViewContext, ViewInputHandler,
|
TextStyle, UTF16Selection, UnderlineStyle, UniformListScrollHandle, View, ViewContext,
|
||||||
VisualContext, WeakFocusHandle, WeakView, WindowContext,
|
ViewInputHandler, VisualContext, WeakFocusHandle, WeakView, WindowContext,
|
||||||
};
|
};
|
||||||
use highlight_matching_bracket::refresh_matching_bracket_highlights;
|
use highlight_matching_bracket::refresh_matching_bracket_highlights;
|
||||||
use hover_popover::{hide_hover, HoverState};
|
use hover_popover::{hide_hover, HoverState};
|
||||||
@@ -128,6 +128,7 @@ use serde::{Deserialize, Serialize};
|
|||||||
use settings::{update_settings_file, Settings, SettingsLocation, SettingsStore};
|
use settings::{update_settings_file, Settings, SettingsLocation, SettingsStore};
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
use snippet::Snippet;
|
use snippet::Snippet;
|
||||||
|
use std::any::Any;
|
||||||
use std::{
|
use std::{
|
||||||
any::TypeId,
|
any::TypeId,
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
@@ -12051,10 +12052,12 @@ impl Editor {
|
|||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn register_action<A: Action>(
|
pub fn register_runtime_action(
|
||||||
&mut self,
|
&mut self,
|
||||||
listener: impl Fn(&A, &mut WindowContext) + 'static,
|
action_type_id: ActionTypeId,
|
||||||
|
listener: impl Fn(&dyn Any, &mut WindowContext) + 'static,
|
||||||
) -> Subscription {
|
) -> Subscription {
|
||||||
|
dbg!("register runtime action", action_type_id.clone());
|
||||||
let id = self.next_editor_action_id.post_inc();
|
let id = self.next_editor_action_id.post_inc();
|
||||||
let listener = Arc::new(listener);
|
let listener = Arc::new(listener);
|
||||||
self.editor_actions.borrow_mut().insert(
|
self.editor_actions.borrow_mut().insert(
|
||||||
@@ -12062,8 +12065,8 @@ impl Editor {
|
|||||||
Box::new(move |cx| {
|
Box::new(move |cx| {
|
||||||
let cx = cx.window_context();
|
let cx = cx.window_context();
|
||||||
let listener = listener.clone();
|
let listener = listener.clone();
|
||||||
cx.on_action(TypeId::of::<A>(), move |action, phase, cx| {
|
cx.on_action(action_type_id.clone(), move |action, phase, cx| {
|
||||||
let action = action.downcast_ref().unwrap();
|
eprintln!("Action fired; phase is {phase:?}");
|
||||||
if phase == DispatchPhase::Bubble {
|
if phase == DispatchPhase::Bubble {
|
||||||
listener(action, cx)
|
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 {
|
pub fn file_header_size(&self) -> u32 {
|
||||||
self.file_header_size
|
self.file_header_size
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ use crate::{
|
|||||||
use client::ParticipantIndex;
|
use client::ParticipantIndex;
|
||||||
use collections::{BTreeMap, HashMap};
|
use collections::{BTreeMap, HashMap};
|
||||||
use git::{blame::BlameEntry, diff::DiffHunkStatus, Oid};
|
use git::{blame::BlameEntry, diff::DiffHunkStatus, Oid};
|
||||||
use gpui::Subscription;
|
|
||||||
use gpui::{
|
use gpui::{
|
||||||
anchored, deferred, div, fill, outline, point, px, quad, relative, size, svg,
|
anchored, deferred, div, fill, outline, point, px, quad, relative, size, svg,
|
||||||
transparent_black, Action, AnchorCorner, AnyElement, AvailableSpace, Bounds, ClipboardItem,
|
transparent_black, Action, AnchorCorner, AnyElement, AvailableSpace, Bounds, ClipboardItem,
|
||||||
@@ -40,6 +39,7 @@ use gpui::{
|
|||||||
StatefulInteractiveElement, Style, Styled, TextRun, TextStyle, TextStyleRefinement, View,
|
StatefulInteractiveElement, Style, Styled, TextRun, TextStyle, TextStyleRefinement, View,
|
||||||
ViewContext, WeakView, WindowContext,
|
ViewContext, WeakView, WindowContext,
|
||||||
};
|
};
|
||||||
|
use gpui::{ActionTypeId, Subscription};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use language::{
|
use language::{
|
||||||
language_settings::{
|
language_settings::{
|
||||||
@@ -6258,7 +6258,7 @@ pub fn register_action<T: Action>(
|
|||||||
listener: impl Fn(&mut Editor, &T, &mut ViewContext<Editor>) + 'static,
|
listener: impl Fn(&mut Editor, &T, &mut ViewContext<Editor>) + 'static,
|
||||||
) {
|
) {
|
||||||
let view = view.clone();
|
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();
|
let action = action.downcast_ref().unwrap();
|
||||||
if phase == DispatchPhase::Bubble {
|
if phase == DispatchPhase::Bubble {
|
||||||
view.update(cx, |editor, cx| {
|
view.update(cx, |editor, cx| {
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ async-trait.workspace = true
|
|||||||
client.workspace = true
|
client.workspace = true
|
||||||
collections.workspace = true
|
collections.workspace = true
|
||||||
fs.workspace = true
|
fs.workspace = true
|
||||||
|
editor.workspace = true
|
||||||
futures.workspace = true
|
futures.workspace = true
|
||||||
gpui.workspace = true
|
gpui.workspace = true
|
||||||
http_client.workspace = true
|
http_client.workspace = true
|
||||||
@@ -32,7 +33,9 @@ isahc.workspace = true
|
|||||||
language.workspace = true
|
language.workspace = true
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
lsp.workspace = true
|
lsp.workspace = true
|
||||||
|
multi_buffer.workspace = true
|
||||||
node_runtime.workspace = true
|
node_runtime.workspace = true
|
||||||
|
parking_lot.workspace = true
|
||||||
paths.workspace = true
|
paths.workspace = true
|
||||||
project.workspace = true
|
project.workspace = true
|
||||||
release_channel.workspace = true
|
release_channel.workspace = true
|
||||||
|
|||||||
@@ -80,6 +80,8 @@ pub struct ExtensionManifest {
|
|||||||
pub indexed_docs_providers: BTreeMap<Arc<str>, IndexedDocsProviderEntry>,
|
pub indexed_docs_providers: BTreeMap<Arc<str>, IndexedDocsProviderEntry>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub snippets: Option<PathBuf>,
|
pub snippets: Option<PathBuf>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub editor_actions: BTreeMap<Arc<str>, EditorActionManifestEntry>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Default, PartialEq, Eq, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Default, PartialEq, Eq, Debug, Deserialize, Serialize)]
|
||||||
@@ -116,6 +118,9 @@ pub struct LanguageServerManifestEntry {
|
|||||||
pub code_action_kinds: Option<Vec<lsp::CodeActionKind>>,
|
pub code_action_kinds: Option<Vec<lsp::CodeActionKind>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]
|
||||||
|
pub struct ActionManifestEntry {}
|
||||||
|
|
||||||
impl LanguageServerManifestEntry {
|
impl LanguageServerManifestEntry {
|
||||||
/// Returns the list of languages for the language server.
|
/// Returns the list of languages for the language server.
|
||||||
///
|
///
|
||||||
@@ -140,6 +145,11 @@ pub struct SlashCommandManifestEntry {
|
|||||||
pub requires_argument: bool,
|
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)]
|
#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]
|
||||||
pub struct IndexedDocsProviderEntry {}
|
pub struct IndexedDocsProviderEntry {}
|
||||||
|
|
||||||
@@ -187,6 +197,7 @@ fn manifest_from_old_manifest(
|
|||||||
authors: manifest_json.authors,
|
authors: manifest_json.authors,
|
||||||
schema_version: SchemaVersion::ZERO,
|
schema_version: SchemaVersion::ZERO,
|
||||||
lib: Default::default(),
|
lib: Default::default(),
|
||||||
|
editor_actions: Default::default(),
|
||||||
themes: {
|
themes: {
|
||||||
let mut themes = manifest_json.themes.into_values().collect::<Vec<_>>();
|
let mut themes = manifest_json.themes.into_values().collect::<Vec<_>>();
|
||||||
themes.sort();
|
themes.sort();
|
||||||
|
|||||||
@@ -18,7 +18,8 @@ use assistant_slash_command::SlashCommandRegistry;
|
|||||||
use async_compression::futures::bufread::GzipDecoder;
|
use async_compression::futures::bufread::GzipDecoder;
|
||||||
use async_tar::Archive;
|
use async_tar::Archive;
|
||||||
use client::{telemetry::Telemetry, Client, ExtensionMetadata, GetExtensionsResponse};
|
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 extension_builder::{CompileExtensionOptions, ExtensionBuilder};
|
||||||
use fs::{Fs, RemoveOptions};
|
use fs::{Fs, RemoveOptions};
|
||||||
use futures::{
|
use futures::{
|
||||||
@@ -30,8 +31,9 @@ use futures::{
|
|||||||
select_biased, AsyncReadExt as _, Future, FutureExt as _, StreamExt as _,
|
select_biased, AsyncReadExt as _, Future, FutureExt as _, StreamExt as _,
|
||||||
};
|
};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, AppContext, AsyncAppContext, Context, EventEmitter, Global, Model, ModelContext, Task,
|
actions, Action, ActionTypeId, AnyWindowHandle, AppContext, AsyncAppContext,
|
||||||
WeakModel,
|
BackgroundExecutor, Context, EventEmitter, Global, Model, ModelContext, Subscription, Task,
|
||||||
|
WeakModel, WeakView, WindowHandle,
|
||||||
};
|
};
|
||||||
use http_client::{AsyncBody, HttpClient, HttpClientWithUrl};
|
use http_client::{AsyncBody, HttpClient, HttpClientWithUrl};
|
||||||
use indexed_docs::{IndexedDocsRegistry, ProviderId};
|
use indexed_docs::{IndexedDocsRegistry, ProviderId};
|
||||||
@@ -48,6 +50,7 @@ use settings::Settings;
|
|||||||
use snippet_provider::SnippetRegistry;
|
use snippet_provider::SnippetRegistry;
|
||||||
use std::ops::RangeInclusive;
|
use std::ops::RangeInclusive;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
use std::sync::LazyLock;
|
||||||
use std::{
|
use std::{
|
||||||
cmp::Ordering,
|
cmp::Ordering,
|
||||||
path::{self, Path, PathBuf},
|
path::{self, Path, PathBuf},
|
||||||
@@ -55,6 +58,7 @@ use std::{
|
|||||||
time::{Duration, Instant},
|
time::{Duration, Instant},
|
||||||
};
|
};
|
||||||
use theme::{ThemeRegistry, ThemeSettings};
|
use theme::{ThemeRegistry, ThemeSettings};
|
||||||
|
use ui::SharedString;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
use util::{maybe, ResultExt};
|
use util::{maybe, ResultExt};
|
||||||
use wasm_host::{
|
use wasm_host::{
|
||||||
@@ -102,6 +106,143 @@ pub fn is_version_compatible(
|
|||||||
true
|
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 {
|
pub struct ExtensionStore {
|
||||||
builder: Arc<ExtensionBuilder>,
|
builder: Arc<ExtensionBuilder>,
|
||||||
extension_index: ExtensionIndex,
|
extension_index: ExtensionIndex,
|
||||||
@@ -114,6 +255,7 @@ pub struct ExtensionStore {
|
|||||||
outstanding_operations: BTreeMap<Arc<str>, ExtensionOperation>,
|
outstanding_operations: BTreeMap<Arc<str>, ExtensionOperation>,
|
||||||
index_path: PathBuf,
|
index_path: PathBuf,
|
||||||
language_registry: Arc<LanguageRegistry>,
|
language_registry: Arc<LanguageRegistry>,
|
||||||
|
editor_actions_registry: EditorActionRegistry,
|
||||||
theme_registry: Arc<ThemeRegistry>,
|
theme_registry: Arc<ThemeRegistry>,
|
||||||
slash_command_registry: Arc<SlashCommandRegistry>,
|
slash_command_registry: Arc<SlashCommandRegistry>,
|
||||||
indexed_docs_registry: Arc<IndexedDocsRegistry>,
|
indexed_docs_registry: Arc<IndexedDocsRegistry>,
|
||||||
@@ -239,11 +381,37 @@ impl ExtensionStore {
|
|||||||
let installed_dir = extensions_dir.join("installed");
|
let installed_dir = extensions_dir.join("installed");
|
||||||
let index_path = extensions_dir.join("index.json");
|
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 (reload_tx, mut reload_rx) = unbounded();
|
||||||
let mut this = Self {
|
let mut this = Self {
|
||||||
extension_index: Default::default(),
|
extension_index: Default::default(),
|
||||||
installed_dir,
|
installed_dir,
|
||||||
index_path,
|
index_path,
|
||||||
|
editor_actions_registry: EditorActionRegistry::new(wasm_host.clone(), cx),
|
||||||
builder: Arc::new(ExtensionBuilder::new(
|
builder: Arc::new(ExtensionBuilder::new(
|
||||||
// Construct a real HTTP client for the extension builder, as we
|
// Construct a real HTTP client for the extension builder, as we
|
||||||
// don't want to use a fake one in the tests.
|
// don't want to use a fake one in the tests.
|
||||||
@@ -253,15 +421,8 @@ impl ExtensionStore {
|
|||||||
outstanding_operations: Default::default(),
|
outstanding_operations: Default::default(),
|
||||||
modified_extensions: Default::default(),
|
modified_extensions: Default::default(),
|
||||||
reload_complete_senders: Vec::new(),
|
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_extensions: Vec::new(),
|
||||||
|
wasm_host,
|
||||||
fs,
|
fs,
|
||||||
http_client,
|
http_client,
|
||||||
telemetry,
|
telemetry,
|
||||||
@@ -939,7 +1100,7 @@ impl ExtensionStore {
|
|||||||
///
|
///
|
||||||
/// First, this unloads any themes, languages, or grammars that are
|
/// First, this unloads any themes, languages, or grammars that are
|
||||||
/// no longer in the manifest, or whose files have changed on disk.
|
/// 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.
|
/// added to the manifest, or whose files have changed on disk.
|
||||||
fn extensions_updated(
|
fn extensions_updated(
|
||||||
&mut self,
|
&mut self,
|
||||||
@@ -1064,6 +1225,7 @@ impl ExtensionStore {
|
|||||||
let mut grammars_to_add = Vec::new();
|
let mut grammars_to_add = Vec::new();
|
||||||
let mut themes_to_add = Vec::new();
|
let mut themes_to_add = Vec::new();
|
||||||
let mut snippets_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 {
|
for extension_id in &extensions_to_load {
|
||||||
let Some(extension) = new_index.extensions.get(extension_id) else {
|
let Some(extension) = new_index.extensions.get(extension_id) else {
|
||||||
continue;
|
continue;
|
||||||
@@ -1086,8 +1248,25 @@ impl ExtensionStore {
|
|||||||
path.extend([Path::new(extension_id.as_ref()), snippets_path.as_path()]);
|
path.extend([Path::new(extension_id.as_ref()), snippets_path.as_path()]);
|
||||||
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
|
self.language_registry
|
||||||
.register_wasm_grammars(grammars_to_add);
|
.register_wasm_grammars(grammars_to_add);
|
||||||
|
|
||||||
|
|||||||
@@ -145,6 +145,7 @@ async fn test_extension_store(cx: &mut TestAppContext) {
|
|||||||
id: "zed-ruby".into(),
|
id: "zed-ruby".into(),
|
||||||
name: "Zed Ruby".into(),
|
name: "Zed Ruby".into(),
|
||||||
version: "1.0.0".into(),
|
version: "1.0.0".into(),
|
||||||
|
editor_actions: Default::default(),
|
||||||
schema_version: SchemaVersion::ZERO,
|
schema_version: SchemaVersion::ZERO,
|
||||||
description: None,
|
description: None,
|
||||||
authors: Vec::new(),
|
authors: Vec::new(),
|
||||||
@@ -170,6 +171,7 @@ async fn test_extension_store(cx: &mut TestAppContext) {
|
|||||||
"zed-monokai".into(),
|
"zed-monokai".into(),
|
||||||
ExtensionIndexEntry {
|
ExtensionIndexEntry {
|
||||||
manifest: Arc::new(ExtensionManifest {
|
manifest: Arc::new(ExtensionManifest {
|
||||||
|
editor_actions: Default::default(),
|
||||||
id: "zed-monokai".into(),
|
id: "zed-monokai".into(),
|
||||||
name: "Zed Monokai".into(),
|
name: "Zed Monokai".into(),
|
||||||
version: "2.0.0".into(),
|
version: "2.0.0".into(),
|
||||||
@@ -336,6 +338,7 @@ async fn test_extension_store(cx: &mut TestAppContext) {
|
|||||||
"zed-gruvbox".into(),
|
"zed-gruvbox".into(),
|
||||||
ExtensionIndexEntry {
|
ExtensionIndexEntry {
|
||||||
manifest: Arc::new(ExtensionManifest {
|
manifest: Arc::new(ExtensionManifest {
|
||||||
|
editor_actions: Default::default(),
|
||||||
id: "zed-gruvbox".into(),
|
id: "zed-gruvbox".into(),
|
||||||
name: "Zed Gruvbox".into(),
|
name: "Zed Gruvbox".into(),
|
||||||
version: "1.0.0".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(
|
pub fn load_extension(
|
||||||
self: &Arc<Self>,
|
self: &Arc<Self>,
|
||||||
wasm_bytes: Vec<u8>,
|
wasm_bytes: Vec<u8>,
|
||||||
@@ -270,19 +289,7 @@ impl WasmState {
|
|||||||
T: 'static + Send,
|
T: 'static + Send,
|
||||||
Fn: 'static + Send + for<'a> FnOnce(&'a mut AsyncAppContext) -> LocalBoxFuture<'a, T>,
|
Fn: 'static + Send + for<'a> FnOnce(&'a mut AsyncAppContext) -> LocalBoxFuture<'a, T>,
|
||||||
{
|
{
|
||||||
let (return_tx, return_rx) = oneshot::channel();
|
self.host.on_main_thread(f)
|
||||||
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") }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn work_dir(&self) -> PathBuf {
|
fn work_dir(&self) -> PathBuf {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ mod since_v0_1_0;
|
|||||||
mod since_v0_2_0;
|
mod since_v0_2_0;
|
||||||
use indexed_docs::IndexedDocsDatabase;
|
use indexed_docs::IndexedDocsDatabase;
|
||||||
use release_channel::ReleaseChannel;
|
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 super::{wasm_engine, WasmState};
|
||||||
use anyhow::{anyhow, Context, Result};
|
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(
|
pub async fn call_language_server_command(
|
||||||
&self,
|
&self,
|
||||||
store: &mut Store<WasmState>,
|
store: &mut Store<WasmState>,
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ use anyhow::{anyhow, bail, Context, Result};
|
|||||||
use async_compression::futures::bufread::GzipDecoder;
|
use async_compression::futures::bufread::GzipDecoder;
|
||||||
use async_tar::Archive;
|
use async_tar::Archive;
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
|
use editor::MultiBufferSnapshot;
|
||||||
use futures::{io::BufReader, FutureExt as _};
|
use futures::{io::BufReader, FutureExt as _};
|
||||||
use futures::{lock::Mutex, AsyncReadExt};
|
use futures::{lock::Mutex, AsyncReadExt};
|
||||||
use indexed_docs::IndexedDocsDatabase;
|
use indexed_docs::IndexedDocsDatabase;
|
||||||
@@ -32,17 +33,47 @@ wasmtime::component::bindgen!({
|
|||||||
path: "../extension_api/wit/since_v0.2.0",
|
path: "../extension_api/wit/since_v0.2.0",
|
||||||
with: {
|
with: {
|
||||||
"worktree": ExtensionWorktree,
|
"worktree": ExtensionWorktree,
|
||||||
|
"buffer": ExtensionBuffer,
|
||||||
|
"char-iterator": ExtensionCharIterator,
|
||||||
"key-value-store": ExtensionKeyValueStore,
|
"key-value-store": ExtensionKeyValueStore,
|
||||||
"zed:extension/http-client/http-response-stream": ExtensionHttpResponseStream
|
"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::*;
|
pub use self::zed::extension::*;
|
||||||
|
|
||||||
mod settings {
|
mod settings {
|
||||||
include!(concat!(env!("OUT_DIR"), "/since_v0.2.0/settings.rs"));
|
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 ExtensionWorktree = Arc<dyn LspAdapterDelegate>;
|
||||||
pub type ExtensionKeyValueStore = Arc<IndexedDocsDatabase>;
|
pub type ExtensionKeyValueStore = Arc<IndexedDocsDatabase>;
|
||||||
pub type ExtensionHttpResponseStream = Arc<Mutex<::http_client::Response<AsyncBody>>>;
|
pub type ExtensionHttpResponseStream = Arc<Mutex<::http_client::Response<AsyncBody>>>;
|
||||||
@@ -549,3 +580,53 @@ impl ExtensionImports for WasmState {
|
|||||||
Ok(Ok(()))
|
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::{
|
zed::extension::slash_command::{
|
||||||
SlashCommand, SlashCommandArgumentCompletion, SlashCommandOutput, SlashCommandOutputSection,
|
SlashCommand, SlashCommandArgumentCompletion, SlashCommandOutput, SlashCommandOutputSection,
|
||||||
},
|
},
|
||||||
CodeLabel, CodeLabelSpan, CodeLabelSpanLiteral, Command, DownloadedFileType, EnvVars,
|
Buffer, CodeLabel, CodeLabelSpan, CodeLabelSpanLiteral, Command, DownloadedFileType, EnvVars,
|
||||||
KeyValueStore, LanguageServerInstallationStatus, Range, Worktree,
|
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)
|
wit::set_language_server_installation_status(&language_server_id.0, status)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// regsister editor action
|
||||||
|
// handle action (editor)
|
||||||
|
|
||||||
/// A Zed extension.
|
/// A Zed extension.
|
||||||
pub trait Extension: Send + Sync {
|
pub trait Extension: Send + Sync {
|
||||||
/// Returns a new instance of the extension.
|
/// Returns a new instance of the extension.
|
||||||
@@ -147,6 +150,9 @@ pub trait Extension: Send + Sync {
|
|||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
Err("`index_docs` not implemented".to_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.
|
/// Registers the provided type as a Zed extension.
|
||||||
@@ -281,6 +287,10 @@ impl wit::Guest for Component {
|
|||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
extension().index_docs(provider, package, database)
|
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.
|
/// The ID of a language server.
|
||||||
|
|||||||
@@ -83,12 +83,36 @@ world extension {
|
|||||||
shell-env: func() -> env-vars;
|
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.
|
/// A key-value store.
|
||||||
resource key-value-store {
|
resource key-value-store {
|
||||||
/// Inserts an entry under the specified key.
|
/// Inserts an entry under the specified key.
|
||||||
insert: func(key: string, value: string) -> result<_, string>;
|
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.
|
/// 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>;
|
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;
|
use collections::HashMap;
|
||||||
pub use no_action::NoAction;
|
pub use no_action::NoAction;
|
||||||
use serde_json::json;
|
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.
|
/// Actions are used to implement keyboard-driven UI.
|
||||||
/// When you declare an action, you can bind keys to the action in the keymap and
|
/// 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
|
/// Get the name of this action, for displaying in UI
|
||||||
fn name(&self) -> &str;
|
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
|
/// Get the name of this action for debugging
|
||||||
fn debug_name() -> &'static str
|
fn debug_name() -> &'static str
|
||||||
where
|
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.
|
/// 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.
|
/// 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
|
where
|
||||||
Self: Sized;
|
Self: Sized;
|
||||||
}
|
}
|
||||||
@@ -72,19 +78,33 @@ impl std::fmt::Debug for dyn Action {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl dyn Action {
|
/// ActionBuilder is a constructor
|
||||||
/// Get the type id of this action
|
pub type ActionBuilder = fn(name: &str, json: serde_json::Value) -> anyhow::Result<Box<dyn Action>>;
|
||||||
pub fn type_id(&self) -> TypeId {
|
|
||||||
self.as_any().type_id()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type ActionBuilder = fn(json: serde_json::Value) -> anyhow::Result<Box<dyn Action>>;
|
|
||||||
|
|
||||||
pub(crate) struct ActionRegistry {
|
pub(crate) struct ActionRegistry {
|
||||||
builders_by_name: HashMap<SharedString, ActionBuilder>,
|
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.
|
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 {
|
impl Default for ActionRegistry {
|
||||||
@@ -93,6 +113,7 @@ impl Default for ActionRegistry {
|
|||||||
builders_by_name: Default::default(),
|
builders_by_name: Default::default(),
|
||||||
names_by_type_id: Default::default(),
|
names_by_type_id: Default::default(),
|
||||||
all_names: Default::default(),
|
all_names: Default::default(),
|
||||||
|
next_action_type_id: Default::default(),
|
||||||
};
|
};
|
||||||
|
|
||||||
this.load_actions();
|
this.load_actions();
|
||||||
@@ -111,7 +132,7 @@ pub type MacroActionBuilder = fn() -> ActionData;
|
|||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub struct ActionData {
|
pub struct ActionData {
|
||||||
pub name: &'static str,
|
pub name: &'static str,
|
||||||
pub type_id: TypeId,
|
pub type_id: ActionTypeId,
|
||||||
pub build: ActionBuilder,
|
pub build: ActionBuilder,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,6 +143,13 @@ pub struct ActionData {
|
|||||||
pub static __GPUI_ACTIONS: [MacroActionBuilder];
|
pub static __GPUI_ACTIONS: [MacroActionBuilder];
|
||||||
|
|
||||||
impl ActionRegistry {
|
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.
|
/// Load all registered actions into the registry.
|
||||||
pub(crate) fn load_actions(&mut self) {
|
pub(crate) fn load_actions(&mut self) {
|
||||||
for builder in __GPUI_ACTIONS {
|
for builder in __GPUI_ACTIONS {
|
||||||
@@ -134,7 +162,7 @@ impl ActionRegistry {
|
|||||||
pub(crate) fn load_action<A: Action>(&mut self) {
|
pub(crate) fn load_action<A: Action>(&mut self) {
|
||||||
self.insert_action(ActionData {
|
self.insert_action(ActionData {
|
||||||
name: A::debug_name(),
|
name: A::debug_name(),
|
||||||
type_id: TypeId::of::<A>(),
|
type_id: ActionTypeId::TypeId(TypeId::of::<A>()),
|
||||||
build: A::build,
|
build: A::build,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -146,8 +174,19 @@ impl ActionRegistry {
|
|||||||
self.all_names.push(name);
|
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.
|
/// 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
|
let name = self
|
||||||
.names_by_type_id
|
.names_by_type_id
|
||||||
.get(type_id)
|
.get(type_id)
|
||||||
@@ -167,12 +206,12 @@ impl ActionRegistry {
|
|||||||
.builders_by_name
|
.builders_by_name
|
||||||
.get(name)
|
.get(name)
|
||||||
.ok_or_else(|| anyhow!("no action type registered for {}", 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))
|
.with_context(|| format!("Attempting to build action {}", name))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn all_action_names(&self) -> &[SharedString] {
|
pub fn all_action_names(&self) -> Vec<SharedString> {
|
||||||
self.all_names.as_slice()
|
self.all_names.clone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -190,7 +229,7 @@ macro_rules! actions {
|
|||||||
pub struct $name;
|
pub struct $name;
|
||||||
|
|
||||||
gpui::__impl_action!($namespace, $name, $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))
|
Ok(Box::new(Self))
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -226,6 +265,7 @@ macro_rules! action_as {
|
|||||||
$name,
|
$name,
|
||||||
$visual_name,
|
$visual_name,
|
||||||
fn build(
|
fn build(
|
||||||
|
_: &str,
|
||||||
_: gpui::private::serde_json::Value,
|
_: gpui::private::serde_json::Value,
|
||||||
) -> gpui::Result<::std::boxed::Box<dyn gpui::Action>> {
|
) -> gpui::Result<::std::boxed::Box<dyn gpui::Action>> {
|
||||||
Ok(Box::new(Self))
|
Ok(Box::new(Self))
|
||||||
@@ -242,7 +282,7 @@ macro_rules! impl_actions {
|
|||||||
($namespace:path, [ $($name:ident),* $(,)? ]) => {
|
($namespace:path, [ $($name:ident),* $(,)? ]) => {
|
||||||
$(
|
$(
|
||||||
gpui::__impl_action!($namespace, $name, $name,
|
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)?))
|
Ok(std::boxed::Box::new(gpui::private::serde_json::from_value::<Self>(value)?))
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -262,6 +302,7 @@ macro_rules! impl_action_as {
|
|||||||
$name,
|
$name,
|
||||||
$visual_name,
|
$visual_name,
|
||||||
fn build(
|
fn build(
|
||||||
|
_: &str,
|
||||||
value: gpui::private::serde_json::Value,
|
value: gpui::private::serde_json::Value,
|
||||||
) -> gpui::Result<::std::boxed::Box<dyn gpui::Action>> {
|
) -> gpui::Result<::std::boxed::Box<dyn gpui::Action>> {
|
||||||
Ok(std::boxed::Box::new(
|
Ok(std::boxed::Box::new(
|
||||||
@@ -315,6 +356,10 @@ macro_rules! __impl_action {
|
|||||||
fn as_any(&self) -> &dyn ::std::any::Any {
|
fn as_any(&self) -> &dyn ::std::any::Any {
|
||||||
self
|
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 util::ResultExt;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
current_platform, hash, init_app_menus, Action, ActionRegistry, Any, AnyView, AnyWindowHandle,
|
current_platform, hash, init_app_menus, Action, ActionBuilder, ActionRegistry, ActionTypeId,
|
||||||
Asset, AssetSource, BackgroundExecutor, ClipboardItem, Context, DispatchPhase, DisplayId,
|
Any, AnyView, AnyWindowHandle, Asset, AssetSource, BackgroundExecutor, ClipboardItem, Context,
|
||||||
Entity, EventEmitter, ForegroundExecutor, Global, KeyBinding, Keymap, Keystroke, LayoutId,
|
DispatchPhase, DisplayId, Entity, EventEmitter, ForegroundExecutor, Global, KeyBinding, Keymap,
|
||||||
Menu, MenuItem, OwnedMenu, PathPromptOptions, Pixels, Platform, PlatformDisplay, Point,
|
Keystroke, LayoutId, MacroActionBuilder, Menu, MenuItem, OwnedMenu, PathPromptOptions, Pixels,
|
||||||
PromptBuilder, PromptHandle, PromptLevel, Render, RenderablePromptHandle, Reservation,
|
Platform, PlatformDisplay, Point, PromptBuilder, PromptHandle, PromptLevel, Render,
|
||||||
SharedString, SubscriberSet, Subscription, SvgRenderer, Task, TextSystem, View, ViewContext,
|
RenderablePromptHandle, Reservation, SharedString, SubscriberSet, Subscription, SvgRenderer,
|
||||||
Window, WindowAppearance, WindowContext, WindowHandle, WindowId,
|
Task, TextSystem, View, ViewContext, Window, WindowAppearance, WindowContext, WindowHandle,
|
||||||
|
WindowId,
|
||||||
};
|
};
|
||||||
|
|
||||||
mod async_context;
|
mod async_context;
|
||||||
@@ -218,7 +219,7 @@ pub struct AppContext {
|
|||||||
text_system: Arc<TextSystem>,
|
text_system: Arc<TextSystem>,
|
||||||
flushing_effects: bool,
|
flushing_effects: bool,
|
||||||
pending_updates: usize,
|
pending_updates: usize,
|
||||||
pub(crate) actions: Rc<ActionRegistry>,
|
pub(crate) actions: Rc<RefCell<ActionRegistry>>,
|
||||||
pub(crate) active_drag: Option<AnyDrag>,
|
pub(crate) active_drag: Option<AnyDrag>,
|
||||||
pub(crate) background_executor: BackgroundExecutor,
|
pub(crate) background_executor: BackgroundExecutor,
|
||||||
pub(crate) foreground_executor: ForegroundExecutor,
|
pub(crate) foreground_executor: ForegroundExecutor,
|
||||||
@@ -233,7 +234,7 @@ pub struct AppContext {
|
|||||||
pub(crate) window_handles: FxHashMap<WindowId, AnyWindowHandle>,
|
pub(crate) window_handles: FxHashMap<WindowId, AnyWindowHandle>,
|
||||||
pub(crate) keymap: Rc<RefCell<Keymap>>,
|
pub(crate) keymap: Rc<RefCell<Keymap>>,
|
||||||
pub(crate) global_action_listeners:
|
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>,
|
pending_effects: VecDeque<Effect>,
|
||||||
pub(crate) pending_notifications: FxHashSet<EntityId>,
|
pub(crate) pending_notifications: FxHashSet<EntityId>,
|
||||||
pub(crate) pending_global_notifications: FxHashSet<TypeId>,
|
pub(crate) pending_global_notifications: FxHashSet<TypeId>,
|
||||||
@@ -271,7 +272,7 @@ impl AppContext {
|
|||||||
this: this.clone(),
|
this: this.clone(),
|
||||||
platform: platform.clone(),
|
platform: platform.clone(),
|
||||||
text_system,
|
text_system,
|
||||||
actions: Rc::new(ActionRegistry::default()),
|
actions: Rc::new(RefCell::new(ActionRegistry::default())),
|
||||||
flushing_effects: false,
|
flushing_effects: false,
|
||||||
pending_updates: 0,
|
pending_updates: 0,
|
||||||
active_drag: None,
|
active_drag: None,
|
||||||
@@ -1078,7 +1079,7 @@ impl AppContext {
|
|||||||
/// Register a global listener for actions invoked via the keyboard.
|
/// 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) {
|
pub fn on_action<A: Action>(&mut self, listener: impl Fn(&A, &mut Self) + 'static) {
|
||||||
self.global_action_listeners
|
self.global_action_listeners
|
||||||
.entry(TypeId::of::<A>())
|
.entry(ActionTypeId::TypeId(TypeId::of::<A>()))
|
||||||
.or_default()
|
.or_default()
|
||||||
.push(Rc::new(move |action, phase, cx| {
|
.push(Rc::new(move |action, phase, cx| {
|
||||||
if phase == DispatchPhase::Bubble {
|
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 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
|
/// 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
|
/// 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,
|
name: &str,
|
||||||
data: Option<serde_json::Value>,
|
data: Option<serde_json::Value>,
|
||||||
) -> Result<Box<dyn Action>> {
|
) -> 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.
|
/// Get a list of all action names that have been registered.
|
||||||
/// in the application. Note that registration only allows for
|
/// in the application. Note that registration only allows for
|
||||||
/// actions to be built dynamically, and is unrelated to binding
|
/// actions to be built dynamically, and is unrelated to binding
|
||||||
/// actions in the element tree.
|
/// actions in the element tree.
|
||||||
pub fn all_action_names(&self) -> &[SharedString] {
|
pub fn all_action_names(&self) -> Vec<SharedString> {
|
||||||
self.actions.all_action_names()
|
self.actions.borrow().all_action_names()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Register a callback to be invoked when the application is about to quit.
|
/// Register a callback to be invoked when the application is about to quit.
|
||||||
@@ -1166,7 +1176,7 @@ impl AppContext {
|
|||||||
action_available
|
action_available
|
||||||
|| self
|
|| self
|
||||||
.global_action_listeners
|
.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.
|
/// 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
|
if let Some(mut global_listeners) = self
|
||||||
.global_action_listeners
|
.global_action_listeners
|
||||||
.remove(&action.as_any().type_id())
|
.remove(&action.action_type_id())
|
||||||
{
|
{
|
||||||
for listener in &global_listeners {
|
for listener in &global_listeners {
|
||||||
listener(action.as_any(), DispatchPhase::Capture, self);
|
listener(action.as_any(), DispatchPhase::Capture, self);
|
||||||
@@ -1220,18 +1230,18 @@ impl AppContext {
|
|||||||
|
|
||||||
global_listeners.extend(
|
global_listeners.extend(
|
||||||
self.global_action_listeners
|
self.global_action_listeners
|
||||||
.remove(&action.as_any().type_id())
|
.remove(&action.action_type_id())
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
);
|
);
|
||||||
|
|
||||||
self.global_action_listeners
|
self.global_action_listeners
|
||||||
.insert(action.as_any().type_id(), global_listeners);
|
.insert(action.action_type_id(), global_listeners);
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.propagate_event {
|
if self.propagate_event {
|
||||||
if let Some(mut global_listeners) = self
|
if let Some(mut global_listeners) = self
|
||||||
.global_action_listeners
|
.global_action_listeners
|
||||||
.remove(&action.as_any().type_id())
|
.remove(&action.action_type_id())
|
||||||
{
|
{
|
||||||
for listener in global_listeners.iter().rev() {
|
for listener in global_listeners.iter().rev() {
|
||||||
listener(action.as_any(), DispatchPhase::Bubble, self);
|
listener(action.as_any(), DispatchPhase::Bubble, self);
|
||||||
@@ -1242,12 +1252,12 @@ impl AppContext {
|
|||||||
|
|
||||||
global_listeners.extend(
|
global_listeners.extend(
|
||||||
self.global_action_listeners
|
self.global_action_listeners
|
||||||
.remove(&action.as_any().type_id())
|
.remove(&action.action_type_id())
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
);
|
);
|
||||||
|
|
||||||
self.global_action_listeners
|
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.
|
//! constructed by combining these two systems into an all-in-one element.
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
point, px, size, Action, AnyDrag, AnyElement, AnyTooltip, AnyView, AppContext, Bounds,
|
point, px, size, Action, ActionTypeId, AnyDrag, AnyElement, AnyTooltip, AnyView, AppContext,
|
||||||
ClickEvent, DispatchPhase, Element, ElementId, FocusHandle, Global, GlobalElementId, Hitbox,
|
Bounds, ClickEvent, DispatchPhase, Element, ElementId, FocusHandle, Global, GlobalElementId,
|
||||||
HitboxId, IntoElement, IsZero, KeyContext, KeyDownEvent, KeyUpEvent, LayoutId,
|
Hitbox, HitboxId, IntoElement, IsZero, KeyContext, KeyDownEvent, KeyUpEvent, LayoutId,
|
||||||
ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent,
|
ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent,
|
||||||
ParentElement, Pixels, Point, Render, ScrollWheelEvent, SharedString, Size, Style,
|
ParentElement, Pixels, Point, Render, ScrollWheelEvent, SharedString, Size, Style,
|
||||||
StyleRefinement, Styled, Task, TooltipId, View, Visibility, WindowContext,
|
StyleRefinement, Styled, Task, TooltipId, View, Visibility, WindowContext,
|
||||||
@@ -286,7 +286,7 @@ impl Interactivity {
|
|||||||
listener: impl Fn(&A, &mut WindowContext) + 'static,
|
listener: impl Fn(&A, &mut WindowContext) + 'static,
|
||||||
) {
|
) {
|
||||||
self.action_listeners.push((
|
self.action_listeners.push((
|
||||||
TypeId::of::<A>(),
|
ActionTypeId::TypeId(TypeId::of::<A>()),
|
||||||
Box::new(move |action, phase, cx| {
|
Box::new(move |action, phase, cx| {
|
||||||
let action = action.downcast_ref().unwrap();
|
let action = action.downcast_ref().unwrap();
|
||||||
if phase == DispatchPhase::Capture {
|
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.
|
/// 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) {
|
pub fn on_action<A: Action>(&mut self, listener: impl Fn(&A, &mut WindowContext) + 'static) {
|
||||||
self.action_listeners.push((
|
self.action_listeners.push((
|
||||||
TypeId::of::<A>(),
|
ActionTypeId::TypeId(TypeId::of::<A>()),
|
||||||
Box::new(move |action, phase, cx| {
|
Box::new(move |action, phase, cx| {
|
||||||
let action = action.downcast_ref().unwrap();
|
let action = action.downcast_ref().unwrap();
|
||||||
if phase == DispatchPhase::Bubble {
|
if phase == DispatchPhase::Bubble {
|
||||||
@@ -327,7 +327,7 @@ impl Interactivity {
|
|||||||
) {
|
) {
|
||||||
let action = action.boxed_clone();
|
let action = action.boxed_clone();
|
||||||
self.action_listeners.push((
|
self.action_listeners.push((
|
||||||
(*action).type_id(),
|
(*action).action_type_id(),
|
||||||
Box::new(move |_, phase, cx| {
|
Box::new(move |_, phase, cx| {
|
||||||
if phase == DispatchPhase::Bubble {
|
if phase == DispatchPhase::Bubble {
|
||||||
(listener)(&*action, cx)
|
(listener)(&*action, cx)
|
||||||
@@ -1268,7 +1268,7 @@ pub struct Interactivity {
|
|||||||
pub(crate) key_down_listeners: Vec<KeyDownListener>,
|
pub(crate) key_down_listeners: Vec<KeyDownListener>,
|
||||||
pub(crate) key_up_listeners: Vec<KeyUpListener>,
|
pub(crate) key_up_listeners: Vec<KeyUpListener>,
|
||||||
pub(crate) modifiers_changed_listeners: Vec<ModifiersChangedListener>,
|
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) drop_listeners: Vec<(TypeId, DropListener)>,
|
||||||
pub(crate) can_drop_predicate: Option<CanDropPredicate>,
|
pub(crate) can_drop_predicate: Option<CanDropPredicate>,
|
||||||
pub(crate) click_listeners: Vec<ClickListener>,
|
pub(crate) click_listeners: Vec<ClickListener>,
|
||||||
|
|||||||
@@ -50,18 +50,13 @@
|
|||||||
/// KeyBinding::new("cmd-k left", pane::SplitLeft, Some("Pane"))
|
/// KeyBinding::new("cmd-k left", pane::SplitLeft, Some("Pane"))
|
||||||
///
|
///
|
||||||
use crate::{
|
use crate::{
|
||||||
Action, ActionRegistry, DispatchPhase, EntityId, FocusId, KeyBinding, KeyContext, Keymap,
|
Action, ActionRegistry, ActionTypeId, DispatchPhase, EntityId, FocusId, KeyBinding, KeyContext,
|
||||||
Keystroke, ModifiersChangedEvent, WindowContext,
|
Keymap, Keystroke, ModifiersChangedEvent, WindowContext,
|
||||||
};
|
};
|
||||||
use collections::FxHashMap;
|
use collections::FxHashMap;
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
use std::{
|
use std::{any::Any, cell::RefCell, mem, ops::Range, rc::Rc};
|
||||||
any::{Any, TypeId},
|
use util::ResultExt;
|
||||||
cell::RefCell,
|
|
||||||
mem,
|
|
||||||
ops::Range,
|
|
||||||
rc::Rc,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
|
||||||
pub(crate) struct DispatchNodeId(usize);
|
pub(crate) struct DispatchNodeId(usize);
|
||||||
@@ -74,7 +69,7 @@ pub(crate) struct DispatchTree {
|
|||||||
focusable_node_ids: FxHashMap<FocusId, DispatchNodeId>,
|
focusable_node_ids: FxHashMap<FocusId, DispatchNodeId>,
|
||||||
view_node_ids: FxHashMap<EntityId, DispatchNodeId>,
|
view_node_ids: FxHashMap<EntityId, DispatchNodeId>,
|
||||||
keymap: Rc<RefCell<Keymap>>,
|
keymap: Rc<RefCell<Keymap>>,
|
||||||
action_registry: Rc<ActionRegistry>,
|
action_registry: Rc<RefCell<ActionRegistry>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
@@ -128,12 +123,12 @@ type ModifiersChangedListener = Rc<dyn Fn(&ModifiersChangedEvent, &mut WindowCon
|
|||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub(crate) struct DispatchActionListener {
|
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)>,
|
pub(crate) listener: Rc<dyn Fn(&dyn Any, DispatchPhase, &mut WindowContext)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DispatchTree {
|
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 {
|
Self {
|
||||||
node_stack: Vec::new(),
|
node_stack: Vec::new(),
|
||||||
context_stack: Vec::new(),
|
context_stack: Vec::new(),
|
||||||
@@ -332,7 +327,7 @@ impl DispatchTree {
|
|||||||
|
|
||||||
pub fn on_action(
|
pub fn on_action(
|
||||||
&mut self,
|
&mut self,
|
||||||
action_type: TypeId,
|
action_type: ActionTypeId,
|
||||||
listener: Rc<dyn Fn(&dyn Any, DispatchPhase, &mut WindowContext)>,
|
listener: Rc<dyn Fn(&dyn Any, DispatchPhase, &mut WindowContext)>,
|
||||||
) {
|
) {
|
||||||
self.active_node()
|
self.active_node()
|
||||||
@@ -364,12 +359,16 @@ impl DispatchTree {
|
|||||||
let mut actions = Vec::<Box<dyn Action>>::new();
|
let mut actions = Vec::<Box<dyn Action>>::new();
|
||||||
for node_id in self.dispatch_path(target) {
|
for node_id in self.dispatch_path(target) {
|
||||||
let node = &self.nodes[node_id.0];
|
let node = &self.nodes[node_id.0];
|
||||||
|
dbg!(&node.context, &node.action_listeners.len());
|
||||||
for DispatchActionListener { action_type, .. } in &node.action_listeners {
|
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.
|
// Intentionally silence these errors without logging.
|
||||||
// If an action cannot be built by default, it's not available.
|
// 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 {
|
if let Some(action) = action {
|
||||||
actions.insert(ix, action);
|
actions.insert(ix, action);
|
||||||
}
|
}
|
||||||
@@ -385,7 +384,7 @@ impl DispatchTree {
|
|||||||
if node
|
if node
|
||||||
.action_listeners
|
.action_listeners
|
||||||
.iter()
|
.iter()
|
||||||
.any(|listener| listener.action_type == action.as_any().type_id())
|
.any(|listener| listener.action_type == action.action_type_id())
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -574,7 +573,9 @@ impl DispatchTree {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use std::{cell::RefCell, rc::Rc};
|
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)]
|
#[derive(PartialEq, Eq)]
|
||||||
struct TestAction;
|
struct TestAction;
|
||||||
@@ -606,7 +607,14 @@ mod tests {
|
|||||||
self
|
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
|
where
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ pub struct KeymapVersion(usize);
|
|||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Keymap {
|
pub struct Keymap {
|
||||||
bindings: Vec<KeyBinding>,
|
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,
|
version: KeymapVersion,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,7 +38,7 @@ impl Keymap {
|
|||||||
/// Add more bindings to the keymap.
|
/// Add more bindings to the keymap.
|
||||||
pub fn add_bindings<T: IntoIterator<Item = KeyBinding>>(&mut self, bindings: T) {
|
pub fn add_bindings<T: IntoIterator<Item = KeyBinding>>(&mut self, bindings: T) {
|
||||||
for binding in bindings {
|
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
|
self.binding_indices_by_action_id
|
||||||
.entry(action_id)
|
.entry(action_id)
|
||||||
.or_default()
|
.or_default()
|
||||||
@@ -66,7 +66,7 @@ impl Keymap {
|
|||||||
&'a self,
|
&'a self,
|
||||||
action: &'a dyn Action,
|
action: &'a dyn Action,
|
||||||
) -> impl 'a + DoubleEndedIterator<Item = &'a KeyBinding> {
|
) -> 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
|
self.binding_indices_by_action_id
|
||||||
.get(&action_id)
|
.get(&action_id)
|
||||||
.map_or(&[] as _, SmallVec::as_slice)
|
.map_or(&[] as _, SmallVec::as_slice)
|
||||||
@@ -122,7 +122,7 @@ impl Keymap {
|
|||||||
let bindings = bindings
|
let bindings = bindings
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map_while(|(binding, _)| {
|
.map_while(|(binding, _)| {
|
||||||
if binding.action.as_any().type_id() == (NoAction {}).type_id() {
|
if binding.action.action_type_id() == (NoAction {}).action_type_id() {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
Some(binding)
|
Some(binding)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
point, prelude::*, px, size, transparent_black, Action, AnyDrag, AnyElement, AnyTooltip,
|
point, prelude::*, px, size, transparent_black, Action, ActionTypeId, AnyDrag, AnyElement,
|
||||||
AnyView, AppContext, Arena, Asset, AsyncWindowContext, AvailableSpace, Bounds, BoxShadow,
|
AnyTooltip, AnyView, AppContext, Arena, Asset, AsyncWindowContext, AvailableSpace, Bounds,
|
||||||
Context, Corners, CursorStyle, Decorations, DevicePixels, DispatchActionListener,
|
BoxShadow, Context, Corners, CursorStyle, Decorations, DevicePixels, DispatchActionListener,
|
||||||
DispatchNodeId, DispatchTree, DisplayId, Edges, Effect, Entity, EntityId, EventEmitter,
|
DispatchNodeId, DispatchTree, DisplayId, Edges, Effect, Entity, EntityId, EventEmitter,
|
||||||
FileDropEvent, Flatten, FontId, GPUSpecs, Global, GlobalElementId, GlyphId, Hsla, InputHandler,
|
FileDropEvent, Flatten, FontId, GPUSpecs, Global, GlobalElementId, GlyphId, Hsla, InputHandler,
|
||||||
IsZero, KeyBinding, KeyContext, KeyDownEvent, KeyEvent, Keystroke, KeystrokeEvent, LayoutId,
|
IsZero, KeyBinding, KeyContext, KeyDownEvent, KeyEvent, Keystroke, KeystrokeEvent, LayoutId,
|
||||||
@@ -3488,7 +3488,7 @@ impl<'a> WindowContext<'a> {
|
|||||||
self.propagate_event = true;
|
self.propagate_event = true;
|
||||||
if let Some(mut global_listeners) = self
|
if let Some(mut global_listeners) = self
|
||||||
.global_action_listeners
|
.global_action_listeners
|
||||||
.remove(&action.as_any().type_id())
|
.remove(&action.action_type_id())
|
||||||
{
|
{
|
||||||
for listener in &global_listeners {
|
for listener in &global_listeners {
|
||||||
listener(action.as_any(), DispatchPhase::Capture, self);
|
listener(action.as_any(), DispatchPhase::Capture, self);
|
||||||
@@ -3499,12 +3499,12 @@ impl<'a> WindowContext<'a> {
|
|||||||
|
|
||||||
global_listeners.extend(
|
global_listeners.extend(
|
||||||
self.global_action_listeners
|
self.global_action_listeners
|
||||||
.remove(&action.as_any().type_id())
|
.remove(&action.action_type_id())
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
);
|
);
|
||||||
|
|
||||||
self.global_action_listeners
|
self.global_action_listeners
|
||||||
.insert(action.as_any().type_id(), global_listeners);
|
.insert(action.action_type_id(), global_listeners);
|
||||||
}
|
}
|
||||||
|
|
||||||
if !self.propagate_event {
|
if !self.propagate_event {
|
||||||
@@ -3519,9 +3519,8 @@ impl<'a> WindowContext<'a> {
|
|||||||
listener,
|
listener,
|
||||||
} in node.action_listeners.clone()
|
} in node.action_listeners.clone()
|
||||||
{
|
{
|
||||||
let any_action = action.as_any();
|
if action_type == action.action_type_id() {
|
||||||
if action_type == any_action.type_id() {
|
listener(action.as_any(), DispatchPhase::Capture, self);
|
||||||
listener(any_action, DispatchPhase::Capture, self);
|
|
||||||
|
|
||||||
if !self.propagate_event {
|
if !self.propagate_event {
|
||||||
return;
|
return;
|
||||||
@@ -3538,10 +3537,9 @@ impl<'a> WindowContext<'a> {
|
|||||||
listener,
|
listener,
|
||||||
} in node.action_listeners.clone()
|
} in node.action_listeners.clone()
|
||||||
{
|
{
|
||||||
let any_action = action.as_any();
|
if action_type == action.action_type_id() {
|
||||||
if action_type == any_action.type_id() {
|
|
||||||
self.propagate_event = false; // Actions stop propagation by default during the bubble phase
|
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 {
|
if !self.propagate_event {
|
||||||
return;
|
return;
|
||||||
@@ -3553,7 +3551,7 @@ impl<'a> WindowContext<'a> {
|
|||||||
// Bubble phase for global actions.
|
// Bubble phase for global actions.
|
||||||
if let Some(mut global_listeners) = self
|
if let Some(mut global_listeners) = self
|
||||||
.global_action_listeners
|
.global_action_listeners
|
||||||
.remove(&action.as_any().type_id())
|
.remove(&action.action_type_id())
|
||||||
{
|
{
|
||||||
for listener in global_listeners.iter().rev() {
|
for listener in global_listeners.iter().rev() {
|
||||||
self.propagate_event = false; // Actions stop propagation by default during the bubble phase
|
self.propagate_event = false; // Actions stop propagation by default during the bubble phase
|
||||||
@@ -3566,12 +3564,12 @@ impl<'a> WindowContext<'a> {
|
|||||||
|
|
||||||
global_listeners.extend(
|
global_listeners.extend(
|
||||||
self.global_action_listeners
|
self.global_action_listeners
|
||||||
.remove(&action.as_any().type_id())
|
.remove(&action.action_type_id())
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
);
|
);
|
||||||
|
|
||||||
self.global_action_listeners
|
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
|
.dispatch_tree
|
||||||
.available_actions(node_id);
|
.available_actions(node_id);
|
||||||
for action_type in self.global_action_listeners.keys() {
|
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()) {
|
if let Err(ix) = actions.binary_search_by_key(action_type, |a| a.action_type_id()) {
|
||||||
let action = self.actions.build_action_type(action_type).ok();
|
let action = RefCell::borrow(&self.actions)
|
||||||
|
.build_action_type(action_type)
|
||||||
|
.log_err();
|
||||||
if let Some(action) = action {
|
if let Some(action) = action {
|
||||||
actions.insert(ix, action);
|
actions.insert(ix, action);
|
||||||
}
|
}
|
||||||
@@ -3765,7 +3765,7 @@ impl<'a> WindowContext<'a> {
|
|||||||
/// a specific need to register a global listener.
|
/// a specific need to register a global listener.
|
||||||
pub fn on_action(
|
pub fn on_action(
|
||||||
&mut self,
|
&mut self,
|
||||||
action_type: TypeId,
|
action_type: ActionTypeId,
|
||||||
listener: impl Fn(&dyn Any, DispatchPhase, &mut WindowContext) + 'static,
|
listener: impl Fn(&dyn Any, DispatchPhase, &mut WindowContext) + 'static,
|
||||||
) {
|
) {
|
||||||
self.window
|
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.
|
/// Register a callback to be invoked when the given Action type is dispatched to the window.
|
||||||
pub fn on_action(
|
pub fn on_action(
|
||||||
&mut self,
|
&mut self,
|
||||||
action_type: TypeId,
|
action_type: ActionTypeId,
|
||||||
listener: impl Fn(&mut V, &dyn Any, DispatchPhase, &mut ViewContext<V>) + 'static,
|
listener: impl Fn(&mut V, &dyn Any, DispatchPhase, &mut ViewContext<V>) + 'static,
|
||||||
) {
|
) {
|
||||||
let handle = self.view().clone();
|
let handle = self.view().clone();
|
||||||
|
|||||||
@@ -46,5 +46,9 @@ fn test_action_macros() {
|
|||||||
{
|
{
|
||||||
unimplemented!()
|
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 {
|
fn #action_builder_fn_name() -> gpui::ActionData {
|
||||||
gpui::ActionData {
|
gpui::ActionData {
|
||||||
name: <#type_name as gpui::Action>::debug_name(),
|
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,
|
build: <#type_name as gpui::Action>::build,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -141,6 +141,9 @@ pub trait ToLspPosition {
|
|||||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)]
|
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)]
|
||||||
pub struct LanguageServerName(pub Arc<str>);
|
pub struct LanguageServerName(pub Arc<str>);
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)]
|
||||||
|
pub struct ActionName(Arc<str>);
|
||||||
|
|
||||||
impl LanguageServerName {
|
impl LanguageServerName {
|
||||||
pub fn from_proto(s: String) -> Self {
|
pub fn from_proto(s: String) -> Self {
|
||||||
Self(Arc::from(s))
|
Self(Arc::from(s))
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ pub mod parser;
|
|||||||
use crate::parser::CodeBlockKind;
|
use crate::parser::CodeBlockKind;
|
||||||
use futures::FutureExt;
|
use futures::FutureExt;
|
||||||
use gpui::{
|
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,
|
DispatchPhase, Edges, FocusHandle, FocusableView, FontStyle, FontWeight, GlobalElementId,
|
||||||
Hitbox, Hsla, KeyContext, Length, MouseDownEvent, MouseEvent, MouseMoveEvent, MouseUpEvent,
|
Hitbox, Hsla, KeyContext, Length, MouseDownEvent, MouseEvent, MouseMoveEvent, MouseUpEvent,
|
||||||
Point, Render, StrikethroughStyle, StyleRefinement, StyledText, Task, TextLayout, TextRun,
|
Point, Render, StrikethroughStyle, StyleRefinement, StyledText, Task, TextLayout, TextRun,
|
||||||
@@ -761,7 +761,7 @@ impl Element for MarkdownElement {
|
|||||||
context.add("Markdown");
|
context.add("Markdown");
|
||||||
cx.set_key_context(context);
|
cx.set_key_context(context);
|
||||||
let view = self.markdown.clone();
|
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();
|
let text = rendered_markdown.text.clone();
|
||||||
move |_, phase, cx| {
|
move |_, phase, cx| {
|
||||||
let text = text.clone();
|
let text = text.clone();
|
||||||
|
|||||||
@@ -119,7 +119,7 @@ impl KeymapFile {
|
|||||||
Ok(())
|
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()
|
let mut root_schema = SchemaSettings::draft07()
|
||||||
.with(|settings| settings.option_add_null_type = false)
|
.with(|settings| settings.option_add_null_type = false)
|
||||||
.into_generator()
|
.into_generator()
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
mod appearance_settings_controls;
|
mod appearance_settings_controls;
|
||||||
|
|
||||||
use std::any::TypeId;
|
|
||||||
|
|
||||||
use command_palette_hooks::CommandPaletteFilter;
|
use command_palette_hooks::CommandPaletteFilter;
|
||||||
use editor::EditorSettingsControls;
|
use editor::EditorSettingsControls;
|
||||||
use feature_flags::{FeatureFlag, FeatureFlagViewExt};
|
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 ui::prelude::*;
|
||||||
use workspace::item::{Item, ItemEvent};
|
use workspace::item::{Item, ItemEvent};
|
||||||
use workspace::Workspace;
|
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| {
|
CommandPaletteFilter::update_global(cx, |filter, _cx| {
|
||||||
filter.hide_action_types(&settings_ui_actions);
|
filter.hide_action_types(&settings_ui_actions);
|
||||||
|
|||||||
@@ -17,8 +17,9 @@ use command_palette_hooks::CommandPaletteFilter;
|
|||||||
use editor::{scroll::Autoscroll, Editor, MultiBuffer};
|
use editor::{scroll::Autoscroll, Editor, MultiBuffer};
|
||||||
use feature_flags::FeatureFlagAppExt;
|
use feature_flags::FeatureFlagAppExt;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, point, px, AppContext, AsyncAppContext, Context, FocusableView, MenuItem, PromptLevel,
|
actions, point, px, ActionTypeId, AppContext, AsyncAppContext, Context, FocusableView,
|
||||||
ReadGlobal, TitlebarOptions, View, ViewContext, VisualContext, WindowKind, WindowOptions,
|
MenuItem, PromptLevel, ReadGlobal, TitlebarOptions, View, ViewContext, VisualContext,
|
||||||
|
WindowKind, WindowOptions,
|
||||||
};
|
};
|
||||||
pub use open_listener::*;
|
pub use open_listener::*;
|
||||||
|
|
||||||
@@ -552,7 +553,7 @@ pub fn initialize_workspace(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn feature_gate_zed_pro_actions(cx: &mut AppContext) {
|
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| {
|
CommandPaletteFilter::update_global(cx, |filter, _cx| {
|
||||||
filter.hide_action_types(&zed_pro_actions);
|
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