Add a live Rust style editor to inspector to edit a sequence of no-argument style modifiers (#31443)
Editing JSON styles is not very helpful for bringing style changes back to the actual code. This PR adds a buffer that pretends to be Rust, applying any style attribute identifiers it finds. Also supports completions with display of documentation. The effect of the currently selected completion is previewed. Warning diagnostics appear on any unrecognized identifier. https://github.com/user-attachments/assets/af39ff0a-26a5-4835-a052-d8f642b2080c Adds a `#[derive_inspector_reflection]` macro which allows these methods to be enumerated and called by their name. The macro code changes were 95% generated by Zed Agent + Opus 4. Release Notes: * Added an element inspector for development. On debug builds, `dev::ToggleInspector` will open a pane allowing inspecting of element info and modifying styles.
This commit is contained in:
parent
6253b95f82
commit
649072d140
35 changed files with 1778 additions and 316 deletions
|
@ -1,9 +1,9 @@
|
|||
use fuzzy::{StringMatch, StringMatchCandidate};
|
||||
use gpui::{
|
||||
AnyElement, BackgroundExecutor, Entity, Focusable, FontWeight, ListSizingBehavior,
|
||||
ScrollStrategy, SharedString, Size, StrikethroughStyle, StyledText, UniformListScrollHandle,
|
||||
div, px, uniform_list,
|
||||
AnyElement, Entity, Focusable, FontWeight, ListSizingBehavior, ScrollStrategy, SharedString,
|
||||
Size, StrikethroughStyle, StyledText, UniformListScrollHandle, div, px, uniform_list,
|
||||
};
|
||||
use gpui::{AsyncWindowContext, WeakEntity};
|
||||
use language::Buffer;
|
||||
use language::CodeLabel;
|
||||
use markdown::{Markdown, MarkdownElement};
|
||||
|
@ -50,11 +50,12 @@ impl CodeContextMenu {
|
|||
pub fn select_first(
|
||||
&mut self,
|
||||
provider: Option<&dyn CompletionProvider>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Editor>,
|
||||
) -> bool {
|
||||
if self.visible() {
|
||||
match self {
|
||||
CodeContextMenu::Completions(menu) => menu.select_first(provider, cx),
|
||||
CodeContextMenu::Completions(menu) => menu.select_first(provider, window, cx),
|
||||
CodeContextMenu::CodeActions(menu) => menu.select_first(cx),
|
||||
}
|
||||
true
|
||||
|
@ -66,11 +67,12 @@ impl CodeContextMenu {
|
|||
pub fn select_prev(
|
||||
&mut self,
|
||||
provider: Option<&dyn CompletionProvider>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Editor>,
|
||||
) -> bool {
|
||||
if self.visible() {
|
||||
match self {
|
||||
CodeContextMenu::Completions(menu) => menu.select_prev(provider, cx),
|
||||
CodeContextMenu::Completions(menu) => menu.select_prev(provider, window, cx),
|
||||
CodeContextMenu::CodeActions(menu) => menu.select_prev(cx),
|
||||
}
|
||||
true
|
||||
|
@ -82,11 +84,12 @@ impl CodeContextMenu {
|
|||
pub fn select_next(
|
||||
&mut self,
|
||||
provider: Option<&dyn CompletionProvider>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Editor>,
|
||||
) -> bool {
|
||||
if self.visible() {
|
||||
match self {
|
||||
CodeContextMenu::Completions(menu) => menu.select_next(provider, cx),
|
||||
CodeContextMenu::Completions(menu) => menu.select_next(provider, window, cx),
|
||||
CodeContextMenu::CodeActions(menu) => menu.select_next(cx),
|
||||
}
|
||||
true
|
||||
|
@ -98,11 +101,12 @@ impl CodeContextMenu {
|
|||
pub fn select_last(
|
||||
&mut self,
|
||||
provider: Option<&dyn CompletionProvider>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Editor>,
|
||||
) -> bool {
|
||||
if self.visible() {
|
||||
match self {
|
||||
CodeContextMenu::Completions(menu) => menu.select_last(provider, cx),
|
||||
CodeContextMenu::Completions(menu) => menu.select_last(provider, window, cx),
|
||||
CodeContextMenu::CodeActions(menu) => menu.select_last(cx),
|
||||
}
|
||||
true
|
||||
|
@ -290,6 +294,7 @@ impl CompletionsMenu {
|
|||
fn select_first(
|
||||
&mut self,
|
||||
provider: Option<&dyn CompletionProvider>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Editor>,
|
||||
) {
|
||||
let index = if self.scroll_handle.y_flipped() {
|
||||
|
@ -297,40 +302,56 @@ impl CompletionsMenu {
|
|||
} else {
|
||||
0
|
||||
};
|
||||
self.update_selection_index(index, provider, cx);
|
||||
self.update_selection_index(index, provider, window, cx);
|
||||
}
|
||||
|
||||
fn select_last(&mut self, provider: Option<&dyn CompletionProvider>, cx: &mut Context<Editor>) {
|
||||
fn select_last(
|
||||
&mut self,
|
||||
provider: Option<&dyn CompletionProvider>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Editor>,
|
||||
) {
|
||||
let index = if self.scroll_handle.y_flipped() {
|
||||
0
|
||||
} else {
|
||||
self.entries.borrow().len() - 1
|
||||
};
|
||||
self.update_selection_index(index, provider, cx);
|
||||
self.update_selection_index(index, provider, window, cx);
|
||||
}
|
||||
|
||||
fn select_prev(&mut self, provider: Option<&dyn CompletionProvider>, cx: &mut Context<Editor>) {
|
||||
fn select_prev(
|
||||
&mut self,
|
||||
provider: Option<&dyn CompletionProvider>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Editor>,
|
||||
) {
|
||||
let index = if self.scroll_handle.y_flipped() {
|
||||
self.next_match_index()
|
||||
} else {
|
||||
self.prev_match_index()
|
||||
};
|
||||
self.update_selection_index(index, provider, cx);
|
||||
self.update_selection_index(index, provider, window, cx);
|
||||
}
|
||||
|
||||
fn select_next(&mut self, provider: Option<&dyn CompletionProvider>, cx: &mut Context<Editor>) {
|
||||
fn select_next(
|
||||
&mut self,
|
||||
provider: Option<&dyn CompletionProvider>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Editor>,
|
||||
) {
|
||||
let index = if self.scroll_handle.y_flipped() {
|
||||
self.prev_match_index()
|
||||
} else {
|
||||
self.next_match_index()
|
||||
};
|
||||
self.update_selection_index(index, provider, cx);
|
||||
self.update_selection_index(index, provider, window, cx);
|
||||
}
|
||||
|
||||
fn update_selection_index(
|
||||
&mut self,
|
||||
match_index: usize,
|
||||
provider: Option<&dyn CompletionProvider>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Editor>,
|
||||
) {
|
||||
if self.selected_item != match_index {
|
||||
|
@ -338,6 +359,9 @@ impl CompletionsMenu {
|
|||
self.scroll_handle
|
||||
.scroll_to_item(self.selected_item, ScrollStrategy::Top);
|
||||
self.resolve_visible_completions(provider, cx);
|
||||
if let Some(provider) = provider {
|
||||
self.handle_selection_changed(provider, window, cx);
|
||||
}
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
@ -358,6 +382,21 @@ impl CompletionsMenu {
|
|||
}
|
||||
}
|
||||
|
||||
fn handle_selection_changed(
|
||||
&self,
|
||||
provider: &dyn CompletionProvider,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) {
|
||||
let entries = self.entries.borrow();
|
||||
let entry = if self.selected_item < entries.len() {
|
||||
Some(&entries[self.selected_item])
|
||||
} else {
|
||||
None
|
||||
};
|
||||
provider.selection_changed(entry, window, cx);
|
||||
}
|
||||
|
||||
pub fn resolve_visible_completions(
|
||||
&mut self,
|
||||
provider: Option<&dyn CompletionProvider>,
|
||||
|
@ -753,7 +792,13 @@ impl CompletionsMenu {
|
|||
});
|
||||
}
|
||||
|
||||
pub async fn filter(&mut self, query: Option<&str>, executor: BackgroundExecutor) {
|
||||
pub async fn filter(
|
||||
&mut self,
|
||||
query: Option<&str>,
|
||||
provider: Option<Rc<dyn CompletionProvider>>,
|
||||
editor: WeakEntity<Editor>,
|
||||
cx: &mut AsyncWindowContext,
|
||||
) {
|
||||
let mut matches = if let Some(query) = query {
|
||||
fuzzy::match_strings(
|
||||
&self.match_candidates,
|
||||
|
@ -761,7 +806,7 @@ impl CompletionsMenu {
|
|||
query.chars().any(|c| c.is_uppercase()),
|
||||
100,
|
||||
&Default::default(),
|
||||
executor,
|
||||
cx.background_executor().clone(),
|
||||
)
|
||||
.await
|
||||
} else {
|
||||
|
@ -822,6 +867,28 @@ impl CompletionsMenu {
|
|||
self.selected_item = 0;
|
||||
// This keeps the display consistent when y_flipped.
|
||||
self.scroll_handle.scroll_to_item(0, ScrollStrategy::Top);
|
||||
|
||||
if let Some(provider) = provider {
|
||||
cx.update(|window, cx| {
|
||||
// Since this is async, it's possible the menu has been closed and possibly even
|
||||
// another opened. `provider.selection_changed` should not be called in this case.
|
||||
let this_menu_still_active = editor
|
||||
.read_with(cx, |editor, _cx| {
|
||||
if let Some(CodeContextMenu::Completions(completions_menu)) =
|
||||
editor.context_menu.borrow().as_ref()
|
||||
{
|
||||
completions_menu.id == self.id
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
.unwrap_or(false);
|
||||
if this_menu_still_active {
|
||||
self.handle_selection_changed(&*provider, window, cx);
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -77,7 +77,7 @@ use futures::{
|
|||
FutureExt,
|
||||
future::{self, Shared, join},
|
||||
};
|
||||
use fuzzy::StringMatchCandidate;
|
||||
use fuzzy::{StringMatch, StringMatchCandidate};
|
||||
|
||||
use ::git::blame::BlameEntry;
|
||||
use ::git::{Restore, blame::ParsedCommitMessage};
|
||||
|
@ -912,7 +912,7 @@ pub struct Editor {
|
|||
// TODO: make this a access method
|
||||
pub project: Option<Entity<Project>>,
|
||||
semantics_provider: Option<Rc<dyn SemanticsProvider>>,
|
||||
completion_provider: Option<Box<dyn CompletionProvider>>,
|
||||
completion_provider: Option<Rc<dyn CompletionProvider>>,
|
||||
collaboration_hub: Option<Box<dyn CollaborationHub>>,
|
||||
blink_manager: Entity<BlinkManager>,
|
||||
show_cursor_names: bool,
|
||||
|
@ -1755,7 +1755,7 @@ impl Editor {
|
|||
soft_wrap_mode_override,
|
||||
diagnostics_max_severity,
|
||||
hard_wrap: None,
|
||||
completion_provider: project.clone().map(|project| Box::new(project) as _),
|
||||
completion_provider: project.clone().map(|project| Rc::new(project) as _),
|
||||
semantics_provider: project.clone().map(|project| Rc::new(project) as _),
|
||||
collaboration_hub: project.clone().map(|project| Box::new(project) as _),
|
||||
project,
|
||||
|
@ -2374,7 +2374,7 @@ impl Editor {
|
|||
self.custom_context_menu = Some(Box::new(f))
|
||||
}
|
||||
|
||||
pub fn set_completion_provider(&mut self, provider: Option<Box<dyn CompletionProvider>>) {
|
||||
pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
|
||||
self.completion_provider = provider;
|
||||
}
|
||||
|
||||
|
@ -2684,9 +2684,10 @@ impl Editor {
|
|||
drop(context_menu);
|
||||
|
||||
let query = Self::completion_query(buffer, cursor_position);
|
||||
cx.spawn(async move |this, cx| {
|
||||
let completion_provider = self.completion_provider.clone();
|
||||
cx.spawn_in(window, async move |this, cx| {
|
||||
completion_menu
|
||||
.filter(query.as_deref(), cx.background_executor().clone())
|
||||
.filter(query.as_deref(), completion_provider, this.clone(), cx)
|
||||
.await;
|
||||
|
||||
this.update(cx, |this, cx| {
|
||||
|
@ -4960,15 +4961,16 @@ impl Editor {
|
|||
let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
|
||||
..buffer_snapshot.point_to_offset(max_word_search);
|
||||
|
||||
let provider = self
|
||||
.completion_provider
|
||||
.as_ref()
|
||||
.filter(|_| !ignore_completion_provider);
|
||||
let provider = if ignore_completion_provider {
|
||||
None
|
||||
} else {
|
||||
self.completion_provider.clone()
|
||||
};
|
||||
let skip_digits = query
|
||||
.as_ref()
|
||||
.map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
|
||||
|
||||
let (mut words, provided_completions) = match provider {
|
||||
let (mut words, provided_completions) = match &provider {
|
||||
Some(provider) => {
|
||||
let completions = provider.completions(
|
||||
position.excerpt_id,
|
||||
|
@ -5071,7 +5073,9 @@ impl Editor {
|
|||
} else {
|
||||
None
|
||||
},
|
||||
cx.background_executor().clone(),
|
||||
provider,
|
||||
editor.clone(),
|
||||
cx,
|
||||
)
|
||||
.await;
|
||||
|
||||
|
@ -8651,6 +8655,11 @@ impl Editor {
|
|||
let context_menu = self.context_menu.borrow_mut().take();
|
||||
self.stale_inline_completion_in_menu.take();
|
||||
self.update_visible_inline_completion(window, cx);
|
||||
if let Some(CodeContextMenu::Completions(_)) = &context_menu {
|
||||
if let Some(completion_provider) = &self.completion_provider {
|
||||
completion_provider.selection_changed(None, window, cx);
|
||||
}
|
||||
}
|
||||
context_menu
|
||||
}
|
||||
|
||||
|
@ -11353,7 +11362,7 @@ impl Editor {
|
|||
.context_menu
|
||||
.borrow_mut()
|
||||
.as_mut()
|
||||
.map(|menu| menu.select_first(self.completion_provider.as_deref(), cx))
|
||||
.map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
|
||||
.unwrap_or(false)
|
||||
{
|
||||
return;
|
||||
|
@ -11477,7 +11486,7 @@ impl Editor {
|
|||
.context_menu
|
||||
.borrow_mut()
|
||||
.as_mut()
|
||||
.map(|menu| menu.select_last(self.completion_provider.as_deref(), cx))
|
||||
.map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
|
||||
.unwrap_or(false)
|
||||
{
|
||||
return;
|
||||
|
@ -11532,44 +11541,44 @@ impl Editor {
|
|||
pub fn context_menu_first(
|
||||
&mut self,
|
||||
_: &ContextMenuFirst,
|
||||
_window: &mut Window,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
|
||||
context_menu.select_first(self.completion_provider.as_deref(), cx);
|
||||
context_menu.select_first(self.completion_provider.as_deref(), window, cx);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn context_menu_prev(
|
||||
&mut self,
|
||||
_: &ContextMenuPrevious,
|
||||
_window: &mut Window,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
|
||||
context_menu.select_prev(self.completion_provider.as_deref(), cx);
|
||||
context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn context_menu_next(
|
||||
&mut self,
|
||||
_: &ContextMenuNext,
|
||||
_window: &mut Window,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
|
||||
context_menu.select_next(self.completion_provider.as_deref(), cx);
|
||||
context_menu.select_next(self.completion_provider.as_deref(), window, cx);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn context_menu_last(
|
||||
&mut self,
|
||||
_: &ContextMenuLast,
|
||||
_window: &mut Window,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
|
||||
context_menu.select_last(self.completion_provider.as_deref(), cx);
|
||||
context_menu.select_last(self.completion_provider.as_deref(), window, cx);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -19615,6 +19624,8 @@ pub trait CompletionProvider {
|
|||
cx: &mut Context<Editor>,
|
||||
) -> bool;
|
||||
|
||||
fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
|
||||
|
||||
fn sort_completions(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue