Merge branch 'main' into editor-tests

This commit is contained in:
Mikayla
2023-11-13 15:53:22 -08:00
48 changed files with 2704 additions and 2369 deletions
+22 -63
View File
@@ -6,9 +6,12 @@ use gpui::{
WeakView, WindowContext,
};
use picker::{Picker, PickerDelegate};
use std::cmp::{self, Reverse};
use std::{
cmp::{self, Reverse},
sync::Arc,
};
use theme::ActiveTheme;
use ui::{v_stack, Label, StyledExt};
use ui::{h_stack, v_stack, HighlightedLabel, KeyBinding, StyledExt};
use util::{
channel::{parse_zed_link, ReleaseChannel, RELEASE_CHANNEL},
ResultExt,
@@ -147,6 +150,10 @@ impl CommandPaletteDelegate {
impl PickerDelegate for CommandPaletteDelegate {
type ListItem = Div<Picker<Self>>;
fn placeholder_text(&self) -> Arc<str> {
"Execute a command...".into()
}
fn match_count(&self) -> usize {
self.matches.len()
}
@@ -296,11 +303,10 @@ impl PickerDelegate for CommandPaletteDelegate {
cx: &mut ViewContext<Picker<Self>>,
) -> Self::ListItem {
let colors = cx.theme().colors();
let Some(command) = self
.matches
.get(ix)
.and_then(|m| self.commands.get(m.candidate_id))
else {
let Some(r#match) = self.matches.get(ix) else {
return div();
};
let Some(command) = self.commands.get(r#match.candidate_id) else {
return div();
};
@@ -312,63 +318,16 @@ impl PickerDelegate for CommandPaletteDelegate {
.rounded_md()
.when(selected, |this| this.bg(colors.ghost_element_selected))
.hover(|this| this.bg(colors.ghost_element_hover))
.child(Label::new(command.name.clone()))
.child(
h_stack()
.justify_between()
.child(HighlightedLabel::new(
command.name.clone(),
r#match.positions.clone(),
))
.children(KeyBinding::for_action(&*command.action, cx)),
)
}
// fn render_match(
// &self,
// ix: usize,
// mouse_state: &mut MouseState,
// selected: bool,
// cx: &gpui::AppContext,
// ) -> AnyElement<Picker<Self>> {
// let mat = &self.matches[ix];
// let command = &self.actions[mat.candidate_id];
// let theme = theme::current(cx);
// let style = theme.picker.item.in_state(selected).style_for(mouse_state);
// let key_style = &theme.command_palette.key.in_state(selected);
// let keystroke_spacing = theme.command_palette.keystroke_spacing;
// Flex::row()
// .with_child(
// Label::new(mat.string.clone(), style.label.clone())
// .with_highlights(mat.positions.clone()),
// )
// .with_children(command.keystrokes.iter().map(|keystroke| {
// Flex::row()
// .with_children(
// [
// (keystroke.ctrl, "^"),
// (keystroke.alt, "⌥"),
// (keystroke.cmd, "⌘"),
// (keystroke.shift, "⇧"),
// ]
// .into_iter()
// .filter_map(|(modifier, label)| {
// if modifier {
// Some(
// Label::new(label, key_style.label.clone())
// .contained()
// .with_style(key_style.container),
// )
// } else {
// None
// }
// }),
// )
// .with_child(
// Label::new(keystroke.key.clone(), key_style.label.clone())
// .contained()
// .with_style(key_style.container),
// )
// .contained()
// .with_margin_left(keystroke_spacing)
// .flex_float()
// }))
// .contained()
// .with_style(style.container)
// .into_any()
// }
}
fn humanize_action_name(name: &str) -> String {
+11 -11
View File
@@ -41,8 +41,8 @@ use git::diff_hunk_to_display;
use gpui::{
action, actions, div, point, px, relative, rems, size, uniform_list, AnyElement, AppContext,
AsyncWindowContext, BackgroundExecutor, Bounds, ClipboardItem, Component, Context,
DispatchContext, EventEmitter, FocusHandle, FontFeatures, FontStyle, FontWeight,
HighlightStyle, Hsla, InputHandler, Model, MouseButton, ParentElement, Pixels, Render,
EventEmitter, FocusHandle, FontFeatures, FontStyle, FontWeight, HighlightStyle, Hsla,
InputHandler, KeyContext, Model, MouseButton, ParentElement, Pixels, Render,
StatelessInteractive, Styled, Subscription, Task, TextStyle, UniformListScrollHandle, View,
ViewContext, VisualContext, WeakView, WindowContext,
};
@@ -646,7 +646,7 @@ pub struct Editor {
collapse_matches: bool,
autoindent_mode: Option<AutoindentMode>,
workspace: Option<(WeakView<Workspace>, i64)>,
keymap_context_layers: BTreeMap<TypeId, DispatchContext>,
keymap_context_layers: BTreeMap<TypeId, KeyContext>,
input_enabled: bool,
read_only: bool,
leader_peer_id: Option<PeerId>,
@@ -1981,9 +1981,9 @@ impl Editor {
this
}
fn dispatch_context(&self, cx: &AppContext) -> DispatchContext {
let mut dispatch_context = DispatchContext::default();
dispatch_context.insert("Editor");
fn dispatch_context(&self, cx: &AppContext) -> KeyContext {
let mut dispatch_context = KeyContext::default();
dispatch_context.add("Editor");
let mode = match self.mode {
EditorMode::SingleLine => "single_line",
EditorMode::AutoHeight { .. } => "auto_height",
@@ -1991,17 +1991,17 @@ impl Editor {
};
dispatch_context.set("mode", mode);
if self.pending_rename.is_some() {
dispatch_context.insert("renaming");
dispatch_context.add("renaming");
}
if self.context_menu_visible() {
match self.context_menu.read().as_ref() {
Some(ContextMenu::Completions(_)) => {
dispatch_context.insert("menu");
dispatch_context.insert("showing_completions")
dispatch_context.add("menu");
dispatch_context.add("showing_completions")
}
Some(ContextMenu::CodeActions(_)) => {
dispatch_context.insert("menu");
dispatch_context.insert("showing_code_actions")
dispatch_context.add("menu");
dispatch_context.add("showing_code_actions")
}
None => {}
}
+174 -213
View File
@@ -16,11 +16,11 @@ use anyhow::Result;
use collections::{BTreeMap, HashMap};
use gpui::{
black, hsla, point, px, relative, size, transparent_black, Action, AnyElement, AvailableSpace,
BorrowAppContext, BorrowWindow, Bounds, ContentMask, Corners, DispatchContext, DispatchPhase,
Edges, Element, ElementId, ElementInputHandler, Entity, FocusHandle, GlobalElementId, Hsla,
InputHandler, KeyDownEvent, KeyListener, KeyMatch, Line, LineLayout, Modifiers, MouseButton,
MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, ScrollWheelEvent, ShapedGlyph, Size,
Style, TextRun, TextStyle, TextSystem, ViewContext, WindowContext, WrappedLineLayout,
BorrowAppContext, BorrowWindow, Bounds, ContentMask, Corners, DispatchPhase, Edges, Element,
ElementId, ElementInputHandler, Entity, FocusHandle, GlobalElementId, Hsla, InputHandler,
KeyContext, KeyDownEvent, KeyMatch, Line, LineLayout, Modifiers, MouseButton, MouseDownEvent,
MouseMoveEvent, MouseUpEvent, Pixels, ScrollWheelEvent, ShapedGlyph, Size, Style, TextRun,
TextStyle, TextSystem, ViewContext, WindowContext, WrappedLineLayout,
};
use itertools::Itertools;
use language::language_settings::ShowWhitespaceSetting;
@@ -2459,11 +2459,170 @@ impl Element<Editor> for EditorElement {
let dispatch_context = editor.dispatch_context(cx);
cx.with_element_id(cx.view().entity_id(), |global_id, cx| {
cx.with_key_dispatch_context(dispatch_context, |cx| {
cx.with_key_listeners(build_key_listeners(global_id), |cx| {
cx.with_focus(editor.focus_handle.clone(), |_| {})
});
})
cx.with_key_dispatch(
dispatch_context,
Some(editor.focus_handle.clone()),
|_, cx| {
register_action(cx, Editor::move_left);
register_action(cx, Editor::move_right);
register_action(cx, Editor::move_down);
register_action(cx, Editor::move_up);
// on_action(cx, Editor::new_file); todo!()
// on_action(cx, Editor::new_file_in_direction); todo!()
register_action(cx, Editor::cancel);
register_action(cx, Editor::newline);
register_action(cx, Editor::newline_above);
register_action(cx, Editor::newline_below);
register_action(cx, Editor::backspace);
register_action(cx, Editor::delete);
register_action(cx, Editor::tab);
register_action(cx, Editor::tab_prev);
register_action(cx, Editor::indent);
register_action(cx, Editor::outdent);
register_action(cx, Editor::delete_line);
register_action(cx, Editor::join_lines);
register_action(cx, Editor::sort_lines_case_sensitive);
register_action(cx, Editor::sort_lines_case_insensitive);
register_action(cx, Editor::reverse_lines);
register_action(cx, Editor::shuffle_lines);
register_action(cx, Editor::convert_to_upper_case);
register_action(cx, Editor::convert_to_lower_case);
register_action(cx, Editor::convert_to_title_case);
register_action(cx, Editor::convert_to_snake_case);
register_action(cx, Editor::convert_to_kebab_case);
register_action(cx, Editor::convert_to_upper_camel_case);
register_action(cx, Editor::convert_to_lower_camel_case);
register_action(cx, Editor::delete_to_previous_word_start);
register_action(cx, Editor::delete_to_previous_subword_start);
register_action(cx, Editor::delete_to_next_word_end);
register_action(cx, Editor::delete_to_next_subword_end);
register_action(cx, Editor::delete_to_beginning_of_line);
register_action(cx, Editor::delete_to_end_of_line);
register_action(cx, Editor::cut_to_end_of_line);
register_action(cx, Editor::duplicate_line);
register_action(cx, Editor::move_line_up);
register_action(cx, Editor::move_line_down);
register_action(cx, Editor::transpose);
register_action(cx, Editor::cut);
register_action(cx, Editor::copy);
register_action(cx, Editor::paste);
register_action(cx, Editor::undo);
register_action(cx, Editor::redo);
register_action(cx, Editor::move_page_up);
register_action(cx, Editor::move_page_down);
register_action(cx, Editor::next_screen);
register_action(cx, Editor::scroll_cursor_top);
register_action(cx, Editor::scroll_cursor_center);
register_action(cx, Editor::scroll_cursor_bottom);
register_action(cx, |editor, _: &LineDown, cx| {
editor.scroll_screen(&ScrollAmount::Line(1.), cx)
});
register_action(cx, |editor, _: &LineUp, cx| {
editor.scroll_screen(&ScrollAmount::Line(-1.), cx)
});
register_action(cx, |editor, _: &HalfPageDown, cx| {
editor.scroll_screen(&ScrollAmount::Page(0.5), cx)
});
register_action(cx, |editor, _: &HalfPageUp, cx| {
editor.scroll_screen(&ScrollAmount::Page(-0.5), cx)
});
register_action(cx, |editor, _: &PageDown, cx| {
editor.scroll_screen(&ScrollAmount::Page(1.), cx)
});
register_action(cx, |editor, _: &PageUp, cx| {
editor.scroll_screen(&ScrollAmount::Page(-1.), cx)
});
register_action(cx, Editor::move_to_previous_word_start);
register_action(cx, Editor::move_to_previous_subword_start);
register_action(cx, Editor::move_to_next_word_end);
register_action(cx, Editor::move_to_next_subword_end);
register_action(cx, Editor::move_to_beginning_of_line);
register_action(cx, Editor::move_to_end_of_line);
register_action(cx, Editor::move_to_start_of_paragraph);
register_action(cx, Editor::move_to_end_of_paragraph);
register_action(cx, Editor::move_to_beginning);
register_action(cx, Editor::move_to_end);
register_action(cx, Editor::select_up);
register_action(cx, Editor::select_down);
register_action(cx, Editor::select_left);
register_action(cx, Editor::select_right);
register_action(cx, Editor::select_to_previous_word_start);
register_action(cx, Editor::select_to_previous_subword_start);
register_action(cx, Editor::select_to_next_word_end);
register_action(cx, Editor::select_to_next_subword_end);
register_action(cx, Editor::select_to_beginning_of_line);
register_action(cx, Editor::select_to_end_of_line);
register_action(cx, Editor::select_to_start_of_paragraph);
register_action(cx, Editor::select_to_end_of_paragraph);
register_action(cx, Editor::select_to_beginning);
register_action(cx, Editor::select_to_end);
register_action(cx, Editor::select_all);
register_action(cx, |editor, action, cx| {
editor.select_all_matches(action, cx).log_err();
});
register_action(cx, Editor::select_line);
register_action(cx, Editor::split_selection_into_lines);
register_action(cx, Editor::add_selection_above);
register_action(cx, Editor::add_selection_below);
register_action(cx, |editor, action, cx| {
editor.select_next(action, cx).log_err();
});
register_action(cx, |editor, action, cx| {
editor.select_previous(action, cx).log_err();
});
register_action(cx, Editor::toggle_comments);
register_action(cx, Editor::select_larger_syntax_node);
register_action(cx, Editor::select_smaller_syntax_node);
register_action(cx, Editor::move_to_enclosing_bracket);
register_action(cx, Editor::undo_selection);
register_action(cx, Editor::redo_selection);
register_action(cx, Editor::go_to_diagnostic);
register_action(cx, Editor::go_to_prev_diagnostic);
register_action(cx, Editor::go_to_hunk);
register_action(cx, Editor::go_to_prev_hunk);
register_action(cx, Editor::go_to_definition);
register_action(cx, Editor::go_to_definition_split);
register_action(cx, Editor::go_to_type_definition);
register_action(cx, Editor::go_to_type_definition_split);
register_action(cx, Editor::fold);
register_action(cx, Editor::fold_at);
register_action(cx, Editor::unfold_lines);
register_action(cx, Editor::unfold_at);
register_action(cx, Editor::fold_selected_ranges);
register_action(cx, Editor::show_completions);
register_action(cx, Editor::toggle_code_actions);
// on_action(cx, Editor::open_excerpts); todo!()
register_action(cx, Editor::toggle_soft_wrap);
register_action(cx, Editor::toggle_inlay_hints);
register_action(cx, Editor::reveal_in_finder);
register_action(cx, Editor::copy_path);
register_action(cx, Editor::copy_relative_path);
register_action(cx, Editor::copy_highlight_json);
register_action(cx, |editor, action, cx| {
editor
.format(action, cx)
.map(|task| task.detach_and_log_err(cx));
});
register_action(cx, Editor::restart_language_server);
register_action(cx, Editor::show_character_palette);
// on_action(cx, Editor::confirm_completion); todo!()
register_action(cx, |editor, action, cx| {
editor
.confirm_code_action(action, cx)
.map(|task| task.detach_and_log_err(cx));
});
// on_action(cx, Editor::rename); todo!()
// on_action(cx, Editor::confirm_rename); todo!()
// on_action(cx, Editor::find_all_references); todo!()
register_action(cx, Editor::next_copilot_suggestion);
register_action(cx, Editor::previous_copilot_suggestion);
register_action(cx, Editor::copilot_suggest);
register_action(cx, Editor::context_menu_first);
register_action(cx, Editor::context_menu_prev);
register_action(cx, Editor::context_menu_next);
register_action(cx, Editor::context_menu_last);
},
)
});
}
@@ -3998,212 +4157,14 @@ fn scale_horizontal_mouse_autoscroll_delta(delta: Pixels) -> f32 {
// }
// }
fn build_key_listeners(
global_element_id: GlobalElementId,
) -> impl IntoIterator<Item = (TypeId, KeyListener<Editor>)> {
[
build_action_listener(Editor::move_left),
build_action_listener(Editor::move_right),
build_action_listener(Editor::move_down),
build_action_listener(Editor::move_up),
// build_action_listener(Editor::new_file), todo!()
// build_action_listener(Editor::new_file_in_direction), todo!()
build_action_listener(Editor::cancel),
build_action_listener(Editor::newline),
build_action_listener(Editor::newline_above),
build_action_listener(Editor::newline_below),
build_action_listener(Editor::backspace),
build_action_listener(Editor::delete),
build_action_listener(Editor::tab),
build_action_listener(Editor::tab_prev),
build_action_listener(Editor::indent),
build_action_listener(Editor::outdent),
build_action_listener(Editor::delete_line),
build_action_listener(Editor::join_lines),
build_action_listener(Editor::sort_lines_case_sensitive),
build_action_listener(Editor::sort_lines_case_insensitive),
build_action_listener(Editor::reverse_lines),
build_action_listener(Editor::shuffle_lines),
build_action_listener(Editor::convert_to_upper_case),
build_action_listener(Editor::convert_to_lower_case),
build_action_listener(Editor::convert_to_title_case),
build_action_listener(Editor::convert_to_snake_case),
build_action_listener(Editor::convert_to_kebab_case),
build_action_listener(Editor::convert_to_upper_camel_case),
build_action_listener(Editor::convert_to_lower_camel_case),
build_action_listener(Editor::delete_to_previous_word_start),
build_action_listener(Editor::delete_to_previous_subword_start),
build_action_listener(Editor::delete_to_next_word_end),
build_action_listener(Editor::delete_to_next_subword_end),
build_action_listener(Editor::delete_to_beginning_of_line),
build_action_listener(Editor::delete_to_end_of_line),
build_action_listener(Editor::cut_to_end_of_line),
build_action_listener(Editor::duplicate_line),
build_action_listener(Editor::move_line_up),
build_action_listener(Editor::move_line_down),
build_action_listener(Editor::transpose),
build_action_listener(Editor::cut),
build_action_listener(Editor::copy),
build_action_listener(Editor::paste),
build_action_listener(Editor::undo),
build_action_listener(Editor::redo),
build_action_listener(Editor::move_page_up),
build_action_listener(Editor::move_page_down),
build_action_listener(Editor::next_screen),
build_action_listener(Editor::scroll_cursor_top),
build_action_listener(Editor::scroll_cursor_center),
build_action_listener(Editor::scroll_cursor_bottom),
build_action_listener(|editor, _: &LineDown, cx| {
editor.scroll_screen(&ScrollAmount::Line(1.), cx)
}),
build_action_listener(|editor, _: &LineUp, cx| {
editor.scroll_screen(&ScrollAmount::Line(-1.), cx)
}),
build_action_listener(|editor, _: &HalfPageDown, cx| {
editor.scroll_screen(&ScrollAmount::Page(0.5), cx)
}),
build_action_listener(|editor, _: &HalfPageUp, cx| {
editor.scroll_screen(&ScrollAmount::Page(-0.5), cx)
}),
build_action_listener(|editor, _: &PageDown, cx| {
editor.scroll_screen(&ScrollAmount::Page(1.), cx)
}),
build_action_listener(|editor, _: &PageUp, cx| {
editor.scroll_screen(&ScrollAmount::Page(-1.), cx)
}),
build_action_listener(Editor::move_to_previous_word_start),
build_action_listener(Editor::move_to_previous_subword_start),
build_action_listener(Editor::move_to_next_word_end),
build_action_listener(Editor::move_to_next_subword_end),
build_action_listener(Editor::move_to_beginning_of_line),
build_action_listener(Editor::move_to_end_of_line),
build_action_listener(Editor::move_to_start_of_paragraph),
build_action_listener(Editor::move_to_end_of_paragraph),
build_action_listener(Editor::move_to_beginning),
build_action_listener(Editor::move_to_end),
build_action_listener(Editor::select_up),
build_action_listener(Editor::select_down),
build_action_listener(Editor::select_left),
build_action_listener(Editor::select_right),
build_action_listener(Editor::select_to_previous_word_start),
build_action_listener(Editor::select_to_previous_subword_start),
build_action_listener(Editor::select_to_next_word_end),
build_action_listener(Editor::select_to_next_subword_end),
build_action_listener(Editor::select_to_beginning_of_line),
build_action_listener(Editor::select_to_end_of_line),
build_action_listener(Editor::select_to_start_of_paragraph),
build_action_listener(Editor::select_to_end_of_paragraph),
build_action_listener(Editor::select_to_beginning),
build_action_listener(Editor::select_to_end),
build_action_listener(Editor::select_all),
build_action_listener(|editor, action, cx| {
editor.select_all_matches(action, cx).log_err();
}),
build_action_listener(Editor::select_line),
build_action_listener(Editor::split_selection_into_lines),
build_action_listener(Editor::add_selection_above),
build_action_listener(Editor::add_selection_below),
build_action_listener(|editor, action, cx| {
editor.select_next(action, cx).log_err();
}),
build_action_listener(|editor, action, cx| {
editor.select_previous(action, cx).log_err();
}),
build_action_listener(Editor::toggle_comments),
build_action_listener(Editor::select_larger_syntax_node),
build_action_listener(Editor::select_smaller_syntax_node),
build_action_listener(Editor::move_to_enclosing_bracket),
build_action_listener(Editor::undo_selection),
build_action_listener(Editor::redo_selection),
build_action_listener(Editor::go_to_diagnostic),
build_action_listener(Editor::go_to_prev_diagnostic),
build_action_listener(Editor::go_to_hunk),
build_action_listener(Editor::go_to_prev_hunk),
build_action_listener(Editor::go_to_definition),
build_action_listener(Editor::go_to_definition_split),
build_action_listener(Editor::go_to_type_definition),
build_action_listener(Editor::go_to_type_definition_split),
build_action_listener(Editor::fold),
build_action_listener(Editor::fold_at),
build_action_listener(Editor::unfold_lines),
build_action_listener(Editor::unfold_at),
build_action_listener(Editor::fold_selected_ranges),
build_action_listener(Editor::show_completions),
build_action_listener(Editor::toggle_code_actions),
// build_action_listener(Editor::open_excerpts), todo!()
build_action_listener(Editor::toggle_soft_wrap),
build_action_listener(Editor::toggle_inlay_hints),
build_action_listener(Editor::reveal_in_finder),
build_action_listener(Editor::copy_path),
build_action_listener(Editor::copy_relative_path),
build_action_listener(Editor::copy_highlight_json),
build_action_listener(|editor, action, cx| {
editor
.format(action, cx)
.map(|task| task.detach_and_log_err(cx));
}),
build_action_listener(Editor::restart_language_server),
build_action_listener(Editor::show_character_palette),
// build_action_listener(Editor::confirm_completion), todo!()
build_action_listener(|editor, action, cx| {
editor
.confirm_code_action(action, cx)
.map(|task| task.detach_and_log_err(cx));
}),
// build_action_listener(Editor::rename), todo!()
// build_action_listener(Editor::confirm_rename), todo!()
// build_action_listener(Editor::find_all_references), todo!()
build_action_listener(Editor::next_copilot_suggestion),
build_action_listener(Editor::previous_copilot_suggestion),
build_action_listener(Editor::copilot_suggest),
build_action_listener(Editor::context_menu_first),
build_action_listener(Editor::context_menu_prev),
build_action_listener(Editor::context_menu_next),
build_action_listener(Editor::context_menu_last),
build_key_listener(
move |editor, key_down: &KeyDownEvent, dispatch_context, phase, cx| {
if phase == DispatchPhase::Bubble {
if let KeyMatch::Some(action) = cx.match_keystroke(
&global_element_id,
&key_down.keystroke,
dispatch_context,
) {
return Some(action);
}
}
None
},
),
]
}
fn build_key_listener<T: 'static>(
listener: impl Fn(
&mut Editor,
&T,
&[&DispatchContext],
DispatchPhase,
&mut ViewContext<Editor>,
) -> Option<Box<dyn Action>>
+ 'static,
) -> (TypeId, KeyListener<Editor>) {
(
TypeId::of::<T>(),
Box::new(move |editor, event, dispatch_context, phase, cx| {
let key_event = event.downcast_ref::<T>()?;
listener(editor, key_event, dispatch_context, phase, cx)
}),
)
}
fn build_action_listener<T: Action>(
fn register_action<T: Action>(
cx: &mut ViewContext<Editor>,
listener: impl Fn(&mut Editor, &T, &mut ViewContext<Editor>) + 'static,
) -> (TypeId, KeyListener<Editor>) {
build_key_listener(move |editor, action: &T, dispatch_context, phase, cx| {
) {
cx.on_action(TypeId::of::<T>(), move |editor, action, phase, cx| {
let action = action.downcast_ref().unwrap();
if phase == DispatchPhase::Bubble {
listener(editor, action, cx);
}
None
})
}
+9 -5
View File
@@ -30,6 +30,7 @@ use std::{
};
use text::Selection;
use theme::{ActiveTheme, Theme};
use ui::{Label, LabelColor};
use util::{paths::PathExt, ResultExt, TryFutureExt};
use workspace::item::{BreadcrumbText, FollowEvent, FollowableEvents, FollowableItemHandle};
use workspace::{
@@ -595,16 +596,19 @@ impl Item for Editor {
.flex_row()
.items_center()
.gap_2()
.child(self.title(cx).to_string())
.child(Label::new(self.title(cx).to_string()))
.children(detail.and_then(|detail| {
let path = path_for_buffer(&self.buffer, detail, false, cx)?;
let description = path.to_string_lossy();
Some(
div()
.text_color(theme.colors().text_muted)
.text_xs()
.child(util::truncate_and_trailoff(&description, MAX_TAB_TITLE_LEN)),
div().child(
Label::new(util::truncate_and_trailoff(
&description,
MAX_TAB_TITLE_LEN,
))
.color(LabelColor::Muted),
),
)
})),
)
+3 -4
View File
@@ -1,8 +1,7 @@
use editor::{display_map::ToDisplayPoint, scroll::autoscroll::Autoscroll, Editor};
use gpui::{
actions, div, AppContext, Div, EventEmitter, ParentElement, Render, SharedString,
StatefulInteractivity, StatelessInteractive, Styled, Subscription, View, ViewContext,
VisualContext, WindowContext,
StatelessInteractive, Styled, Subscription, View, ViewContext, VisualContext, WindowContext,
};
use text::{Bias, Point};
use theme::ActiveTheme;
@@ -146,11 +145,11 @@ impl GoToLine {
}
impl Render for GoToLine {
type Element = Div<Self, StatefulInteractivity<Self>>;
type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
modal(cx)
.id("go to line")
.context("GoToLine")
.on_action(Self::cancel)
.on_action(Self::confirm)
.w_96()
+1
View File
@@ -0,0 +1 @@
+1 -399
View File
@@ -1,6 +1,6 @@
use crate::SharedString;
use anyhow::{anyhow, Context, Result};
use collections::{HashMap, HashSet};
use collections::HashMap;
use lazy_static::lazy_static;
use parking_lot::{MappedRwLockReadGuard, RwLock, RwLockReadGuard};
use serde::Deserialize;
@@ -186,401 +186,3 @@ macro_rules! actions {
actions!($($rest)*);
};
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct DispatchContext {
set: HashSet<SharedString>,
map: HashMap<SharedString, SharedString>,
}
impl<'a> TryFrom<&'a str> for DispatchContext {
type Error = anyhow::Error;
fn try_from(value: &'a str) -> Result<Self> {
Self::parse(value)
}
}
impl DispatchContext {
pub fn parse(source: &str) -> Result<Self> {
let mut context = Self::default();
let source = skip_whitespace(source);
Self::parse_expr(&source, &mut context)?;
Ok(context)
}
fn parse_expr(mut source: &str, context: &mut Self) -> Result<()> {
if source.is_empty() {
return Ok(());
}
let key = source
.chars()
.take_while(|c| is_identifier_char(*c))
.collect::<String>();
source = skip_whitespace(&source[key.len()..]);
if let Some(suffix) = source.strip_prefix('=') {
source = skip_whitespace(suffix);
let value = source
.chars()
.take_while(|c| is_identifier_char(*c))
.collect::<String>();
source = skip_whitespace(&source[value.len()..]);
context.set(key, value);
} else {
context.insert(key);
}
Self::parse_expr(source, context)
}
pub fn is_empty(&self) -> bool {
self.set.is_empty() && self.map.is_empty()
}
pub fn clear(&mut self) {
self.set.clear();
self.map.clear();
}
pub fn extend(&mut self, other: &Self) {
for v in &other.set {
self.set.insert(v.clone());
}
for (k, v) in &other.map {
self.map.insert(k.clone(), v.clone());
}
}
pub fn insert<I: Into<SharedString>>(&mut self, identifier: I) {
self.set.insert(identifier.into());
}
pub fn set<S1: Into<SharedString>, S2: Into<SharedString>>(&mut self, key: S1, value: S2) {
self.map.insert(key.into(), value.into());
}
}
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub enum DispatchContextPredicate {
Identifier(SharedString),
Equal(SharedString, SharedString),
NotEqual(SharedString, SharedString),
Child(Box<DispatchContextPredicate>, Box<DispatchContextPredicate>),
Not(Box<DispatchContextPredicate>),
And(Box<DispatchContextPredicate>, Box<DispatchContextPredicate>),
Or(Box<DispatchContextPredicate>, Box<DispatchContextPredicate>),
}
impl DispatchContextPredicate {
pub fn parse(source: &str) -> Result<Self> {
let source = skip_whitespace(source);
let (predicate, rest) = Self::parse_expr(source, 0)?;
if let Some(next) = rest.chars().next() {
Err(anyhow!("unexpected character {next:?}"))
} else {
Ok(predicate)
}
}
pub fn eval(&self, contexts: &[&DispatchContext]) -> bool {
let Some(context) = contexts.last() else {
return false;
};
match self {
Self::Identifier(name) => context.set.contains(name),
Self::Equal(left, right) => context
.map
.get(left)
.map(|value| value == right)
.unwrap_or(false),
Self::NotEqual(left, right) => context
.map
.get(left)
.map(|value| value != right)
.unwrap_or(true),
Self::Not(pred) => !pred.eval(contexts),
Self::Child(parent, child) => {
parent.eval(&contexts[..contexts.len() - 1]) && child.eval(contexts)
}
Self::And(left, right) => left.eval(contexts) && right.eval(contexts),
Self::Or(left, right) => left.eval(contexts) || right.eval(contexts),
}
}
fn parse_expr(mut source: &str, min_precedence: u32) -> anyhow::Result<(Self, &str)> {
type Op = fn(
DispatchContextPredicate,
DispatchContextPredicate,
) -> Result<DispatchContextPredicate>;
let (mut predicate, rest) = Self::parse_primary(source)?;
source = rest;
'parse: loop {
for (operator, precedence, constructor) in [
(">", PRECEDENCE_CHILD, Self::new_child as Op),
("&&", PRECEDENCE_AND, Self::new_and as Op),
("||", PRECEDENCE_OR, Self::new_or as Op),
("==", PRECEDENCE_EQ, Self::new_eq as Op),
("!=", PRECEDENCE_EQ, Self::new_neq as Op),
] {
if source.starts_with(operator) && precedence >= min_precedence {
source = skip_whitespace(&source[operator.len()..]);
let (right, rest) = Self::parse_expr(source, precedence + 1)?;
predicate = constructor(predicate, right)?;
source = rest;
continue 'parse;
}
}
break;
}
Ok((predicate, source))
}
fn parse_primary(mut source: &str) -> anyhow::Result<(Self, &str)> {
let next = source
.chars()
.next()
.ok_or_else(|| anyhow!("unexpected eof"))?;
match next {
'(' => {
source = skip_whitespace(&source[1..]);
let (predicate, rest) = Self::parse_expr(source, 0)?;
if rest.starts_with(')') {
source = skip_whitespace(&rest[1..]);
Ok((predicate, source))
} else {
Err(anyhow!("expected a ')'"))
}
}
'!' => {
let source = skip_whitespace(&source[1..]);
let (predicate, source) = Self::parse_expr(&source, PRECEDENCE_NOT)?;
Ok((DispatchContextPredicate::Not(Box::new(predicate)), source))
}
_ if is_identifier_char(next) => {
let len = source
.find(|c: char| !is_identifier_char(c))
.unwrap_or(source.len());
let (identifier, rest) = source.split_at(len);
source = skip_whitespace(rest);
Ok((
DispatchContextPredicate::Identifier(identifier.to_string().into()),
source,
))
}
_ => Err(anyhow!("unexpected character {next:?}")),
}
}
fn new_or(self, other: Self) -> Result<Self> {
Ok(Self::Or(Box::new(self), Box::new(other)))
}
fn new_and(self, other: Self) -> Result<Self> {
Ok(Self::And(Box::new(self), Box::new(other)))
}
fn new_child(self, other: Self) -> Result<Self> {
Ok(Self::Child(Box::new(self), Box::new(other)))
}
fn new_eq(self, other: Self) -> Result<Self> {
if let (Self::Identifier(left), Self::Identifier(right)) = (self, other) {
Ok(Self::Equal(left, right))
} else {
Err(anyhow!("operands must be identifiers"))
}
}
fn new_neq(self, other: Self) -> Result<Self> {
if let (Self::Identifier(left), Self::Identifier(right)) = (self, other) {
Ok(Self::NotEqual(left, right))
} else {
Err(anyhow!("operands must be identifiers"))
}
}
}
const PRECEDENCE_CHILD: u32 = 1;
const PRECEDENCE_OR: u32 = 2;
const PRECEDENCE_AND: u32 = 3;
const PRECEDENCE_EQ: u32 = 4;
const PRECEDENCE_NOT: u32 = 5;
fn is_identifier_char(c: char) -> bool {
c.is_alphanumeric() || c == '_' || c == '-'
}
fn skip_whitespace(source: &str) -> &str {
let len = source
.find(|c: char| !c.is_whitespace())
.unwrap_or(source.len());
&source[len..]
}
#[cfg(test)]
mod tests {
use super::*;
use crate as gpui;
use DispatchContextPredicate::*;
#[test]
fn test_actions_definition() {
{
actions!(A, B, C, D, E, F, G);
}
{
actions!(
A,
B,
C,
D,
E,
F,
G, // Don't wrap, test the trailing comma
);
}
}
#[test]
fn test_parse_context() {
let mut expected = DispatchContext::default();
expected.set("foo", "bar");
expected.insert("baz");
assert_eq!(DispatchContext::parse("baz foo=bar").unwrap(), expected);
assert_eq!(DispatchContext::parse("foo = bar baz").unwrap(), expected);
assert_eq!(
DispatchContext::parse(" baz foo = bar baz").unwrap(),
expected
);
assert_eq!(DispatchContext::parse(" foo = bar baz").unwrap(), expected);
}
#[test]
fn test_parse_identifiers() {
// Identifiers
assert_eq!(
DispatchContextPredicate::parse("abc12").unwrap(),
Identifier("abc12".into())
);
assert_eq!(
DispatchContextPredicate::parse("_1a").unwrap(),
Identifier("_1a".into())
);
}
#[test]
fn test_parse_negations() {
assert_eq!(
DispatchContextPredicate::parse("!abc").unwrap(),
Not(Box::new(Identifier("abc".into())))
);
assert_eq!(
DispatchContextPredicate::parse(" ! ! abc").unwrap(),
Not(Box::new(Not(Box::new(Identifier("abc".into())))))
);
}
#[test]
fn test_parse_equality_operators() {
assert_eq!(
DispatchContextPredicate::parse("a == b").unwrap(),
Equal("a".into(), "b".into())
);
assert_eq!(
DispatchContextPredicate::parse("c!=d").unwrap(),
NotEqual("c".into(), "d".into())
);
assert_eq!(
DispatchContextPredicate::parse("c == !d")
.unwrap_err()
.to_string(),
"operands must be identifiers"
);
}
#[test]
fn test_parse_boolean_operators() {
assert_eq!(
DispatchContextPredicate::parse("a || b").unwrap(),
Or(
Box::new(Identifier("a".into())),
Box::new(Identifier("b".into()))
)
);
assert_eq!(
DispatchContextPredicate::parse("a || !b && c").unwrap(),
Or(
Box::new(Identifier("a".into())),
Box::new(And(
Box::new(Not(Box::new(Identifier("b".into())))),
Box::new(Identifier("c".into()))
))
)
);
assert_eq!(
DispatchContextPredicate::parse("a && b || c&&d").unwrap(),
Or(
Box::new(And(
Box::new(Identifier("a".into())),
Box::new(Identifier("b".into()))
)),
Box::new(And(
Box::new(Identifier("c".into())),
Box::new(Identifier("d".into()))
))
)
);
assert_eq!(
DispatchContextPredicate::parse("a == b && c || d == e && f").unwrap(),
Or(
Box::new(And(
Box::new(Equal("a".into(), "b".into())),
Box::new(Identifier("c".into()))
)),
Box::new(And(
Box::new(Equal("d".into(), "e".into())),
Box::new(Identifier("f".into()))
))
)
);
assert_eq!(
DispatchContextPredicate::parse("a && b && c && d").unwrap(),
And(
Box::new(And(
Box::new(And(
Box::new(Identifier("a".into())),
Box::new(Identifier("b".into()))
)),
Box::new(Identifier("c".into())),
)),
Box::new(Identifier("d".into()))
),
);
}
#[test]
fn test_parse_parenthesized_expressions() {
assert_eq!(
DispatchContextPredicate::parse("a && (b == c || d != e)").unwrap(),
And(
Box::new(Identifier("a".into())),
Box::new(Or(
Box::new(Equal("b".into(), "c".into())),
Box::new(NotEqual("d".into(), "e".into())),
)),
),
);
assert_eq!(
DispatchContextPredicate::parse(" ( a || b ) ").unwrap(),
Or(
Box::new(Identifier("a".into())),
Box::new(Identifier("b".into())),
)
);
}
}
+11 -6
View File
@@ -641,14 +641,19 @@ impl AppContext {
// The window might change focus multiple times in an effect cycle.
// We only honor effects for the most recently focused handle.
if cx.window.focus == focused {
// if someone calls focus multiple times in one frame with the same handle
// the first apply_focus_changed_effect will have taken the last blur already
// and run the rest of this, so we can return.
let Some(last_blur) = cx.window.last_blur.take() else {
return;
};
let focused = focused
.map(|id| FocusHandle::for_id(id, &cx.window.focus_handles).unwrap());
let blurred = cx
.window
.last_blur
.take()
.unwrap()
.and_then(|id| FocusHandle::for_id(id, &cx.window.focus_handles));
let blurred =
last_blur.and_then(|id| FocusHandle::for_id(id, &cx.window.focus_handles));
let focus_changed = focused.is_some() || blurred.is_some();
let event = FocusEvent { focused, blurred };
+58 -38
View File
@@ -1,29 +1,33 @@
use std::fmt::Debug;
use crate::{
point, AnyElement, BorrowWindow, Bounds, Component, Element, ElementFocus, ElementId,
ElementInteractivity, FocusDisabled, FocusEnabled, FocusHandle, FocusListeners, Focusable,
GlobalElementId, GroupBounds, InteractiveElementState, LayoutId, Overflow, ParentElement,
Pixels, Point, SharedString, StatefulInteractive, StatefulInteractivity, StatelessInteractive,
StatelessInteractivity, Style, StyleRefinement, Styled, ViewContext, Visibility,
point, AnyElement, BorrowWindow, Bounds, Component, Element, ElementId, ElementInteractivity,
FocusHandle, FocusListeners, Focusable, FocusableKeyDispatch, GlobalElementId, GroupBounds,
InteractiveElementState, KeyContext, KeyDispatch, LayoutId, NonFocusableKeyDispatch, Overflow,
ParentElement, Pixels, Point, SharedString, StatefulInteractive, StatefulInteractivity,
StatelessInteractive, StatelessInteractivity, Style, StyleRefinement, Styled, ViewContext,
Visibility,
};
use refineable::Refineable;
use smallvec::SmallVec;
use util::ResultExt;
pub struct Div<
V: 'static,
I: ElementInteractivity<V> = StatelessInteractivity<V>,
F: ElementFocus<V> = FocusDisabled,
K: KeyDispatch<V> = NonFocusableKeyDispatch,
> {
interactivity: I,
focus: F,
key_dispatch: K,
children: SmallVec<[AnyElement<V>; 2]>,
group: Option<SharedString>,
base_style: StyleRefinement,
}
pub fn div<V: 'static>() -> Div<V, StatelessInteractivity<V>, FocusDisabled> {
pub fn div<V: 'static>() -> Div<V, StatelessInteractivity<V>, NonFocusableKeyDispatch> {
Div {
interactivity: StatelessInteractivity::default(),
focus: FocusDisabled,
key_dispatch: NonFocusableKeyDispatch::default(),
children: SmallVec::new(),
group: None,
base_style: StyleRefinement::default(),
@@ -33,12 +37,12 @@ pub fn div<V: 'static>() -> Div<V, StatelessInteractivity<V>, FocusDisabled> {
impl<V, F> Div<V, StatelessInteractivity<V>, F>
where
V: 'static,
F: ElementFocus<V>,
F: KeyDispatch<V>,
{
pub fn id(self, id: impl Into<ElementId>) -> Div<V, StatefulInteractivity<V>, F> {
Div {
interactivity: StatefulInteractivity::new(id.into(), self.interactivity),
focus: self.focus,
key_dispatch: self.key_dispatch,
children: self.children,
group: self.group,
base_style: self.base_style,
@@ -49,7 +53,7 @@ where
impl<V, I, F> Div<V, I, F>
where
I: ElementInteractivity<V>,
F: ElementFocus<V>,
F: KeyDispatch<V>,
{
pub fn group(mut self, group: impl Into<SharedString>) -> Self {
self.group = Some(group.into());
@@ -61,6 +65,18 @@ where
self
}
pub fn context<C>(mut self, context: C) -> Self
where
Self: Sized,
C: TryInto<KeyContext>,
C::Error: Debug,
{
if let Some(context) = context.try_into().log_err() {
*self.key_dispatch.key_context_mut() = context;
}
self
}
pub fn overflow_hidden(mut self) -> Self {
self.base_style.overflow.x = Some(Overflow::Hidden);
self.base_style.overflow.y = Some(Overflow::Hidden);
@@ -97,7 +113,7 @@ where
) -> Style {
let mut computed_style = Style::default();
computed_style.refine(&self.base_style);
self.focus.refine_style(&mut computed_style, cx);
self.key_dispatch.refine_style(&mut computed_style, cx);
self.interactivity.refine_style(
&mut computed_style,
bounds,
@@ -108,11 +124,11 @@ where
}
}
impl<V: 'static> Div<V, StatefulInteractivity<V>, FocusDisabled> {
pub fn focusable(self) -> Div<V, StatefulInteractivity<V>, FocusEnabled<V>> {
impl<V: 'static> Div<V, StatefulInteractivity<V>, NonFocusableKeyDispatch> {
pub fn focusable(self) -> Div<V, StatefulInteractivity<V>, FocusableKeyDispatch<V>> {
Div {
interactivity: self.interactivity,
focus: FocusEnabled::new(),
key_dispatch: FocusableKeyDispatch::new(self.key_dispatch),
children: self.children,
group: self.group,
base_style: self.base_style,
@@ -122,10 +138,10 @@ impl<V: 'static> Div<V, StatefulInteractivity<V>, FocusDisabled> {
pub fn track_focus(
self,
handle: &FocusHandle,
) -> Div<V, StatefulInteractivity<V>, FocusEnabled<V>> {
) -> Div<V, StatefulInteractivity<V>, FocusableKeyDispatch<V>> {
Div {
interactivity: self.interactivity,
focus: FocusEnabled::tracked(handle),
key_dispatch: FocusableKeyDispatch::tracked(self.key_dispatch, handle),
children: self.children,
group: self.group,
base_style: self.base_style,
@@ -149,14 +165,14 @@ impl<V: 'static> Div<V, StatefulInteractivity<V>, FocusDisabled> {
}
}
impl<V: 'static> Div<V, StatelessInteractivity<V>, FocusDisabled> {
impl<V: 'static> Div<V, StatelessInteractivity<V>, NonFocusableKeyDispatch> {
pub fn track_focus(
self,
handle: &FocusHandle,
) -> Div<V, StatefulInteractivity<V>, FocusEnabled<V>> {
) -> Div<V, StatefulInteractivity<V>, FocusableKeyDispatch<V>> {
Div {
interactivity: self.interactivity.into_stateful(handle),
focus: handle.clone().into(),
key_dispatch: FocusableKeyDispatch::tracked(self.key_dispatch, handle),
children: self.children,
group: self.group,
base_style: self.base_style,
@@ -164,25 +180,25 @@ impl<V: 'static> Div<V, StatelessInteractivity<V>, FocusDisabled> {
}
}
impl<V, I> Focusable<V> for Div<V, I, FocusEnabled<V>>
impl<V, I> Focusable<V> for Div<V, I, FocusableKeyDispatch<V>>
where
V: 'static,
I: ElementInteractivity<V>,
{
fn focus_listeners(&mut self) -> &mut FocusListeners<V> {
&mut self.focus.focus_listeners
&mut self.key_dispatch.focus_listeners
}
fn set_focus_style(&mut self, style: StyleRefinement) {
self.focus.focus_style = style;
self.key_dispatch.focus_style = style;
}
fn set_focus_in_style(&mut self, style: StyleRefinement) {
self.focus.focus_in_style = style;
self.key_dispatch.focus_in_style = style;
}
fn set_in_focus_style(&mut self, style: StyleRefinement) {
self.focus.in_focus_style = style;
self.key_dispatch.in_focus_style = style;
}
}
@@ -196,7 +212,7 @@ pub struct DivState {
impl<V, I, F> Element<V> for Div<V, I, F>
where
I: ElementInteractivity<V>,
F: ElementFocus<V>,
F: KeyDispatch<V>,
{
type ElementState = DivState;
@@ -213,14 +229,18 @@ where
cx: &mut ViewContext<V>,
) -> Self::ElementState {
let mut element_state = element_state.unwrap_or_default();
self.interactivity.initialize(cx, |cx| {
self.focus
.initialize(element_state.focus_handle.take(), cx, |focus_handle, cx| {
self.with_element_id(cx, |this, _global_id, cx| {
this.key_dispatch.initialize(
element_state.focus_handle.take(),
cx,
|focus_handle, cx| {
this.interactivity.initialize(cx);
element_state.focus_handle = focus_handle;
for child in &mut self.children {
for child in &mut this.children {
child.initialize(view_state, cx);
}
})
},
);
});
element_state
}
@@ -288,7 +308,7 @@ where
cx.with_z_index(z_index, |cx| {
cx.with_z_index(0, |cx| {
style.paint(bounds, cx);
this.focus.paint(bounds, cx);
this.key_dispatch.paint(bounds, cx);
this.interactivity.paint(
bounds,
content_size,
@@ -321,7 +341,7 @@ where
impl<V, I, F> Component<V> for Div<V, I, F>
where
I: ElementInteractivity<V>,
F: ElementFocus<V>,
F: KeyDispatch<V>,
{
fn render(self) -> AnyElement<V> {
AnyElement::new(self)
@@ -331,7 +351,7 @@ where
impl<V, I, F> ParentElement<V> for Div<V, I, F>
where
I: ElementInteractivity<V>,
F: ElementFocus<V>,
F: KeyDispatch<V>,
{
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {
&mut self.children
@@ -341,7 +361,7 @@ where
impl<V, I, F> Styled for Div<V, I, F>
where
I: ElementInteractivity<V>,
F: ElementFocus<V>,
F: KeyDispatch<V>,
{
fn style(&mut self) -> &mut StyleRefinement {
&mut self.base_style
@@ -351,7 +371,7 @@ where
impl<V, I, F> StatelessInteractive<V> for Div<V, I, F>
where
I: ElementInteractivity<V>,
F: ElementFocus<V>,
F: KeyDispatch<V>,
{
fn stateless_interactivity(&mut self) -> &mut StatelessInteractivity<V> {
self.interactivity.as_stateless_mut()
@@ -360,7 +380,7 @@ where
impl<V, F> StatefulInteractive<V> for Div<V, StatefulInteractivity<V>, F>
where
F: ElementFocus<V>,
F: KeyDispatch<V>,
{
fn stateful_interactivity(&mut self) -> &mut StatefulInteractivity<V> {
&mut self.interactivity
+13 -13
View File
@@ -1,7 +1,7 @@
use crate::{
div, AnyElement, BorrowWindow, Bounds, Component, Div, DivState, Element, ElementFocus,
ElementId, ElementInteractivity, FocusDisabled, FocusEnabled, FocusListeners, Focusable,
LayoutId, Pixels, SharedString, StatefulInteractive, StatefulInteractivity,
div, AnyElement, BorrowWindow, Bounds, Component, Div, DivState, Element, ElementId,
ElementInteractivity, FocusListeners, Focusable, FocusableKeyDispatch, KeyDispatch, LayoutId,
NonFocusableKeyDispatch, Pixels, SharedString, StatefulInteractive, StatefulInteractivity,
StatelessInteractive, StatelessInteractivity, StyleRefinement, Styled, ViewContext,
};
use futures::FutureExt;
@@ -10,14 +10,14 @@ use util::ResultExt;
pub struct Img<
V: 'static,
I: ElementInteractivity<V> = StatelessInteractivity<V>,
F: ElementFocus<V> = FocusDisabled,
F: KeyDispatch<V> = NonFocusableKeyDispatch,
> {
base: Div<V, I, F>,
uri: Option<SharedString>,
grayscale: bool,
}
pub fn img<V: 'static>() -> Img<V, StatelessInteractivity<V>, FocusDisabled> {
pub fn img<V: 'static>() -> Img<V, StatelessInteractivity<V>, NonFocusableKeyDispatch> {
Img {
base: div(),
uri: None,
@@ -29,7 +29,7 @@ impl<V, I, F> Img<V, I, F>
where
V: 'static,
I: ElementInteractivity<V>,
F: ElementFocus<V>,
F: KeyDispatch<V>,
{
pub fn uri(mut self, uri: impl Into<SharedString>) -> Self {
self.uri = Some(uri.into());
@@ -44,7 +44,7 @@ where
impl<V, F> Img<V, StatelessInteractivity<V>, F>
where
F: ElementFocus<V>,
F: KeyDispatch<V>,
{
pub fn id(self, id: impl Into<ElementId>) -> Img<V, StatefulInteractivity<V>, F> {
Img {
@@ -58,7 +58,7 @@ where
impl<V, I, F> Component<V> for Img<V, I, F>
where
I: ElementInteractivity<V>,
F: ElementFocus<V>,
F: KeyDispatch<V>,
{
fn render(self) -> AnyElement<V> {
AnyElement::new(self)
@@ -68,7 +68,7 @@ where
impl<V, I, F> Element<V> for Img<V, I, F>
where
I: ElementInteractivity<V>,
F: ElementFocus<V>,
F: KeyDispatch<V>,
{
type ElementState = DivState;
@@ -137,7 +137,7 @@ where
impl<V, I, F> Styled for Img<V, I, F>
where
I: ElementInteractivity<V>,
F: ElementFocus<V>,
F: KeyDispatch<V>,
{
fn style(&mut self) -> &mut StyleRefinement {
self.base.style()
@@ -147,7 +147,7 @@ where
impl<V, I, F> StatelessInteractive<V> for Img<V, I, F>
where
I: ElementInteractivity<V>,
F: ElementFocus<V>,
F: KeyDispatch<V>,
{
fn stateless_interactivity(&mut self) -> &mut StatelessInteractivity<V> {
self.base.stateless_interactivity()
@@ -156,14 +156,14 @@ where
impl<V, F> StatefulInteractive<V> for Img<V, StatefulInteractivity<V>, F>
where
F: ElementFocus<V>,
F: KeyDispatch<V>,
{
fn stateful_interactivity(&mut self) -> &mut StatefulInteractivity<V> {
self.base.stateful_interactivity()
}
}
impl<V, I> Focusable<V> for Img<V, I, FocusEnabled<V>>
impl<V, I> Focusable<V> for Img<V, I, FocusableKeyDispatch<V>>
where
V: 'static,
I: ElementInteractivity<V>,
+14 -14
View File
@@ -1,21 +1,21 @@
use crate::{
div, AnyElement, Bounds, Component, Div, DivState, Element, ElementFocus, ElementId,
ElementInteractivity, FocusDisabled, FocusEnabled, FocusListeners, Focusable, LayoutId, Pixels,
SharedString, StatefulInteractive, StatefulInteractivity, StatelessInteractive,
StatelessInteractivity, StyleRefinement, Styled, ViewContext,
div, AnyElement, Bounds, Component, Div, DivState, Element, ElementId, ElementInteractivity,
FocusListeners, Focusable, FocusableKeyDispatch, KeyDispatch, LayoutId,
NonFocusableKeyDispatch, Pixels, SharedString, StatefulInteractive, StatefulInteractivity,
StatelessInteractive, StatelessInteractivity, StyleRefinement, Styled, ViewContext,
};
use util::ResultExt;
pub struct Svg<
V: 'static,
I: ElementInteractivity<V> = StatelessInteractivity<V>,
F: ElementFocus<V> = FocusDisabled,
F: KeyDispatch<V> = NonFocusableKeyDispatch,
> {
base: Div<V, I, F>,
path: Option<SharedString>,
}
pub fn svg<V: 'static>() -> Svg<V, StatelessInteractivity<V>, FocusDisabled> {
pub fn svg<V: 'static>() -> Svg<V, StatelessInteractivity<V>, NonFocusableKeyDispatch> {
Svg {
base: div(),
path: None,
@@ -25,7 +25,7 @@ pub fn svg<V: 'static>() -> Svg<V, StatelessInteractivity<V>, FocusDisabled> {
impl<V, I, F> Svg<V, I, F>
where
I: ElementInteractivity<V>,
F: ElementFocus<V>,
F: KeyDispatch<V>,
{
pub fn path(mut self, path: impl Into<SharedString>) -> Self {
self.path = Some(path.into());
@@ -35,7 +35,7 @@ where
impl<V, F> Svg<V, StatelessInteractivity<V>, F>
where
F: ElementFocus<V>,
F: KeyDispatch<V>,
{
pub fn id(self, id: impl Into<ElementId>) -> Svg<V, StatefulInteractivity<V>, F> {
Svg {
@@ -48,7 +48,7 @@ where
impl<V, I, F> Component<V> for Svg<V, I, F>
where
I: ElementInteractivity<V>,
F: ElementFocus<V>,
F: KeyDispatch<V>,
{
fn render(self) -> AnyElement<V> {
AnyElement::new(self)
@@ -58,7 +58,7 @@ where
impl<V, I, F> Element<V> for Svg<V, I, F>
where
I: ElementInteractivity<V>,
F: ElementFocus<V>,
F: KeyDispatch<V>,
{
type ElementState = DivState;
@@ -108,7 +108,7 @@ where
impl<V, I, F> Styled for Svg<V, I, F>
where
I: ElementInteractivity<V>,
F: ElementFocus<V>,
F: KeyDispatch<V>,
{
fn style(&mut self) -> &mut StyleRefinement {
self.base.style()
@@ -118,7 +118,7 @@ where
impl<V, I, F> StatelessInteractive<V> for Svg<V, I, F>
where
I: ElementInteractivity<V>,
F: ElementFocus<V>,
F: KeyDispatch<V>,
{
fn stateless_interactivity(&mut self) -> &mut StatelessInteractivity<V> {
self.base.stateless_interactivity()
@@ -128,14 +128,14 @@ where
impl<V, F> StatefulInteractive<V> for Svg<V, StatefulInteractivity<V>, F>
where
V: 'static,
F: ElementFocus<V>,
F: KeyDispatch<V>,
{
fn stateful_interactivity(&mut self) -> &mut StatefulInteractivity<V> {
self.base.stateful_interactivity()
}
}
impl<V: 'static, I> Focusable<V> for Svg<V, I, FocusEnabled<V>>
impl<V: 'static, I> Focusable<V> for Svg<V, I, FocusableKeyDispatch<V>>
where
I: ElementInteractivity<V>,
{
-252
View File
@@ -1,252 +0,0 @@
use crate::{
Bounds, DispatchPhase, Element, FocusEvent, FocusHandle, MouseDownEvent, Pixels, Style,
StyleRefinement, ViewContext, WindowContext,
};
use refineable::Refineable;
use smallvec::SmallVec;
pub type FocusListeners<V> = SmallVec<[FocusListener<V>; 2]>;
pub type FocusListener<V> =
Box<dyn Fn(&mut V, &FocusHandle, &FocusEvent, &mut ViewContext<V>) + 'static>;
pub trait Focusable<V: 'static>: Element<V> {
fn focus_listeners(&mut self) -> &mut FocusListeners<V>;
fn set_focus_style(&mut self, style: StyleRefinement);
fn set_focus_in_style(&mut self, style: StyleRefinement);
fn set_in_focus_style(&mut self, style: StyleRefinement);
fn focus(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
where
Self: Sized,
{
self.set_focus_style(f(StyleRefinement::default()));
self
}
fn focus_in(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
where
Self: Sized,
{
self.set_focus_in_style(f(StyleRefinement::default()));
self
}
fn in_focus(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
where
Self: Sized,
{
self.set_in_focus_style(f(StyleRefinement::default()));
self
}
fn on_focus(
mut self,
listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext<V>) + 'static,
) -> Self
where
Self: Sized,
{
self.focus_listeners()
.push(Box::new(move |view, focus_handle, event, cx| {
if event.focused.as_ref() == Some(focus_handle) {
listener(view, event, cx)
}
}));
self
}
fn on_blur(
mut self,
listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext<V>) + 'static,
) -> Self
where
Self: Sized,
{
self.focus_listeners()
.push(Box::new(move |view, focus_handle, event, cx| {
if event.blurred.as_ref() == Some(focus_handle) {
listener(view, event, cx)
}
}));
self
}
fn on_focus_in(
mut self,
listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext<V>) + 'static,
) -> Self
where
Self: Sized,
{
self.focus_listeners()
.push(Box::new(move |view, focus_handle, event, cx| {
let descendant_blurred = event
.blurred
.as_ref()
.map_or(false, |blurred| focus_handle.contains(blurred, cx));
let descendant_focused = event
.focused
.as_ref()
.map_or(false, |focused| focus_handle.contains(focused, cx));
if !descendant_blurred && descendant_focused {
listener(view, event, cx)
}
}));
self
}
fn on_focus_out(
mut self,
listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext<V>) + 'static,
) -> Self
where
Self: Sized,
{
self.focus_listeners()
.push(Box::new(move |view, focus_handle, event, cx| {
let descendant_blurred = event
.blurred
.as_ref()
.map_or(false, |blurred| focus_handle.contains(blurred, cx));
let descendant_focused = event
.focused
.as_ref()
.map_or(false, |focused| focus_handle.contains(focused, cx));
if descendant_blurred && !descendant_focused {
listener(view, event, cx)
}
}));
self
}
}
pub trait ElementFocus<V: 'static>: 'static {
fn as_focusable(&self) -> Option<&FocusEnabled<V>>;
fn as_focusable_mut(&mut self) -> Option<&mut FocusEnabled<V>>;
fn initialize<R>(
&mut self,
focus_handle: Option<FocusHandle>,
cx: &mut ViewContext<V>,
f: impl FnOnce(Option<FocusHandle>, &mut ViewContext<V>) -> R,
) -> R {
if let Some(focusable) = self.as_focusable_mut() {
let focus_handle = focusable
.focus_handle
.get_or_insert_with(|| focus_handle.unwrap_or_else(|| cx.focus_handle()))
.clone();
for listener in focusable.focus_listeners.drain(..) {
let focus_handle = focus_handle.clone();
cx.on_focus_changed(move |view, event, cx| {
listener(view, &focus_handle, event, cx)
});
}
cx.with_focus(focus_handle.clone(), |cx| f(Some(focus_handle), cx))
} else {
f(None, cx)
}
}
fn refine_style(&self, style: &mut Style, cx: &WindowContext) {
if let Some(focusable) = self.as_focusable() {
let focus_handle = focusable
.focus_handle
.as_ref()
.expect("must call initialize before refine_style");
if focus_handle.contains_focused(cx) {
style.refine(&focusable.focus_in_style);
}
if focus_handle.within_focused(cx) {
style.refine(&focusable.in_focus_style);
}
if focus_handle.is_focused(cx) {
style.refine(&focusable.focus_style);
}
}
}
fn paint(&self, bounds: Bounds<Pixels>, cx: &mut WindowContext) {
if let Some(focusable) = self.as_focusable() {
let focus_handle = focusable
.focus_handle
.clone()
.expect("must call initialize before paint");
cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| {
if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
if !cx.default_prevented() {
cx.focus(&focus_handle);
cx.prevent_default();
}
}
})
}
}
}
pub struct FocusEnabled<V> {
pub focus_handle: Option<FocusHandle>,
pub focus_listeners: FocusListeners<V>,
pub focus_style: StyleRefinement,
pub focus_in_style: StyleRefinement,
pub in_focus_style: StyleRefinement,
}
impl<V> FocusEnabled<V> {
pub fn new() -> Self {
Self {
focus_handle: None,
focus_listeners: FocusListeners::default(),
focus_style: StyleRefinement::default(),
focus_in_style: StyleRefinement::default(),
in_focus_style: StyleRefinement::default(),
}
}
pub fn tracked(handle: &FocusHandle) -> Self {
Self {
focus_handle: Some(handle.clone()),
focus_listeners: FocusListeners::default(),
focus_style: StyleRefinement::default(),
focus_in_style: StyleRefinement::default(),
in_focus_style: StyleRefinement::default(),
}
}
}
impl<V: 'static> ElementFocus<V> for FocusEnabled<V> {
fn as_focusable(&self) -> Option<&FocusEnabled<V>> {
Some(self)
}
fn as_focusable_mut(&mut self) -> Option<&mut FocusEnabled<V>> {
Some(self)
}
}
impl<V> From<FocusHandle> for FocusEnabled<V> {
fn from(value: FocusHandle) -> Self {
Self {
focus_handle: Some(value),
focus_listeners: FocusListeners::default(),
focus_style: StyleRefinement::default(),
focus_in_style: StyleRefinement::default(),
in_focus_style: StyleRefinement::default(),
}
}
}
pub struct FocusDisabled;
impl<V: 'static> ElementFocus<V> for FocusDisabled {
fn as_focusable(&self) -> Option<&FocusEnabled<V>> {
None
}
fn as_focusable_mut(&mut self) -> Option<&mut FocusEnabled<V>> {
None
}
}
+3 -2
View File
@@ -6,13 +6,14 @@ mod color;
mod element;
mod elements;
mod executor;
mod focusable;
mod geometry;
mod image_cache;
mod input;
mod interactive;
mod key_dispatch;
mod keymap;
mod platform;
pub mod prelude;
mod scene;
mod style;
mod styled;
@@ -41,12 +42,12 @@ pub use ctor::ctor;
pub use element::*;
pub use elements::*;
pub use executor::*;
pub use focusable::*;
pub use geometry::*;
pub use gpui2_macros::*;
pub use image_cache::*;
pub use input::*;
pub use interactive::*;
pub use key_dispatch::*;
pub use keymap::*;
pub use platform::*;
use private::Sealed;
+59 -93
View File
@@ -1,8 +1,8 @@
use crate::{
div, point, px, Action, AnyDrag, AnyTooltip, AnyView, AppContext, BorrowWindow, Bounds,
Component, DispatchContext, DispatchPhase, Div, Element, ElementId, FocusHandle, KeyMatch,
Keystroke, Modifiers, Overflow, Pixels, Point, Render, SharedString, Size, Style,
StyleRefinement, Task, View, ViewContext,
div, point, px, Action, AnyDrag, AnyTooltip, AnyView, AppContext, Bounds, Component,
DispatchPhase, Div, Element, ElementId, FocusHandle, KeyContext, Keystroke, Modifiers,
Overflow, Pixels, Point, Render, SharedString, Size, Style, StyleRefinement, Task, View,
ViewContext,
};
use collections::HashMap;
use derive_more::{Deref, DerefMut};
@@ -164,55 +164,41 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
self
}
fn context<C>(mut self, context: C) -> Self
where
Self: Sized,
C: TryInto<DispatchContext>,
C::Error: Debug,
{
self.stateless_interactivity().dispatch_context =
context.try_into().expect("invalid dispatch context");
self
}
/// Capture the given action, fires during the capture phase
fn capture_action<A: 'static>(
fn capture_action<A: Action>(
mut self,
listener: impl Fn(&mut V, &A, &mut ViewContext<V>) + 'static,
) -> Self
where
Self: Sized,
{
self.stateless_interactivity().key_listeners.push((
self.stateless_interactivity().action_listeners.push((
TypeId::of::<A>(),
Box::new(move |view, action, _dipatch_context, phase, cx| {
Box::new(move |view, action, phase, cx| {
let action = action.downcast_ref().unwrap();
if phase == DispatchPhase::Capture {
listener(view, action, cx)
}
None
}),
));
self
}
/// Add a listener for the given action, fires during the bubble event phase
fn on_action<A: 'static>(
fn on_action<A: Action>(
mut self,
listener: impl Fn(&mut V, &A, &mut ViewContext<V>) + 'static,
) -> Self
where
Self: Sized,
{
self.stateless_interactivity().key_listeners.push((
self.stateless_interactivity().action_listeners.push((
TypeId::of::<A>(),
Box::new(move |view, action, _dispatch_context, phase, cx| {
Box::new(move |view, action, phase, cx| {
let action = action.downcast_ref().unwrap();
if phase == DispatchPhase::Bubble {
listener(view, action, cx)
}
None
}),
));
self
@@ -225,14 +211,11 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
where
Self: Sized,
{
self.stateless_interactivity().key_listeners.push((
TypeId::of::<KeyDownEvent>(),
Box::new(move |view, event, _, phase, cx| {
let event = event.downcast_ref().unwrap();
listener(view, event, phase, cx);
None
}),
));
self.stateless_interactivity()
.key_down_listeners
.push(Box::new(move |view, event, phase, cx| {
listener(view, event, phase, cx)
}));
self
}
@@ -243,14 +226,11 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
where
Self: Sized,
{
self.stateless_interactivity().key_listeners.push((
TypeId::of::<KeyUpEvent>(),
Box::new(move |view, event, _, phase, cx| {
let event = event.downcast_ref().unwrap();
listener(view, event, phase, cx);
None
}),
));
self.stateless_interactivity()
.key_up_listeners
.push(Box::new(move |view, event, phase, cx| {
listener(view, event, phase, cx)
}));
self
}
@@ -396,43 +376,6 @@ pub trait ElementInteractivity<V: 'static>: 'static {
fn as_stateful(&self) -> Option<&StatefulInteractivity<V>>;
fn as_stateful_mut(&mut self) -> Option<&mut StatefulInteractivity<V>>;
fn initialize<R>(
&mut self,
cx: &mut ViewContext<V>,
f: impl FnOnce(&mut ViewContext<V>) -> R,
) -> R {
if let Some(stateful) = self.as_stateful_mut() {
cx.with_element_id(stateful.id.clone(), |global_id, cx| {
// In addition to any key down/up listeners registered directly on the element,
// we also add a key listener to match actions from the keymap.
stateful.key_listeners.push((
TypeId::of::<KeyDownEvent>(),
Box::new(move |_, key_down, context, phase, cx| {
if phase == DispatchPhase::Bubble {
let key_down = key_down.downcast_ref::<KeyDownEvent>().unwrap();
if let KeyMatch::Some(action) =
cx.match_keystroke(&global_id, &key_down.keystroke, context)
{
return Some(action);
}
}
None
}),
));
cx.with_key_dispatch_context(stateful.dispatch_context.clone(), |cx| {
cx.with_key_listeners(mem::take(&mut stateful.key_listeners), f)
})
})
} else {
let stateless = self.as_stateless_mut();
cx.with_key_dispatch_context(stateless.dispatch_context.clone(), |cx| {
cx.with_key_listeners(mem::take(&mut stateless.key_listeners), f)
})
}
}
fn refine_style(
&self,
style: &mut Style,
@@ -487,6 +430,26 @@ pub trait ElementInteractivity<V: 'static>: 'static {
}
}
fn initialize(&mut self, cx: &mut ViewContext<V>) {
let stateless = self.as_stateless_mut();
for listener in stateless.key_down_listeners.drain(..) {
cx.on_key_event(move |state, event: &KeyDownEvent, phase, cx| {
listener(state, event, phase, cx);
})
}
for listener in stateless.key_up_listeners.drain(..) {
cx.on_key_event(move |state, event: &KeyUpEvent, phase, cx| {
listener(state, event, phase, cx);
})
}
for (action_type, listener) in stateless.action_listeners.drain(..) {
cx.on_action(action_type, listener)
}
}
fn paint(
&mut self,
bounds: Bounds<Pixels>,
@@ -808,12 +771,14 @@ impl<V: 'static> ElementInteractivity<V> for StatefulInteractivity<V> {
type DropListener<V> = dyn Fn(&mut V, AnyView, &mut ViewContext<V>) + 'static;
pub struct StatelessInteractivity<V> {
pub dispatch_context: DispatchContext,
pub dispatch_context: KeyContext,
pub mouse_down_listeners: SmallVec<[MouseDownListener<V>; 2]>,
pub mouse_up_listeners: SmallVec<[MouseUpListener<V>; 2]>,
pub mouse_move_listeners: SmallVec<[MouseMoveListener<V>; 2]>,
pub scroll_wheel_listeners: SmallVec<[ScrollWheelListener<V>; 2]>,
pub key_listeners: SmallVec<[(TypeId, KeyListener<V>); 32]>,
pub key_down_listeners: SmallVec<[KeyDownListener<V>; 2]>,
pub key_up_listeners: SmallVec<[KeyUpListener<V>; 2]>,
pub action_listeners: SmallVec<[(TypeId, ActionListener<V>); 8]>,
pub hover_style: StyleRefinement,
pub group_hover_style: Option<GroupStyle>,
drag_over_styles: SmallVec<[(TypeId, StyleRefinement); 2]>,
@@ -910,12 +875,14 @@ impl InteractiveElementState {
impl<V> Default for StatelessInteractivity<V> {
fn default() -> Self {
Self {
dispatch_context: DispatchContext::default(),
dispatch_context: KeyContext::default(),
mouse_down_listeners: SmallVec::new(),
mouse_up_listeners: SmallVec::new(),
mouse_move_listeners: SmallVec::new(),
scroll_wheel_listeners: SmallVec::new(),
key_listeners: SmallVec::new(),
key_down_listeners: SmallVec::new(),
key_up_listeners: SmallVec::new(),
action_listeners: SmallVec::new(),
hover_style: StyleRefinement::default(),
group_hover_style: None,
drag_over_styles: SmallVec::new(),
@@ -1250,16 +1217,14 @@ pub(crate) type HoverListener<V> = Box<dyn Fn(&mut V, bool, &mut ViewContext<V>)
pub(crate) type TooltipBuilder<V> = Arc<dyn Fn(&mut V, &mut ViewContext<V>) -> AnyView + 'static>;
pub type KeyListener<V> = Box<
dyn Fn(
&mut V,
&dyn Any,
&[&DispatchContext],
DispatchPhase,
&mut ViewContext<V>,
) -> Option<Box<dyn Action>>
+ 'static,
>;
pub(crate) type KeyDownListener<V> =
Box<dyn Fn(&mut V, &KeyDownEvent, DispatchPhase, &mut ViewContext<V>) + 'static>;
pub(crate) type KeyUpListener<V> =
Box<dyn Fn(&mut V, &KeyUpEvent, DispatchPhase, &mut ViewContext<V>) + 'static>;
pub type ActionListener<V> =
Box<dyn Fn(&mut V, &dyn Any, DispatchPhase, &mut ViewContext<V>) + 'static>;
#[cfg(test)]
mod test {
@@ -1282,9 +1247,10 @@ mod test {
fn render(&mut self, _: &mut gpui::ViewContext<Self>) -> Self::Element {
div().id("testview").child(
div()
.context("test")
.track_focus(&self.focus_handle)
.on_key_down(|this: &mut TestView, _, _, _| this.saw_key_down = true)
.on_action(|this: &mut TestView, _: &TestAction, _| this.saw_action = true)
.track_focus(&self.focus_handle),
.on_action(|this: &mut TestView, _: &TestAction, _| this.saw_action = true),
)
}
}
+465
View File
@@ -0,0 +1,465 @@
use crate::{
build_action_from_type, Action, Bounds, DispatchPhase, Element, FocusEvent, FocusHandle,
FocusId, KeyBinding, KeyContext, KeyMatch, Keymap, Keystroke, KeystrokeMatcher, MouseDownEvent,
Pixels, Style, StyleRefinement, ViewContext, WindowContext,
};
use collections::HashMap;
use parking_lot::Mutex;
use refineable::Refineable;
use smallvec::SmallVec;
use std::{
any::{Any, TypeId},
rc::Rc,
sync::Arc,
};
use util::ResultExt;
pub type FocusListeners<V> = SmallVec<[FocusListener<V>; 2]>;
pub type FocusListener<V> =
Box<dyn Fn(&mut V, &FocusHandle, &FocusEvent, &mut ViewContext<V>) + 'static>;
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
pub struct DispatchNodeId(usize);
pub(crate) struct DispatchTree {
node_stack: Vec<DispatchNodeId>,
context_stack: Vec<KeyContext>,
nodes: Vec<DispatchNode>,
focusable_node_ids: HashMap<FocusId, DispatchNodeId>,
keystroke_matchers: HashMap<SmallVec<[KeyContext; 4]>, KeystrokeMatcher>,
keymap: Arc<Mutex<Keymap>>,
}
#[derive(Default)]
pub(crate) struct DispatchNode {
pub key_listeners: SmallVec<[KeyListener; 2]>,
pub action_listeners: SmallVec<[DispatchActionListener; 16]>,
pub context: KeyContext,
parent: Option<DispatchNodeId>,
}
type KeyListener = Rc<dyn Fn(&dyn Any, DispatchPhase, &mut WindowContext)>;
#[derive(Clone)]
pub(crate) struct DispatchActionListener {
pub(crate) action_type: TypeId,
pub(crate) listener: Rc<dyn Fn(&dyn Any, DispatchPhase, &mut WindowContext)>,
}
impl DispatchTree {
pub fn new(keymap: Arc<Mutex<Keymap>>) -> Self {
Self {
node_stack: Vec::new(),
context_stack: Vec::new(),
nodes: Vec::new(),
focusable_node_ids: HashMap::default(),
keystroke_matchers: HashMap::default(),
keymap,
}
}
pub fn clear(&mut self) {
self.node_stack.clear();
self.nodes.clear();
self.context_stack.clear();
self.focusable_node_ids.clear();
self.keystroke_matchers.clear();
}
pub fn push_node(&mut self, context: KeyContext, old_dispatcher: &mut Self) {
let parent = self.node_stack.last().copied();
let node_id = DispatchNodeId(self.nodes.len());
self.nodes.push(DispatchNode {
parent,
..Default::default()
});
self.node_stack.push(node_id);
if !context.is_empty() {
self.active_node().context = context.clone();
self.context_stack.push(context);
if let Some((context_stack, matcher)) = old_dispatcher
.keystroke_matchers
.remove_entry(self.context_stack.as_slice())
{
self.keystroke_matchers.insert(context_stack, matcher);
}
}
}
pub fn pop_node(&mut self) {
let node_id = self.node_stack.pop().unwrap();
if !self.nodes[node_id.0].context.is_empty() {
self.context_stack.pop();
}
}
pub fn on_key_event(&mut self, listener: KeyListener) {
self.active_node().key_listeners.push(listener);
}
pub fn on_action(
&mut self,
action_type: TypeId,
listener: Rc<dyn Fn(&dyn Any, DispatchPhase, &mut WindowContext)>,
) {
self.active_node()
.action_listeners
.push(DispatchActionListener {
action_type,
listener,
});
}
pub fn make_focusable(&mut self, focus_id: FocusId) {
self.focusable_node_ids
.insert(focus_id, self.active_node_id());
}
pub fn focus_contains(&self, parent: FocusId, child: FocusId) -> bool {
if parent == child {
return true;
}
if let Some(parent_node_id) = self.focusable_node_ids.get(&parent) {
let mut current_node_id = self.focusable_node_ids.get(&child).copied();
while let Some(node_id) = current_node_id {
if node_id == *parent_node_id {
return true;
}
current_node_id = self.nodes[node_id.0].parent;
}
}
false
}
pub fn available_actions(&self, target: FocusId) -> Vec<Box<dyn Action>> {
let mut actions = Vec::new();
if let Some(node) = self.focusable_node_ids.get(&target) {
for node_id in self.dispatch_path(*node) {
let node = &self.nodes[node_id.0];
for DispatchActionListener { action_type, .. } in &node.action_listeners {
actions.extend(build_action_from_type(action_type).log_err());
}
}
}
actions
}
pub fn bindings_for_action(&self, action: &dyn Action) -> Vec<KeyBinding> {
self.keymap
.lock()
.bindings_for_action(action.type_id())
.filter(|candidate| candidate.action.partial_eq(action))
.cloned()
.collect()
}
pub fn dispatch_key(
&mut self,
keystroke: &Keystroke,
context: &[KeyContext],
) -> Option<Box<dyn Action>> {
if !self.keystroke_matchers.contains_key(context) {
let keystroke_contexts = context.iter().cloned().collect();
self.keystroke_matchers.insert(
keystroke_contexts,
KeystrokeMatcher::new(self.keymap.clone()),
);
}
let keystroke_matcher = self.keystroke_matchers.get_mut(context).unwrap();
if let KeyMatch::Some(action) = keystroke_matcher.match_keystroke(keystroke, context) {
// Clear all pending keystrokes when an action has been found.
for keystroke_matcher in self.keystroke_matchers.values_mut() {
keystroke_matcher.clear_pending();
}
Some(action)
} else {
None
}
}
pub fn dispatch_path(&self, target: DispatchNodeId) -> SmallVec<[DispatchNodeId; 32]> {
let mut dispatch_path: SmallVec<[DispatchNodeId; 32]> = SmallVec::new();
let mut current_node_id = Some(target);
while let Some(node_id) = current_node_id {
dispatch_path.push(node_id);
current_node_id = self.nodes[node_id.0].parent;
}
dispatch_path.reverse(); // Reverse the path so it goes from the root to the focused node.
dispatch_path
}
pub fn node(&self, node_id: DispatchNodeId) -> &DispatchNode {
&self.nodes[node_id.0]
}
fn active_node(&mut self) -> &mut DispatchNode {
let active_node_id = self.active_node_id();
&mut self.nodes[active_node_id.0]
}
pub fn focusable_node_id(&self, target: FocusId) -> Option<DispatchNodeId> {
self.focusable_node_ids.get(&target).copied()
}
fn active_node_id(&self) -> DispatchNodeId {
*self.node_stack.last().unwrap()
}
}
pub trait KeyDispatch<V: 'static>: 'static {
fn as_focusable(&self) -> Option<&FocusableKeyDispatch<V>>;
fn as_focusable_mut(&mut self) -> Option<&mut FocusableKeyDispatch<V>>;
fn key_context(&self) -> &KeyContext;
fn key_context_mut(&mut self) -> &mut KeyContext;
fn initialize<R>(
&mut self,
focus_handle: Option<FocusHandle>,
cx: &mut ViewContext<V>,
f: impl FnOnce(Option<FocusHandle>, &mut ViewContext<V>) -> R,
) -> R {
let focus_handle = if let Some(focusable) = self.as_focusable_mut() {
let focus_handle = focusable
.focus_handle
.get_or_insert_with(|| focus_handle.unwrap_or_else(|| cx.focus_handle()))
.clone();
for listener in focusable.focus_listeners.drain(..) {
let focus_handle = focus_handle.clone();
cx.on_focus_changed(move |view, event, cx| {
listener(view, &focus_handle, event, cx)
});
}
Some(focus_handle)
} else {
None
};
cx.with_key_dispatch(self.key_context().clone(), focus_handle, f)
}
fn refine_style(&self, style: &mut Style, cx: &WindowContext) {
if let Some(focusable) = self.as_focusable() {
let focus_handle = focusable
.focus_handle
.as_ref()
.expect("must call initialize before refine_style");
if focus_handle.contains_focused(cx) {
style.refine(&focusable.focus_in_style);
}
if focus_handle.within_focused(cx) {
style.refine(&focusable.in_focus_style);
}
if focus_handle.is_focused(cx) {
style.refine(&focusable.focus_style);
}
}
}
fn paint(&self, bounds: Bounds<Pixels>, cx: &mut WindowContext) {
if let Some(focusable) = self.as_focusable() {
let focus_handle = focusable
.focus_handle
.clone()
.expect("must call initialize before paint");
cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| {
if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
if !cx.default_prevented() {
cx.focus(&focus_handle);
cx.prevent_default();
}
}
})
}
}
}
pub struct FocusableKeyDispatch<V> {
pub non_focusable: NonFocusableKeyDispatch,
pub focus_handle: Option<FocusHandle>,
pub focus_listeners: FocusListeners<V>,
pub focus_style: StyleRefinement,
pub focus_in_style: StyleRefinement,
pub in_focus_style: StyleRefinement,
}
impl<V> FocusableKeyDispatch<V> {
pub fn new(non_focusable: NonFocusableKeyDispatch) -> Self {
Self {
non_focusable,
focus_handle: None,
focus_listeners: FocusListeners::default(),
focus_style: StyleRefinement::default(),
focus_in_style: StyleRefinement::default(),
in_focus_style: StyleRefinement::default(),
}
}
pub fn tracked(non_focusable: NonFocusableKeyDispatch, handle: &FocusHandle) -> Self {
Self {
non_focusable,
focus_handle: Some(handle.clone()),
focus_listeners: FocusListeners::default(),
focus_style: StyleRefinement::default(),
focus_in_style: StyleRefinement::default(),
in_focus_style: StyleRefinement::default(),
}
}
}
impl<V: 'static> KeyDispatch<V> for FocusableKeyDispatch<V> {
fn as_focusable(&self) -> Option<&FocusableKeyDispatch<V>> {
Some(self)
}
fn as_focusable_mut(&mut self) -> Option<&mut FocusableKeyDispatch<V>> {
Some(self)
}
fn key_context(&self) -> &KeyContext {
&self.non_focusable.key_context
}
fn key_context_mut(&mut self) -> &mut KeyContext {
&mut self.non_focusable.key_context
}
}
#[derive(Default)]
pub struct NonFocusableKeyDispatch {
pub(crate) key_context: KeyContext,
}
impl<V: 'static> KeyDispatch<V> for NonFocusableKeyDispatch {
fn as_focusable(&self) -> Option<&FocusableKeyDispatch<V>> {
None
}
fn as_focusable_mut(&mut self) -> Option<&mut FocusableKeyDispatch<V>> {
None
}
fn key_context(&self) -> &KeyContext {
&self.key_context
}
fn key_context_mut(&mut self) -> &mut KeyContext {
&mut self.key_context
}
}
pub trait Focusable<V: 'static>: Element<V> {
fn focus_listeners(&mut self) -> &mut FocusListeners<V>;
fn set_focus_style(&mut self, style: StyleRefinement);
fn set_focus_in_style(&mut self, style: StyleRefinement);
fn set_in_focus_style(&mut self, style: StyleRefinement);
fn focus(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
where
Self: Sized,
{
self.set_focus_style(f(StyleRefinement::default()));
self
}
fn focus_in(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
where
Self: Sized,
{
self.set_focus_in_style(f(StyleRefinement::default()));
self
}
fn in_focus(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
where
Self: Sized,
{
self.set_in_focus_style(f(StyleRefinement::default()));
self
}
fn on_focus(
mut self,
listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext<V>) + 'static,
) -> Self
where
Self: Sized,
{
self.focus_listeners()
.push(Box::new(move |view, focus_handle, event, cx| {
if event.focused.as_ref() == Some(focus_handle) {
listener(view, event, cx)
}
}));
self
}
fn on_blur(
mut self,
listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext<V>) + 'static,
) -> Self
where
Self: Sized,
{
self.focus_listeners()
.push(Box::new(move |view, focus_handle, event, cx| {
if event.blurred.as_ref() == Some(focus_handle) {
listener(view, event, cx)
}
}));
self
}
fn on_focus_in(
mut self,
listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext<V>) + 'static,
) -> Self
where
Self: Sized,
{
self.focus_listeners()
.push(Box::new(move |view, focus_handle, event, cx| {
let descendant_blurred = event
.blurred
.as_ref()
.map_or(false, |blurred| focus_handle.contains(blurred, cx));
let descendant_focused = event
.focused
.as_ref()
.map_or(false, |focused| focus_handle.contains(focused, cx));
if !descendant_blurred && descendant_focused {
listener(view, event, cx)
}
}));
self
}
fn on_focus_out(
mut self,
listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext<V>) + 'static,
) -> Self
where
Self: Sized,
{
self.focus_listeners()
.push(Box::new(move |view, focus_handle, event, cx| {
let descendant_blurred = event
.blurred
.as_ref()
.map_or(false, |blurred| focus_handle.contains(blurred, cx));
let descendant_focused = event
.focused
.as_ref()
.map_or(false, |focused| focus_handle.contains(focused, cx));
if descendant_blurred && !descendant_focused {
listener(view, event, cx)
}
}));
self
}
}
+18 -8
View File
@@ -1,11 +1,21 @@
use crate::{Action, DispatchContext, DispatchContextPredicate, KeyMatch, Keystroke};
use crate::{Action, KeyBindingContextPredicate, KeyContext, KeyMatch, Keystroke};
use anyhow::Result;
use smallvec::SmallVec;
pub struct KeyBinding {
action: Box<dyn Action>,
pub(super) keystrokes: SmallVec<[Keystroke; 2]>,
pub(super) context_predicate: Option<DispatchContextPredicate>,
pub(crate) action: Box<dyn Action>,
pub(crate) keystrokes: SmallVec<[Keystroke; 2]>,
pub(crate) context_predicate: Option<KeyBindingContextPredicate>,
}
impl Clone for KeyBinding {
fn clone(&self) -> Self {
KeyBinding {
action: self.action.boxed_clone(),
keystrokes: self.keystrokes.clone(),
context_predicate: self.context_predicate.clone(),
}
}
}
impl KeyBinding {
@@ -15,7 +25,7 @@ impl KeyBinding {
pub fn load(keystrokes: &str, action: Box<dyn Action>, context: Option<&str>) -> Result<Self> {
let context = if let Some(context) = context {
Some(DispatchContextPredicate::parse(context)?)
Some(KeyBindingContextPredicate::parse(context)?)
} else {
None
};
@@ -32,7 +42,7 @@ impl KeyBinding {
})
}
pub fn matches_context(&self, contexts: &[&DispatchContext]) -> bool {
pub fn matches_context(&self, contexts: &[KeyContext]) -> bool {
self.context_predicate
.as_ref()
.map(|predicate| predicate.eval(contexts))
@@ -42,7 +52,7 @@ impl KeyBinding {
pub fn match_keystrokes(
&self,
pending_keystrokes: &[Keystroke],
contexts: &[&DispatchContext],
contexts: &[KeyContext],
) -> KeyMatch {
if self.keystrokes.as_ref().starts_with(&pending_keystrokes)
&& self.matches_context(contexts)
@@ -61,7 +71,7 @@ impl KeyBinding {
pub fn keystrokes_for_action(
&self,
action: &dyn Action,
contexts: &[&DispatchContext],
contexts: &[KeyContext],
) -> Option<SmallVec<[Keystroke; 2]>> {
if self.action.partial_eq(action) && self.matches_context(contexts) {
Some(self.keystrokes.clone())
+449
View File
@@ -0,0 +1,449 @@
use crate::SharedString;
use anyhow::{anyhow, Result};
use smallvec::SmallVec;
use std::fmt;
#[derive(Clone, Default, Eq, PartialEq, Hash)]
pub struct KeyContext(SmallVec<[ContextEntry; 8]>);
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
struct ContextEntry {
key: SharedString,
value: Option<SharedString>,
}
impl<'a> TryFrom<&'a str> for KeyContext {
type Error = anyhow::Error;
fn try_from(value: &'a str) -> Result<Self> {
Self::parse(value)
}
}
impl KeyContext {
pub fn parse(source: &str) -> Result<Self> {
let mut context = Self::default();
let source = skip_whitespace(source);
Self::parse_expr(&source, &mut context)?;
Ok(context)
}
fn parse_expr(mut source: &str, context: &mut Self) -> Result<()> {
if source.is_empty() {
return Ok(());
}
let key = source
.chars()
.take_while(|c| is_identifier_char(*c))
.collect::<String>();
source = skip_whitespace(&source[key.len()..]);
if let Some(suffix) = source.strip_prefix('=') {
source = skip_whitespace(suffix);
let value = source
.chars()
.take_while(|c| is_identifier_char(*c))
.collect::<String>();
source = skip_whitespace(&source[value.len()..]);
context.set(key, value);
} else {
context.add(key);
}
Self::parse_expr(source, context)
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn clear(&mut self) {
self.0.clear();
}
pub fn extend(&mut self, other: &Self) {
for entry in &other.0 {
if !self.contains(&entry.key) {
self.0.push(entry.clone());
}
}
}
pub fn add<I: Into<SharedString>>(&mut self, identifier: I) {
let key = identifier.into();
if !self.contains(&key) {
self.0.push(ContextEntry { key, value: None })
}
}
pub fn set<S1: Into<SharedString>, S2: Into<SharedString>>(&mut self, key: S1, value: S2) {
let key = key.into();
if !self.contains(&key) {
self.0.push(ContextEntry {
key,
value: Some(value.into()),
})
}
}
pub fn contains(&self, key: &str) -> bool {
self.0.iter().any(|entry| entry.key.as_ref() == key)
}
pub fn get(&self, key: &str) -> Option<&SharedString> {
self.0
.iter()
.find(|entry| entry.key.as_ref() == key)?
.value
.as_ref()
}
}
impl fmt::Debug for KeyContext {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut entries = self.0.iter().peekable();
while let Some(entry) = entries.next() {
if let Some(ref value) = entry.value {
write!(f, "{}={}", entry.key, value)?;
} else {
write!(f, "{}", entry.key)?;
}
if entries.peek().is_some() {
write!(f, " ")?;
}
}
Ok(())
}
}
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub enum KeyBindingContextPredicate {
Identifier(SharedString),
Equal(SharedString, SharedString),
NotEqual(SharedString, SharedString),
Child(
Box<KeyBindingContextPredicate>,
Box<KeyBindingContextPredicate>,
),
Not(Box<KeyBindingContextPredicate>),
And(
Box<KeyBindingContextPredicate>,
Box<KeyBindingContextPredicate>,
),
Or(
Box<KeyBindingContextPredicate>,
Box<KeyBindingContextPredicate>,
),
}
impl KeyBindingContextPredicate {
pub fn parse(source: &str) -> Result<Self> {
let source = skip_whitespace(source);
let (predicate, rest) = Self::parse_expr(source, 0)?;
if let Some(next) = rest.chars().next() {
Err(anyhow!("unexpected character {next:?}"))
} else {
Ok(predicate)
}
}
pub fn eval(&self, contexts: &[KeyContext]) -> bool {
let Some(context) = contexts.last() else {
return false;
};
match self {
Self::Identifier(name) => context.contains(name),
Self::Equal(left, right) => context
.get(left)
.map(|value| value == right)
.unwrap_or(false),
Self::NotEqual(left, right) => context
.get(left)
.map(|value| value != right)
.unwrap_or(true),
Self::Not(pred) => !pred.eval(contexts),
Self::Child(parent, child) => {
parent.eval(&contexts[..contexts.len() - 1]) && child.eval(contexts)
}
Self::And(left, right) => left.eval(contexts) && right.eval(contexts),
Self::Or(left, right) => left.eval(contexts) || right.eval(contexts),
}
}
fn parse_expr(mut source: &str, min_precedence: u32) -> anyhow::Result<(Self, &str)> {
type Op = fn(
KeyBindingContextPredicate,
KeyBindingContextPredicate,
) -> Result<KeyBindingContextPredicate>;
let (mut predicate, rest) = Self::parse_primary(source)?;
source = rest;
'parse: loop {
for (operator, precedence, constructor) in [
(">", PRECEDENCE_CHILD, Self::new_child as Op),
("&&", PRECEDENCE_AND, Self::new_and as Op),
("||", PRECEDENCE_OR, Self::new_or as Op),
("==", PRECEDENCE_EQ, Self::new_eq as Op),
("!=", PRECEDENCE_EQ, Self::new_neq as Op),
] {
if source.starts_with(operator) && precedence >= min_precedence {
source = skip_whitespace(&source[operator.len()..]);
let (right, rest) = Self::parse_expr(source, precedence + 1)?;
predicate = constructor(predicate, right)?;
source = rest;
continue 'parse;
}
}
break;
}
Ok((predicate, source))
}
fn parse_primary(mut source: &str) -> anyhow::Result<(Self, &str)> {
let next = source
.chars()
.next()
.ok_or_else(|| anyhow!("unexpected eof"))?;
match next {
'(' => {
source = skip_whitespace(&source[1..]);
let (predicate, rest) = Self::parse_expr(source, 0)?;
if rest.starts_with(')') {
source = skip_whitespace(&rest[1..]);
Ok((predicate, source))
} else {
Err(anyhow!("expected a ')'"))
}
}
'!' => {
let source = skip_whitespace(&source[1..]);
let (predicate, source) = Self::parse_expr(&source, PRECEDENCE_NOT)?;
Ok((KeyBindingContextPredicate::Not(Box::new(predicate)), source))
}
_ if is_identifier_char(next) => {
let len = source
.find(|c: char| !is_identifier_char(c))
.unwrap_or(source.len());
let (identifier, rest) = source.split_at(len);
source = skip_whitespace(rest);
Ok((
KeyBindingContextPredicate::Identifier(identifier.to_string().into()),
source,
))
}
_ => Err(anyhow!("unexpected character {next:?}")),
}
}
fn new_or(self, other: Self) -> Result<Self> {
Ok(Self::Or(Box::new(self), Box::new(other)))
}
fn new_and(self, other: Self) -> Result<Self> {
Ok(Self::And(Box::new(self), Box::new(other)))
}
fn new_child(self, other: Self) -> Result<Self> {
Ok(Self::Child(Box::new(self), Box::new(other)))
}
fn new_eq(self, other: Self) -> Result<Self> {
if let (Self::Identifier(left), Self::Identifier(right)) = (self, other) {
Ok(Self::Equal(left, right))
} else {
Err(anyhow!("operands must be identifiers"))
}
}
fn new_neq(self, other: Self) -> Result<Self> {
if let (Self::Identifier(left), Self::Identifier(right)) = (self, other) {
Ok(Self::NotEqual(left, right))
} else {
Err(anyhow!("operands must be identifiers"))
}
}
}
const PRECEDENCE_CHILD: u32 = 1;
const PRECEDENCE_OR: u32 = 2;
const PRECEDENCE_AND: u32 = 3;
const PRECEDENCE_EQ: u32 = 4;
const PRECEDENCE_NOT: u32 = 5;
fn is_identifier_char(c: char) -> bool {
c.is_alphanumeric() || c == '_' || c == '-'
}
fn skip_whitespace(source: &str) -> &str {
let len = source
.find(|c: char| !c.is_whitespace())
.unwrap_or(source.len());
&source[len..]
}
#[cfg(test)]
mod tests {
use super::*;
use crate as gpui;
use KeyBindingContextPredicate::*;
#[test]
fn test_actions_definition() {
{
actions!(A, B, C, D, E, F, G);
}
{
actions!(
A,
B,
C,
D,
E,
F,
G, // Don't wrap, test the trailing comma
);
}
}
#[test]
fn test_parse_context() {
let mut expected = KeyContext::default();
expected.add("baz");
expected.set("foo", "bar");
assert_eq!(KeyContext::parse("baz foo=bar").unwrap(), expected);
assert_eq!(KeyContext::parse("baz foo = bar").unwrap(), expected);
assert_eq!(
KeyContext::parse(" baz foo = bar baz").unwrap(),
expected
);
assert_eq!(KeyContext::parse(" baz foo = bar").unwrap(), expected);
}
#[test]
fn test_parse_identifiers() {
// Identifiers
assert_eq!(
KeyBindingContextPredicate::parse("abc12").unwrap(),
Identifier("abc12".into())
);
assert_eq!(
KeyBindingContextPredicate::parse("_1a").unwrap(),
Identifier("_1a".into())
);
}
#[test]
fn test_parse_negations() {
assert_eq!(
KeyBindingContextPredicate::parse("!abc").unwrap(),
Not(Box::new(Identifier("abc".into())))
);
assert_eq!(
KeyBindingContextPredicate::parse(" ! ! abc").unwrap(),
Not(Box::new(Not(Box::new(Identifier("abc".into())))))
);
}
#[test]
fn test_parse_equality_operators() {
assert_eq!(
KeyBindingContextPredicate::parse("a == b").unwrap(),
Equal("a".into(), "b".into())
);
assert_eq!(
KeyBindingContextPredicate::parse("c!=d").unwrap(),
NotEqual("c".into(), "d".into())
);
assert_eq!(
KeyBindingContextPredicate::parse("c == !d")
.unwrap_err()
.to_string(),
"operands must be identifiers"
);
}
#[test]
fn test_parse_boolean_operators() {
assert_eq!(
KeyBindingContextPredicate::parse("a || b").unwrap(),
Or(
Box::new(Identifier("a".into())),
Box::new(Identifier("b".into()))
)
);
assert_eq!(
KeyBindingContextPredicate::parse("a || !b && c").unwrap(),
Or(
Box::new(Identifier("a".into())),
Box::new(And(
Box::new(Not(Box::new(Identifier("b".into())))),
Box::new(Identifier("c".into()))
))
)
);
assert_eq!(
KeyBindingContextPredicate::parse("a && b || c&&d").unwrap(),
Or(
Box::new(And(
Box::new(Identifier("a".into())),
Box::new(Identifier("b".into()))
)),
Box::new(And(
Box::new(Identifier("c".into())),
Box::new(Identifier("d".into()))
))
)
);
assert_eq!(
KeyBindingContextPredicate::parse("a == b && c || d == e && f").unwrap(),
Or(
Box::new(And(
Box::new(Equal("a".into(), "b".into())),
Box::new(Identifier("c".into()))
)),
Box::new(And(
Box::new(Equal("d".into(), "e".into())),
Box::new(Identifier("f".into()))
))
)
);
assert_eq!(
KeyBindingContextPredicate::parse("a && b && c && d").unwrap(),
And(
Box::new(And(
Box::new(And(
Box::new(Identifier("a".into())),
Box::new(Identifier("b".into()))
)),
Box::new(Identifier("c".into())),
)),
Box::new(Identifier("d".into()))
),
);
}
#[test]
fn test_parse_parenthesized_expressions() {
assert_eq!(
KeyBindingContextPredicate::parse("a && (b == c || d != e)").unwrap(),
And(
Box::new(Identifier("a".into())),
Box::new(Or(
Box::new(Equal("b".into(), "c".into())),
Box::new(NotEqual("d".into(), "e".into())),
)),
),
);
assert_eq!(
KeyBindingContextPredicate::parse(" ( a || b ) ").unwrap(),
Or(
Box::new(Identifier("a".into())),
Box::new(Identifier("b".into())),
)
);
}
}
+2 -2
View File
@@ -1,4 +1,4 @@
use crate::{DispatchContextPredicate, KeyBinding, Keystroke};
use crate::{KeyBinding, KeyBindingContextPredicate, Keystroke};
use collections::HashSet;
use smallvec::SmallVec;
use std::{any::TypeId, collections::HashMap};
@@ -11,7 +11,7 @@ pub struct Keymap {
bindings: Vec<KeyBinding>,
binding_indices_by_action_id: HashMap<TypeId, SmallVec<[usize; 3]>>,
disabled_keystrokes:
HashMap<SmallVec<[Keystroke; 2]>, HashSet<Option<DispatchContextPredicate>>>,
HashMap<SmallVec<[Keystroke; 2]>, HashSet<Option<KeyBindingContextPredicate>>>,
version: KeymapVersion,
}
+5 -5
View File
@@ -1,15 +1,15 @@
use crate::{Action, DispatchContext, Keymap, KeymapVersion, Keystroke};
use crate::{Action, KeyContext, Keymap, KeymapVersion, Keystroke};
use parking_lot::Mutex;
use smallvec::SmallVec;
use std::sync::Arc;
pub struct KeyMatcher {
pub struct KeystrokeMatcher {
pending_keystrokes: Vec<Keystroke>,
keymap: Arc<Mutex<Keymap>>,
keymap_version: KeymapVersion,
}
impl KeyMatcher {
impl KeystrokeMatcher {
pub fn new(keymap: Arc<Mutex<Keymap>>) -> Self {
let keymap_version = keymap.lock().version();
Self {
@@ -44,7 +44,7 @@ impl KeyMatcher {
pub fn match_keystroke(
&mut self,
keystroke: &Keystroke,
context_stack: &[&DispatchContext],
context_stack: &[KeyContext],
) -> KeyMatch {
let keymap = self.keymap.lock();
// Clear pending keystrokes if the keymap has changed since the last matched keystroke.
@@ -86,7 +86,7 @@ impl KeyMatcher {
pub fn keystrokes_for_action(
&self,
action: &dyn Action,
contexts: &[&DispatchContext],
contexts: &[KeyContext],
) -> Option<SmallVec<[Keystroke; 2]>> {
self.keymap
.lock()
+2
View File
@@ -1,7 +1,9 @@
mod binding;
mod context;
mod keymap;
mod matcher;
pub use binding::*;
pub use context::*;
pub use keymap::*;
pub use matcher::*;
+1
View File
@@ -0,0 +1 @@
pub use crate::{Context, ParentElement, Refineable};
-4
View File
@@ -184,10 +184,6 @@ impl AnyView {
.compute_layout(layout_id, available_space);
(self.paint)(self, &mut rendered_element, cx);
}
pub(crate) fn draw_dispatch_stack(&self, cx: &mut WindowContext) {
(self.initialize)(self, cx);
}
}
impl<V: 'static> Component<V> for AnyView {
+297 -412
View File
@@ -1,15 +1,15 @@
use crate::{
build_action_from_type, px, size, Action, AnyBox, AnyDrag, AnyView, AppContext,
key_dispatch::DispatchActionListener, px, size, Action, AnyBox, AnyDrag, AnyView, AppContext,
AsyncWindowContext, AvailableSpace, Bounds, BoxShadow, Context, Corners, CursorStyle,
DevicePixels, DispatchContext, DisplayId, Edges, Effect, Entity, EntityId, EventEmitter,
FileDropEvent, FocusEvent, FontId, GlobalElementId, GlyphId, Hsla, ImageData, InputEvent,
IsZero, KeyListener, KeyMatch, KeyMatcher, Keystroke, LayoutId, Model, ModelContext, Modifiers,
MonochromeSprite, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Path, Pixels,
PlatformAtlas, PlatformDisplay, PlatformInputHandler, PlatformWindow, Point, PolychromeSprite,
PromptLevel, Quad, Render, RenderGlyphParams, RenderImageParams, RenderSvgParams, ScaledPixels,
SceneBuilder, Shadow, SharedString, Size, Style, SubscriberSet, Subscription,
TaffyLayoutEngine, Task, Underline, UnderlineStyle, View, VisualContext, WeakView,
WindowBounds, WindowOptions, SUBPIXEL_VARIANTS,
DevicePixels, DispatchNodeId, DispatchTree, DisplayId, Edges, Effect, Entity, EntityId,
EventEmitter, FileDropEvent, FocusEvent, FontId, GlobalElementId, GlyphId, Hsla, ImageData,
InputEvent, IsZero, KeyBinding, KeyContext, KeyDownEvent, LayoutId, Model, ModelContext,
Modifiers, MonochromeSprite, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Path,
Pixels, PlatformAtlas, PlatformDisplay, PlatformInputHandler, PlatformWindow, Point,
PolychromeSprite, PromptLevel, Quad, Render, RenderGlyphParams, RenderImageParams,
RenderSvgParams, ScaledPixels, SceneBuilder, Shadow, SharedString, Size, Style, SubscriberSet,
Subscription, TaffyLayoutEngine, Task, Underline, UnderlineStyle, View, VisualContext,
WeakView, WindowBounds, WindowOptions, SUBPIXEL_VARIANTS,
};
use anyhow::{anyhow, Context as _, Result};
use collections::HashMap;
@@ -60,16 +60,7 @@ pub enum DispatchPhase {
}
type AnyObserver = Box<dyn FnMut(&mut WindowContext) -> bool + 'static>;
type AnyListener = Box<dyn FnMut(&dyn Any, DispatchPhase, &mut WindowContext) + 'static>;
type AnyKeyListener = Box<
dyn Fn(
&dyn Any,
&[&DispatchContext],
DispatchPhase,
&mut WindowContext,
) -> Option<Box<dyn Action>>
+ 'static,
>;
type AnyMouseListener = Box<dyn FnMut(&dyn Any, DispatchPhase, &mut WindowContext) + 'static>;
type AnyFocusListener = Box<dyn Fn(&FocusEvent, &mut WindowContext) + 'static>;
type AnyWindowFocusListener = Box<dyn FnMut(&FocusEvent, &mut WindowContext) -> bool + 'static>;
@@ -97,20 +88,10 @@ impl FocusId {
/// Obtains whether this handle contains the given handle in the most recently rendered frame.
pub(crate) fn contains(&self, other: Self, cx: &WindowContext) -> bool {
let mut ancestor = Some(other);
while let Some(ancestor_id) = ancestor {
if *self == ancestor_id {
return true;
} else {
ancestor = cx
.window
.current_frame
.focus_parents_by_child
.get(&ancestor_id)
.copied();
}
}
false
cx.window
.current_frame
.dispatch_tree
.focus_contains(*self, other)
}
}
@@ -227,20 +208,31 @@ pub struct Window {
pub(crate) focus: Option<FocusId>,
}
#[derive(Default)]
// #[derive(Default)]
pub(crate) struct Frame {
element_states: HashMap<GlobalElementId, AnyBox>,
key_matchers: HashMap<GlobalElementId, KeyMatcher>,
mouse_listeners: HashMap<TypeId, Vec<(StackingOrder, AnyListener)>>,
mouse_listeners: HashMap<TypeId, Vec<(StackingOrder, AnyMouseListener)>>,
pub(crate) dispatch_tree: DispatchTree,
pub(crate) focus_listeners: Vec<AnyFocusListener>,
pub(crate) key_dispatch_stack: Vec<KeyDispatchStackFrame>,
freeze_key_dispatch_stack: bool,
focus_parents_by_child: HashMap<FocusId, FocusId>,
pub(crate) scene_builder: SceneBuilder,
z_index_stack: StackingOrder,
content_mask_stack: Vec<ContentMask<Pixels>>,
element_offset_stack: Vec<Point<Pixels>>,
focus_stack: Vec<FocusId>,
}
impl Frame {
pub fn new(dispatch_tree: DispatchTree) -> Self {
Frame {
element_states: HashMap::default(),
mouse_listeners: HashMap::default(),
dispatch_tree,
focus_listeners: Vec::new(),
scene_builder: SceneBuilder::default(),
z_index_stack: StackingOrder::default(),
content_mask_stack: Vec::new(),
element_offset_stack: Vec::new(),
}
}
}
impl Window {
@@ -309,8 +301,8 @@ impl Window {
layout_engine: TaffyLayoutEngine::new(),
root_view: None,
element_id_stack: GlobalElementId::default(),
previous_frame: Frame::default(),
current_frame: Frame::default(),
previous_frame: Frame::new(DispatchTree::new(cx.keymap.clone())),
current_frame: Frame::new(DispatchTree::new(cx.keymap.clone())),
focus_handles: Arc::new(RwLock::new(SlotMap::with_key())),
focus_listeners: SubscriberSet::new(),
default_prevented: true,
@@ -328,18 +320,6 @@ impl Window {
}
}
/// When constructing the element tree, we maintain a stack of key dispatch frames until we
/// find the focused element. We interleave key listeners with dispatch contexts so we can use the
/// contexts when matching key events against the keymap. A key listener can be either an action
/// handler or a [KeyDown] / [KeyUp] event listener.
pub(crate) enum KeyDispatchStackFrame {
Listener {
event_type: TypeId,
listener: AnyKeyListener,
},
Context(DispatchContext),
}
/// Indicates which region of the window is visible. Content falling outside of this mask will not be
/// rendered. Currently, only rectangular content masks are supported, but we give the mask its own type
/// to leave room to support more complex shapes in the future.
@@ -407,21 +387,16 @@ impl<'a> WindowContext<'a> {
/// Move focus to the element associated with the given `FocusHandle`.
pub fn focus(&mut self, handle: &FocusHandle) {
if self.window.focus == Some(handle.id) {
return;
}
let focus_id = handle.id;
if self.window.last_blur.is_none() {
self.window.last_blur = Some(self.window.focus);
}
self.window.focus = Some(handle.id);
// self.window.current_frame.key_dispatch_stack.clear()
// self.window.root_view.initialize()
self.window.focus = Some(focus_id);
self.app.push_effect(Effect::FocusChanged {
window_handle: self.window.handle,
focused: Some(handle.id),
focused: Some(focus_id),
});
self.notify();
}
@@ -441,11 +416,18 @@ impl<'a> WindowContext<'a> {
}
pub fn dispatch_action(&mut self, action: Box<dyn Action>) {
self.defer(|cx| {
cx.app.propagate_event = true;
let stack = cx.dispatch_stack();
cx.dispatch_action_internal(action, &stack[..])
})
if let Some(focus_handle) = self.focused() {
self.defer(move |cx| {
if let Some(node_id) = cx
.window
.current_frame
.dispatch_tree
.focusable_node_id(focus_handle.id)
{
cx.dispatch_action_on_node(node_id, action);
}
})
}
}
/// Schedules the given function to be run at the end of the current effect cycle, allowing entities
@@ -731,6 +713,43 @@ impl<'a> WindowContext<'a> {
))
}
/// Register a key event listener on the window for the current frame. The type of event
/// is determined by the first parameter of the given listener. When the next frame is rendered
/// the listener will be cleared.
///
/// This is a fairly low-level method, so prefer using event handlers on elements unless you have
/// a specific need to register a global listener.
pub fn on_key_event<Event: 'static>(
&mut self,
handler: impl Fn(&Event, DispatchPhase, &mut WindowContext) + 'static,
) {
self.window
.current_frame
.dispatch_tree
.on_key_event(Rc::new(move |event, phase, cx| {
if let Some(event) = event.downcast_ref::<Event>() {
handler(event, phase, cx)
}
}));
}
/// Register an action listener on the window for the current frame. The type of action
/// is determined by the first parameter of the given listener. When the next frame is rendered
/// the listener will be cleared.
///
/// This is a fairly low-level method, so prefer using action handlers on elements unless you have
/// a specific need to register a global listener.
pub fn on_action(
&mut self,
action_type: TypeId,
handler: impl Fn(&dyn Any, DispatchPhase, &mut WindowContext) + 'static,
) {
self.window.current_frame.dispatch_tree.on_action(
action_type,
Rc::new(move |action, phase, cx| handler(action, phase, cx)),
);
}
/// The position of the mouse relative to the window.
pub fn mouse_position(&self) -> Point<Pixels> {
self.window.mouse_position
@@ -1079,26 +1098,6 @@ impl<'a> WindowContext<'a> {
self.window.dirty = false;
}
pub(crate) fn dispatch_stack(&mut self) -> Vec<KeyDispatchStackFrame> {
let root_view = self.window.root_view.take().unwrap();
let window = &mut *self.window;
let mut spare_frame = Frame::default();
mem::swap(&mut spare_frame, &mut window.previous_frame);
self.start_frame();
root_view.draw_dispatch_stack(self);
let window = &mut *self.window;
// restore the old values of current and previous frame,
// putting the new frame into spare_frame.
mem::swap(&mut window.current_frame, &mut window.previous_frame);
mem::swap(&mut spare_frame, &mut window.previous_frame);
self.window.root_view = Some(root_view);
spare_frame.key_dispatch_stack
}
/// Rotate the current frame and the previous frame, then clear the current frame.
/// We repopulate all state in the current frame during each paint.
fn start_frame(&mut self) {
@@ -1110,12 +1109,9 @@ impl<'a> WindowContext<'a> {
mem::swap(&mut window.previous_frame, &mut window.current_frame);
let frame = &mut window.current_frame;
frame.element_states.clear();
frame.key_matchers.clear();
frame.mouse_listeners.values_mut().for_each(Vec::clear);
frame.focus_listeners.clear();
frame.key_dispatch_stack.clear();
frame.focus_parents_by_child.clear();
frame.freeze_key_dispatch_stack = false;
frame.dispatch_tree.clear();
}
/// Dispatch a mouse or keyboard event on the window.
@@ -1177,146 +1173,172 @@ impl<'a> WindowContext<'a> {
};
if let Some(any_mouse_event) = event.mouse_event() {
if let Some(mut handlers) = self
.window
.current_frame
.mouse_listeners
.remove(&any_mouse_event.type_id())
{
// Because handlers may add other handlers, we sort every time.
handlers.sort_by(|(a, _), (b, _)| a.cmp(b));
// Capture phase, events bubble from back to front. Handlers for this phase are used for
// special purposes, such as detecting events outside of a given Bounds.
for (_, handler) in &mut handlers {
handler(any_mouse_event, DispatchPhase::Capture, self);
if !self.app.propagate_event {
break;
}
}
// Bubble phase, where most normal handlers do their work.
if self.app.propagate_event {
for (_, handler) in handlers.iter_mut().rev() {
handler(any_mouse_event, DispatchPhase::Bubble, self);
if !self.app.propagate_event {
break;
}
}
}
if self.app.propagate_event
&& any_mouse_event.downcast_ref::<MouseUpEvent>().is_some()
{
self.active_drag = None;
}
// Just in case any handlers added new handlers, which is weird, but possible.
handlers.extend(
self.window
.current_frame
.mouse_listeners
.get_mut(&any_mouse_event.type_id())
.into_iter()
.flat_map(|handlers| handlers.drain(..)),
);
self.window
.current_frame
.mouse_listeners
.insert(any_mouse_event.type_id(), handlers);
}
self.dispatch_mouse_event(any_mouse_event);
} else if let Some(any_key_event) = event.keyboard_event() {
let key_dispatch_stack = mem::take(&mut self.window.current_frame.key_dispatch_stack);
let key_event_type = any_key_event.type_id();
let mut context_stack = SmallVec::<[&DispatchContext; 16]>::new();
for (ix, frame) in key_dispatch_stack.iter().enumerate() {
match frame {
KeyDispatchStackFrame::Listener {
event_type,
listener,
} => {
if key_event_type == *event_type {
if let Some(action) = listener(
any_key_event,
&context_stack,
DispatchPhase::Capture,
self,
) {
self.dispatch_action_internal(action, &key_dispatch_stack[..ix]);
}
if !self.app.propagate_event {
break;
}
}
}
KeyDispatchStackFrame::Context(context) => {
context_stack.push(&context);
}
}
}
if self.app.propagate_event {
for (ix, frame) in key_dispatch_stack.iter().enumerate().rev() {
match frame {
KeyDispatchStackFrame::Listener {
event_type,
listener,
} => {
if key_event_type == *event_type {
if let Some(action) = listener(
any_key_event,
&context_stack,
DispatchPhase::Bubble,
self,
) {
self.dispatch_action_internal(
action,
&key_dispatch_stack[..ix],
);
}
if !self.app.propagate_event {
break;
}
}
}
KeyDispatchStackFrame::Context(_) => {
context_stack.pop();
}
}
}
}
drop(context_stack);
self.window.current_frame.key_dispatch_stack = key_dispatch_stack;
self.dispatch_key_event(any_key_event);
}
!self.app.propagate_event
}
/// Attempt to map a keystroke to an action based on the keymap.
pub fn match_keystroke(
&mut self,
element_id: &GlobalElementId,
keystroke: &Keystroke,
context_stack: &[&DispatchContext],
) -> KeyMatch {
let key_match = self
fn dispatch_mouse_event(&mut self, event: &dyn Any) {
if let Some(mut handlers) = self
.window
.current_frame
.key_matchers
.get_mut(element_id)
.unwrap()
.match_keystroke(keystroke, context_stack);
.mouse_listeners
.remove(&event.type_id())
{
// Because handlers may add other handlers, we sort every time.
handlers.sort_by(|(a, _), (b, _)| a.cmp(b));
if key_match.is_some() {
for matcher in self.window.current_frame.key_matchers.values_mut() {
matcher.clear_pending();
// Capture phase, events bubble from back to front. Handlers for this phase are used for
// special purposes, such as detecting events outside of a given Bounds.
for (_, handler) in &mut handlers {
handler(event, DispatchPhase::Capture, self);
if !self.app.propagate_event {
break;
}
}
// Bubble phase, where most normal handlers do their work.
if self.app.propagate_event {
for (_, handler) in handlers.iter_mut().rev() {
handler(event, DispatchPhase::Bubble, self);
if !self.app.propagate_event {
break;
}
}
}
if self.app.propagate_event && event.downcast_ref::<MouseUpEvent>().is_some() {
self.active_drag = None;
}
// Just in case any handlers added new handlers, which is weird, but possible.
handlers.extend(
self.window
.current_frame
.mouse_listeners
.get_mut(&event.type_id())
.into_iter()
.flat_map(|handlers| handlers.drain(..)),
);
self.window
.current_frame
.mouse_listeners
.insert(event.type_id(), handlers);
}
}
fn dispatch_key_event(&mut self, event: &dyn Any) {
if let Some(node_id) = self.window.focus.and_then(|focus_id| {
self.window
.current_frame
.dispatch_tree
.focusable_node_id(focus_id)
}) {
let dispatch_path = self
.window
.current_frame
.dispatch_tree
.dispatch_path(node_id);
// Capture phase
let mut context_stack: SmallVec<[KeyContext; 16]> = SmallVec::new();
self.propagate_event = true;
for node_id in &dispatch_path {
let node = self.window.current_frame.dispatch_tree.node(*node_id);
if !node.context.is_empty() {
context_stack.push(node.context.clone());
}
for key_listener in node.key_listeners.clone() {
key_listener(event, DispatchPhase::Capture, self);
if !self.propagate_event {
return;
}
}
}
// Bubble phase
for node_id in dispatch_path.iter().rev() {
// Handle low level key events
let node = self.window.current_frame.dispatch_tree.node(*node_id);
for key_listener in node.key_listeners.clone() {
key_listener(event, DispatchPhase::Bubble, self);
if !self.propagate_event {
return;
}
}
// Match keystrokes
let node = self.window.current_frame.dispatch_tree.node(*node_id);
if !node.context.is_empty() {
if let Some(key_down_event) = event.downcast_ref::<KeyDownEvent>() {
if let Some(action) = self
.window
.current_frame
.dispatch_tree
.dispatch_key(&key_down_event.keystroke, &context_stack)
{
self.dispatch_action_on_node(*node_id, action);
if !self.propagate_event {
return;
}
}
}
context_stack.pop();
}
}
}
}
fn dispatch_action_on_node(&mut self, node_id: DispatchNodeId, action: Box<dyn Action>) {
let dispatch_path = self
.window
.current_frame
.dispatch_tree
.dispatch_path(node_id);
// Capture phase
for node_id in &dispatch_path {
let node = self.window.current_frame.dispatch_tree.node(*node_id);
for DispatchActionListener {
action_type,
listener,
} in node.action_listeners.clone()
{
let any_action = action.as_any();
if action_type == any_action.type_id() {
listener(any_action, DispatchPhase::Capture, self);
if !self.propagate_event {
return;
}
}
}
}
key_match
// Bubble phase
for node_id in dispatch_path.iter().rev() {
let node = self.window.current_frame.dispatch_tree.node(*node_id);
for DispatchActionListener {
action_type,
listener,
} in node.action_listeners.clone()
{
let any_action = action.as_any();
if action_type == any_action.type_id() {
self.propagate_event = false; // Actions stop propagation by default during the bubble phase
listener(any_action, DispatchPhase::Bubble, self);
if !self.propagate_event {
return;
}
}
}
}
}
/// Register the given handler to be invoked whenever the global of the given type
@@ -1345,106 +1367,22 @@ impl<'a> WindowContext<'a> {
self.window.platform_window.prompt(level, msg, answers)
}
pub fn available_actions(&self) -> impl Iterator<Item = Box<dyn Action>> + '_ {
let key_dispatch_stack = &self.window.previous_frame.key_dispatch_stack;
key_dispatch_stack.iter().filter_map(|frame| {
match frame {
// todo!factor out a KeyDispatchStackFrame::Action
KeyDispatchStackFrame::Listener {
event_type,
listener: _,
} => {
match build_action_from_type(event_type) {
Ok(action) => Some(action),
Err(err) => {
dbg!(err);
None
} // we'll hit his if TypeId == KeyDown
}
}
KeyDispatchStackFrame::Context(_) => None,
}
})
pub fn available_actions(&self) -> Vec<Box<dyn Action>> {
if let Some(focus_id) = self.window.focus {
self.window
.current_frame
.dispatch_tree
.available_actions(focus_id)
} else {
Vec::new()
}
}
pub(crate) fn dispatch_action_internal(
&mut self,
action: Box<dyn Action>,
dispatch_stack: &[KeyDispatchStackFrame],
) {
let action_type = action.as_any().type_id();
if let Some(mut global_listeners) = self.app.global_action_listeners.remove(&action_type) {
for listener in &global_listeners {
listener(action.as_ref(), DispatchPhase::Capture, self);
if !self.app.propagate_event {
break;
}
}
global_listeners.extend(
self.global_action_listeners
.remove(&action_type)
.unwrap_or_default(),
);
self.global_action_listeners
.insert(action_type, global_listeners);
}
if self.app.propagate_event {
for stack_frame in dispatch_stack {
if let KeyDispatchStackFrame::Listener {
event_type,
listener,
} = stack_frame
{
if action_type == *event_type {
listener(action.as_any(), &[], DispatchPhase::Capture, self);
if !self.app.propagate_event {
break;
}
}
}
}
}
if self.app.propagate_event {
for stack_frame in dispatch_stack.iter().rev() {
if let KeyDispatchStackFrame::Listener {
event_type,
listener,
} = stack_frame
{
if action_type == *event_type {
self.app.propagate_event = false;
listener(action.as_any(), &[], DispatchPhase::Bubble, self);
if !self.app.propagate_event {
break;
}
}
}
}
}
if self.app.propagate_event {
if let Some(mut global_listeners) =
self.app.global_action_listeners.remove(&action_type)
{
for listener in global_listeners.iter().rev() {
self.app.propagate_event = false;
listener(action.as_ref(), DispatchPhase::Bubble, self);
if !self.app.propagate_event {
break;
}
}
global_listeners.extend(
self.global_action_listeners
.remove(&action_type)
.unwrap_or_default(),
);
self.global_action_listeners
.insert(action_type, global_listeners);
}
}
pub fn bindings_for_action(&self, action: &dyn Action) -> Vec<KeyBinding> {
self.window
.current_frame
.dispatch_tree
.bindings_for_action(action)
}
}
@@ -1631,22 +1569,9 @@ pub trait BorrowWindow: BorrowMut<Window> + BorrowMut<AppContext> {
id: impl Into<ElementId>,
f: impl FnOnce(GlobalElementId, &mut Self) -> R,
) -> R {
let keymap = self.app_mut().keymap.clone();
let window = self.window_mut();
window.element_id_stack.push(id.into());
let global_id = window.element_id_stack.clone();
if window.current_frame.key_matchers.get(&global_id).is_none() {
window.current_frame.key_matchers.insert(
global_id.clone(),
window
.previous_frame
.key_matchers
.remove(&global_id)
.unwrap_or_else(|| KeyMatcher::new(keymap)),
);
}
let result = f(global_id, self);
let window: &mut Window = self.borrow_mut();
window.element_id_stack.pop();
@@ -2134,94 +2059,28 @@ impl<'a, V: 'static> ViewContext<'a, V> {
}));
}
pub fn with_key_listeners<R>(
pub fn with_key_dispatch<R>(
&mut self,
key_listeners: impl IntoIterator<Item = (TypeId, KeyListener<V>)>,
f: impl FnOnce(&mut Self) -> R,
context: KeyContext,
focus_handle: Option<FocusHandle>,
f: impl FnOnce(Option<FocusHandle>, &mut Self) -> R,
) -> R {
let old_stack_len = self.window.current_frame.key_dispatch_stack.len();
if !self.window.current_frame.freeze_key_dispatch_stack {
for (event_type, listener) in key_listeners {
let handle = self.view().downgrade();
let listener = Box::new(
move |event: &dyn Any,
context_stack: &[&DispatchContext],
phase: DispatchPhase,
cx: &mut WindowContext<'_>| {
handle
.update(cx, |view, cx| {
listener(view, event, context_stack, phase, cx)
})
.log_err()
.flatten()
},
);
self.window.current_frame.key_dispatch_stack.push(
KeyDispatchStackFrame::Listener {
event_type,
listener,
},
);
}
}
let window = &mut self.window;
let result = f(self);
if !self.window.current_frame.freeze_key_dispatch_stack {
self.window
window
.current_frame
.dispatch_tree
.push_node(context, &mut window.previous_frame.dispatch_tree);
if let Some(focus_handle) = focus_handle.as_ref() {
window
.current_frame
.key_dispatch_stack
.truncate(old_stack_len);
.dispatch_tree
.make_focusable(focus_handle.id);
}
let result = f(focus_handle, self);
result
}
self.window.current_frame.dispatch_tree.pop_node();
pub fn with_key_dispatch_context<R>(
&mut self,
context: DispatchContext,
f: impl FnOnce(&mut Self) -> R,
) -> R {
if context.is_empty() {
return f(self);
}
if !self.window.current_frame.freeze_key_dispatch_stack {
self.window
.current_frame
.key_dispatch_stack
.push(KeyDispatchStackFrame::Context(context));
}
let result = f(self);
if !self.window.previous_frame.freeze_key_dispatch_stack {
self.window.previous_frame.key_dispatch_stack.pop();
}
result
}
pub fn with_focus<R>(
&mut self,
focus_handle: FocusHandle,
f: impl FnOnce(&mut Self) -> R,
) -> R {
if let Some(parent_focus_id) = self.window.current_frame.focus_stack.last().copied() {
self.window
.current_frame
.focus_parents_by_child
.insert(focus_handle.id, parent_focus_id);
}
self.window.current_frame.focus_stack.push(focus_handle.id);
if Some(focus_handle.id) == self.window.focus {
self.window.current_frame.freeze_key_dispatch_stack = true;
}
let result = f(self);
self.window.current_frame.focus_stack.pop();
result
}
@@ -2275,6 +2134,32 @@ impl<'a, V: 'static> ViewContext<'a, V> {
});
}
pub fn on_key_event<Event: 'static>(
&mut self,
handler: impl Fn(&mut V, &Event, DispatchPhase, &mut ViewContext<V>) + 'static,
) {
let handle = self.view();
self.window_cx.on_key_event(move |event, phase, cx| {
handle.update(cx, |view, cx| {
handler(view, event, phase, cx);
})
});
}
pub fn on_action(
&mut self,
action_type: TypeId,
handler: impl Fn(&mut V, &dyn Any, DispatchPhase, &mut ViewContext<V>) + 'static,
) {
let handle = self.view();
self.window_cx
.on_action(action_type, move |action, phase, cx| {
handle.update(cx, |view, cx| {
handler(view, action, phase, cx);
})
});
}
/// Set an input handler, such as [ElementInputHandler], which interfaces with the
/// platform to receive textual input with proper integration with concerns such
/// as IME interactions.
+41 -28
View File
@@ -1,11 +1,10 @@
use editor::Editor;
use gpui::{
div, uniform_list, Component, Div, FocusEnabled, ParentElement, Render, StatefulInteractivity,
StatelessInteractive, Styled, Task, UniformListScrollHandle, View, ViewContext, VisualContext,
WindowContext,
div, uniform_list, Component, Div, ParentElement, Render, StatelessInteractive, Styled, Task,
UniformListScrollHandle, View, ViewContext, VisualContext, WindowContext,
};
use std::cmp;
use ui::{prelude::*, v_stack, Divider};
use std::{cmp, sync::Arc};
use ui::{prelude::*, v_stack, Divider, Label, LabelColor};
pub struct Picker<D: PickerDelegate> {
pub delegate: D,
@@ -21,7 +20,7 @@ pub trait PickerDelegate: Sized + 'static {
fn selected_index(&self) -> usize;
fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<Picker<Self>>);
// fn placeholder_text(&self) -> Arc<str>;
fn placeholder_text(&self) -> Arc<str>;
fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()>;
fn confirm(&mut self, secondary: bool, cx: &mut ViewContext<Picker<Self>>);
@@ -37,7 +36,11 @@ pub trait PickerDelegate: Sized + 'static {
impl<D: PickerDelegate> Picker<D> {
pub fn new(delegate: D, cx: &mut ViewContext<Self>) -> Self {
let editor = cx.build_view(|cx| Editor::single_line(cx));
let editor = cx.build_view(|cx| {
let mut editor = Editor::single_line(cx);
editor.set_placeholder_text(delegate.placeholder_text(), cx);
editor
});
cx.subscribe(&editor, Self::on_input_editor_event).detach();
Self {
delegate,
@@ -136,13 +139,11 @@ impl<D: PickerDelegate> Picker<D> {
}
impl<D: PickerDelegate> Render for Picker<D> {
type Element = Div<Self, StatefulInteractivity<Self>, FocusEnabled<Self>>;
type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
div()
.context("picker")
.id("picker-container")
.focusable()
.size_full()
.elevation_2(cx)
.on_action(Self::select_next)
@@ -159,23 +160,35 @@ impl<D: PickerDelegate> Render for Picker<D> {
.child(div().px_1().py_0p5().child(self.editor.clone())),
)
.child(Divider::horizontal())
.child(
v_stack()
.p_1()
.grow()
.child(
uniform_list("candidates", self.delegate.match_count(), {
move |this: &mut Self, visible_range, cx| {
let selected_ix = this.delegate.selected_index();
visible_range
.map(|ix| this.delegate.render_match(ix, ix == selected_ix, cx))
.collect()
}
})
.track_scroll(self.scroll_handle.clone()),
)
.max_h_72()
.overflow_hidden(),
)
.when(self.delegate.match_count() > 0, |el| {
el.child(
v_stack()
.p_1()
.grow()
.child(
uniform_list("candidates", self.delegate.match_count(), {
move |this: &mut Self, visible_range, cx| {
let selected_ix = this.delegate.selected_index();
visible_range
.map(|ix| {
this.delegate.render_match(ix, ix == selected_ix, cx)
})
.collect()
}
})
.track_scroll(self.scroll_handle.clone()),
)
.max_h_72()
.overflow_hidden(),
)
})
.when(self.delegate.match_count() == 0, |el| {
el.child(
v_stack()
.p_1()
.grow()
.child(Label::new("No matches").color(LabelColor::Muted)),
)
})
}
}
+17 -12
View File
@@ -1,12 +1,16 @@
use gpui::{
actions, div, Div, FocusEnabled, Focusable, KeyBinding, ParentElement, Render,
StatefulInteractivity, StatelessInteractive, Styled, View, VisualContext, WindowContext,
actions, div, Div, FocusHandle, Focusable, FocusableKeyDispatch, KeyBinding, ParentElement,
Render, StatefulInteractivity, StatelessInteractive, Styled, View, VisualContext,
WindowContext,
};
use theme2::ActiveTheme;
actions!(ActionA, ActionB, ActionC);
pub struct FocusStory {}
pub struct FocusStory {
child_1_focus: FocusHandle,
child_2_focus: FocusHandle,
}
impl FocusStory {
pub fn view(cx: &mut WindowContext) -> View<Self> {
@@ -16,12 +20,15 @@ impl FocusStory {
KeyBinding::new("cmd-c", ActionC, None),
]);
cx.build_view(move |cx| Self {})
cx.build_view(move |cx| Self {
child_1_focus: cx.focus_handle(),
child_2_focus: cx.focus_handle(),
})
}
}
impl Render for FocusStory {
type Element = Div<Self, StatefulInteractivity<Self>, FocusEnabled<Self>>;
type Element = Div<Self, StatefulInteractivity<Self>, FocusableKeyDispatch<Self>>;
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Self::Element {
let theme = cx.theme();
@@ -31,18 +38,16 @@ impl Render for FocusStory {
let color_4 = theme.status().conflict;
let color_5 = theme.status().ignored;
let color_6 = theme.status().renamed;
let child_1 = cx.focus_handle();
let child_2 = cx.focus_handle();
div()
.id("parent")
.focusable()
.context("parent")
.on_action(|_, action: &ActionA, cx| {
println!("Action A dispatched on parent during");
println!("Action A dispatched on parent");
})
.on_action(|_, action: &ActionB, cx| {
println!("Action B dispatched on parent during");
println!("Action B dispatched on parent");
})
.on_focus(|_, _, _| println!("Parent focused"))
.on_blur(|_, _, _| println!("Parent blurred"))
@@ -56,7 +61,7 @@ impl Render for FocusStory {
.focus_in(|style| style.bg(color_3))
.child(
div()
.track_focus(&child_1)
.track_focus(&self.child_1_focus)
.context("child-1")
.on_action(|_, action: &ActionB, cx| {
println!("Action B dispatched on child 1 during");
@@ -76,10 +81,10 @@ impl Render for FocusStory {
)
.child(
div()
.track_focus(&child_2)
.track_focus(&self.child_2_focus)
.context("child-2")
.on_action(|_, action: &ActionC, cx| {
println!("Action C dispatched on child 2 during");
println!("Action C dispatched on child 2");
})
.w_full()
.h_6()
+4
View File
@@ -44,6 +44,10 @@ impl PickerDelegate for Delegate {
self.candidates.len()
}
fn placeholder_text(&self) -> Arc<str> {
"Test".into()
}
fn render_match(
&self,
ix: usize,
+1 -1
View File
@@ -48,7 +48,7 @@ fn main() {
let args = Args::parse();
let story_selector = args.story.clone();
let theme_name = args.theme.unwrap_or("Zed Pro Moonlight".to_string());
let theme_name = args.theme.unwrap_or("One Dark".to_string());
let asset_source = Arc::new(Assets);
gpui::App::production(asset_source).run(move |cx| {
+39 -285
View File
@@ -1,261 +1,15 @@
use gpui::{hsla, Hsla, Rgba};
use gpui::{Hsla, Rgba};
use crate::colors::{StatusColors, SystemColors, ThemeColors};
use crate::scale::{ColorScaleSet, ColorScales};
use crate::syntax::SyntaxTheme;
use crate::{ColorScale, PlayerColor, PlayerColors};
use crate::ColorScale;
use crate::{SystemColors, ThemeColors};
impl Default for PlayerColors {
fn default() -> Self {
Self(vec![
PlayerColor {
cursor: blue().dark().step_9(),
background: blue().dark().step_5(),
selection: blue().dark().step_3(),
},
PlayerColor {
cursor: orange().dark().step_9(),
background: orange().dark().step_5(),
selection: orange().dark().step_3(),
},
PlayerColor {
cursor: pink().dark().step_9(),
background: pink().dark().step_5(),
selection: pink().dark().step_3(),
},
PlayerColor {
cursor: lime().dark().step_9(),
background: lime().dark().step_5(),
selection: lime().dark().step_3(),
},
PlayerColor {
cursor: purple().dark().step_9(),
background: purple().dark().step_5(),
selection: purple().dark().step_3(),
},
PlayerColor {
cursor: amber().dark().step_9(),
background: amber().dark().step_5(),
selection: amber().dark().step_3(),
},
PlayerColor {
cursor: jade().dark().step_9(),
background: jade().dark().step_5(),
selection: jade().dark().step_3(),
},
PlayerColor {
cursor: red().dark().step_9(),
background: red().dark().step_5(),
selection: red().dark().step_3(),
},
])
}
}
impl PlayerColors {
pub fn default_light() -> Self {
Self(vec![
PlayerColor {
cursor: blue().light().step_9(),
background: blue().light().step_4(),
selection: blue().light().step_3(),
},
PlayerColor {
cursor: orange().light().step_9(),
background: orange().light().step_4(),
selection: orange().light().step_3(),
},
PlayerColor {
cursor: pink().light().step_9(),
background: pink().light().step_4(),
selection: pink().light().step_3(),
},
PlayerColor {
cursor: lime().light().step_9(),
background: lime().light().step_4(),
selection: lime().light().step_3(),
},
PlayerColor {
cursor: purple().light().step_9(),
background: purple().light().step_4(),
selection: purple().light().step_3(),
},
PlayerColor {
cursor: amber().light().step_9(),
background: amber().light().step_4(),
selection: amber().light().step_3(),
},
PlayerColor {
cursor: jade().light().step_9(),
background: jade().light().step_4(),
selection: jade().light().step_3(),
},
PlayerColor {
cursor: red().light().step_9(),
background: red().light().step_4(),
selection: red().light().step_3(),
},
])
}
}
fn neutral() -> ColorScaleSet {
pub(crate) fn neutral() -> ColorScaleSet {
slate()
}
impl Default for SystemColors {
fn default() -> Self {
Self {
transparent: hsla(0.0, 0.0, 0.0, 0.0),
mac_os_traffic_light_red: hsla(0.0139, 0.79, 0.65, 1.0),
mac_os_traffic_light_yellow: hsla(0.114, 0.88, 0.63, 1.0),
mac_os_traffic_light_green: hsla(0.313, 0.49, 0.55, 1.0),
}
}
}
impl Default for StatusColors {
fn default() -> Self {
Self {
conflict: red().dark().step_9(),
created: grass().dark().step_9(),
deleted: red().dark().step_9(),
error: red().dark().step_9(),
hidden: neutral().dark().step_9(),
ignored: neutral().dark().step_9(),
info: blue().dark().step_9(),
modified: yellow().dark().step_9(),
renamed: blue().dark().step_9(),
success: grass().dark().step_9(),
warning: yellow().dark().step_9(),
}
}
}
impl SyntaxTheme {
pub fn default_light() -> Self {
Self {
highlights: vec![
("attribute".into(), cyan().light().step_11().into()),
("boolean".into(), tomato().light().step_11().into()),
("comment".into(), neutral().light().step_11().into()),
("comment.doc".into(), iris().light().step_12().into()),
("constant".into(), red().light().step_9().into()),
("constructor".into(), red().light().step_9().into()),
("embedded".into(), red().light().step_9().into()),
("emphasis".into(), red().light().step_9().into()),
("emphasis.strong".into(), red().light().step_9().into()),
("enum".into(), red().light().step_9().into()),
("function".into(), red().light().step_9().into()),
("hint".into(), red().light().step_9().into()),
("keyword".into(), orange().light().step_11().into()),
("label".into(), red().light().step_9().into()),
("link_text".into(), red().light().step_9().into()),
("link_uri".into(), red().light().step_9().into()),
("number".into(), red().light().step_9().into()),
("operator".into(), red().light().step_9().into()),
("predictive".into(), red().light().step_9().into()),
("preproc".into(), red().light().step_9().into()),
("primary".into(), red().light().step_9().into()),
("property".into(), red().light().step_9().into()),
("punctuation".into(), neutral().light().step_11().into()),
(
"punctuation.bracket".into(),
neutral().light().step_11().into(),
),
(
"punctuation.delimiter".into(),
neutral().light().step_11().into(),
),
(
"punctuation.list_marker".into(),
blue().light().step_11().into(),
),
("punctuation.special".into(), red().light().step_9().into()),
("string".into(), jade().light().step_11().into()),
("string.escape".into(), red().light().step_9().into()),
("string.regex".into(), tomato().light().step_11().into()),
("string.special".into(), red().light().step_9().into()),
(
"string.special.symbol".into(),
red().light().step_9().into(),
),
("tag".into(), red().light().step_9().into()),
("text.literal".into(), red().light().step_9().into()),
("title".into(), red().light().step_9().into()),
("type".into(), red().light().step_9().into()),
("variable".into(), red().light().step_9().into()),
("variable.special".into(), red().light().step_9().into()),
("variant".into(), red().light().step_9().into()),
],
inlay_style: tomato().light().step_1().into(), // todo!("nate: use a proper style")
suggestion_style: orange().light().step_1().into(), // todo!("nate: use proper style")
}
}
pub fn default_dark() -> Self {
Self {
highlights: vec![
("attribute".into(), tomato().dark().step_11().into()),
("boolean".into(), tomato().dark().step_11().into()),
("comment".into(), neutral().dark().step_11().into()),
("comment.doc".into(), iris().dark().step_12().into()),
("constant".into(), orange().dark().step_11().into()),
("constructor".into(), gold().dark().step_11().into()),
("embedded".into(), red().dark().step_11().into()),
("emphasis".into(), red().dark().step_11().into()),
("emphasis.strong".into(), red().dark().step_11().into()),
("enum".into(), yellow().dark().step_11().into()),
("function".into(), blue().dark().step_11().into()),
("hint".into(), indigo().dark().step_11().into()),
("keyword".into(), plum().dark().step_11().into()),
("label".into(), red().dark().step_11().into()),
("link_text".into(), red().dark().step_11().into()),
("link_uri".into(), red().dark().step_11().into()),
("number".into(), red().dark().step_11().into()),
("operator".into(), red().dark().step_11().into()),
("predictive".into(), red().dark().step_11().into()),
("preproc".into(), red().dark().step_11().into()),
("primary".into(), red().dark().step_11().into()),
("property".into(), red().dark().step_11().into()),
("punctuation".into(), neutral().dark().step_11().into()),
(
"punctuation.bracket".into(),
neutral().dark().step_11().into(),
),
(
"punctuation.delimiter".into(),
neutral().dark().step_11().into(),
),
(
"punctuation.list_marker".into(),
blue().dark().step_11().into(),
),
("punctuation.special".into(), red().dark().step_11().into()),
("string".into(), lime().dark().step_11().into()),
("string.escape".into(), orange().dark().step_11().into()),
("string.regex".into(), tomato().dark().step_11().into()),
("string.special".into(), red().dark().step_11().into()),
(
"string.special.symbol".into(),
red().dark().step_11().into(),
),
("tag".into(), red().dark().step_11().into()),
("text.literal".into(), purple().dark().step_11().into()),
("title".into(), sky().dark().step_11().into()),
("type".into(), mint().dark().step_11().into()),
("variable".into(), red().dark().step_11().into()),
("variable.special".into(), red().dark().step_11().into()),
("variant".into(), red().dark().step_11().into()),
],
inlay_style: neutral().dark().step_11().into(), // todo!("nate: use a proper style")
suggestion_style: orange().dark().step_11().into(), // todo!("nate: use a proper style")
}
}
}
impl ThemeColors {
pub fn default_light() -> Self {
pub fn light() -> Self {
let system = SystemColors::default();
Self {
@@ -327,7 +81,7 @@ impl ThemeColors {
}
}
pub fn default_dark() -> Self {
pub fn dark() -> Self {
let system = SystemColors::default();
Self {
@@ -470,7 +224,7 @@ pub fn default_color_scales() -> ColorScales {
}
}
fn gray() -> ColorScaleSet {
pub(crate) fn gray() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Gray",
light: [
@@ -534,7 +288,7 @@ fn gray() -> ColorScaleSet {
.unwrap()
}
fn mauve() -> ColorScaleSet {
pub(crate) fn mauve() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Mauve",
light: [
@@ -598,7 +352,7 @@ fn mauve() -> ColorScaleSet {
.unwrap()
}
fn slate() -> ColorScaleSet {
pub(crate) fn slate() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Slate",
light: [
@@ -662,7 +416,7 @@ fn slate() -> ColorScaleSet {
.unwrap()
}
fn sage() -> ColorScaleSet {
pub(crate) fn sage() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Sage",
light: [
@@ -726,7 +480,7 @@ fn sage() -> ColorScaleSet {
.unwrap()
}
fn olive() -> ColorScaleSet {
pub(crate) fn olive() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Olive",
light: [
@@ -790,7 +544,7 @@ fn olive() -> ColorScaleSet {
.unwrap()
}
fn sand() -> ColorScaleSet {
pub(crate) fn sand() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Sand",
light: [
@@ -854,7 +608,7 @@ fn sand() -> ColorScaleSet {
.unwrap()
}
fn gold() -> ColorScaleSet {
pub(crate) fn gold() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Gold",
light: [
@@ -918,7 +672,7 @@ fn gold() -> ColorScaleSet {
.unwrap()
}
fn bronze() -> ColorScaleSet {
pub(crate) fn bronze() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Bronze",
light: [
@@ -982,7 +736,7 @@ fn bronze() -> ColorScaleSet {
.unwrap()
}
fn brown() -> ColorScaleSet {
pub(crate) fn brown() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Brown",
light: [
@@ -1046,7 +800,7 @@ fn brown() -> ColorScaleSet {
.unwrap()
}
fn yellow() -> ColorScaleSet {
pub(crate) fn yellow() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Yellow",
light: [
@@ -1110,7 +864,7 @@ fn yellow() -> ColorScaleSet {
.unwrap()
}
fn amber() -> ColorScaleSet {
pub(crate) fn amber() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Amber",
light: [
@@ -1174,7 +928,7 @@ fn amber() -> ColorScaleSet {
.unwrap()
}
fn orange() -> ColorScaleSet {
pub(crate) fn orange() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Orange",
light: [
@@ -1238,7 +992,7 @@ fn orange() -> ColorScaleSet {
.unwrap()
}
fn tomato() -> ColorScaleSet {
pub(crate) fn tomato() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Tomato",
light: [
@@ -1302,7 +1056,7 @@ fn tomato() -> ColorScaleSet {
.unwrap()
}
fn red() -> ColorScaleSet {
pub(crate) fn red() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Red",
light: [
@@ -1366,7 +1120,7 @@ fn red() -> ColorScaleSet {
.unwrap()
}
fn ruby() -> ColorScaleSet {
pub(crate) fn ruby() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Ruby",
light: [
@@ -1430,7 +1184,7 @@ fn ruby() -> ColorScaleSet {
.unwrap()
}
fn crimson() -> ColorScaleSet {
pub(crate) fn crimson() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Crimson",
light: [
@@ -1494,7 +1248,7 @@ fn crimson() -> ColorScaleSet {
.unwrap()
}
fn pink() -> ColorScaleSet {
pub(crate) fn pink() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Pink",
light: [
@@ -1558,7 +1312,7 @@ fn pink() -> ColorScaleSet {
.unwrap()
}
fn plum() -> ColorScaleSet {
pub(crate) fn plum() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Plum",
light: [
@@ -1622,7 +1376,7 @@ fn plum() -> ColorScaleSet {
.unwrap()
}
fn purple() -> ColorScaleSet {
pub(crate) fn purple() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Purple",
light: [
@@ -1686,7 +1440,7 @@ fn purple() -> ColorScaleSet {
.unwrap()
}
fn violet() -> ColorScaleSet {
pub(crate) fn violet() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Violet",
light: [
@@ -1750,7 +1504,7 @@ fn violet() -> ColorScaleSet {
.unwrap()
}
fn iris() -> ColorScaleSet {
pub(crate) fn iris() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Iris",
light: [
@@ -1814,7 +1568,7 @@ fn iris() -> ColorScaleSet {
.unwrap()
}
fn indigo() -> ColorScaleSet {
pub(crate) fn indigo() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Indigo",
light: [
@@ -1878,7 +1632,7 @@ fn indigo() -> ColorScaleSet {
.unwrap()
}
fn blue() -> ColorScaleSet {
pub(crate) fn blue() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Blue",
light: [
@@ -1942,7 +1696,7 @@ fn blue() -> ColorScaleSet {
.unwrap()
}
fn cyan() -> ColorScaleSet {
pub(crate) fn cyan() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Cyan",
light: [
@@ -2006,7 +1760,7 @@ fn cyan() -> ColorScaleSet {
.unwrap()
}
fn teal() -> ColorScaleSet {
pub(crate) fn teal() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Teal",
light: [
@@ -2070,7 +1824,7 @@ fn teal() -> ColorScaleSet {
.unwrap()
}
fn jade() -> ColorScaleSet {
pub(crate) fn jade() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Jade",
light: [
@@ -2134,7 +1888,7 @@ fn jade() -> ColorScaleSet {
.unwrap()
}
fn green() -> ColorScaleSet {
pub(crate) fn green() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Green",
light: [
@@ -2198,7 +1952,7 @@ fn green() -> ColorScaleSet {
.unwrap()
}
fn grass() -> ColorScaleSet {
pub(crate) fn grass() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Grass",
light: [
@@ -2262,7 +2016,7 @@ fn grass() -> ColorScaleSet {
.unwrap()
}
fn lime() -> ColorScaleSet {
pub(crate) fn lime() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Lime",
light: [
@@ -2326,7 +2080,7 @@ fn lime() -> ColorScaleSet {
.unwrap()
}
fn mint() -> ColorScaleSet {
pub(crate) fn mint() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Mint",
light: [
@@ -2390,7 +2144,7 @@ fn mint() -> ColorScaleSet {
.unwrap()
}
fn sky() -> ColorScaleSet {
pub(crate) fn sky() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Sky",
light: [
@@ -2454,7 +2208,7 @@ fn sky() -> ColorScaleSet {
.unwrap()
}
fn black() -> ColorScaleSet {
pub(crate) fn black() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Black",
light: [
@@ -2518,7 +2272,7 @@ fn black() -> ColorScaleSet {
.unwrap()
}
fn white() -> ColorScaleSet {
pub(crate) fn white() -> ColorScaleSet {
StaticColorScaleSet {
scale: "White",
light: [
+41 -43
View File
@@ -1,58 +1,56 @@
use std::sync::Arc;
use crate::{
colors::{StatusColors, SystemColors, ThemeColors, ThemeStyles},
default_color_scales, Appearance, PlayerColors, SyntaxTheme, Theme, ThemeFamily,
one_themes::{one_dark, one_family},
Theme, ThemeFamily,
};
fn zed_pro_daylight() -> Theme {
Theme {
id: "zed_pro_daylight".to_string(),
name: "Zed Pro Daylight".into(),
appearance: Appearance::Light,
styles: ThemeStyles {
system: SystemColors::default(),
colors: ThemeColors::default_light(),
status: StatusColors::default(),
player: PlayerColors::default_light(),
syntax: Arc::new(SyntaxTheme::default_light()),
},
}
}
// fn zed_pro_daylight() -> Theme {
// Theme {
// id: "zed_pro_daylight".to_string(),
// name: "Zed Pro Daylight".into(),
// appearance: Appearance::Light,
// styles: ThemeStyles {
// system: SystemColors::default(),
// colors: ThemeColors::light(),
// status: StatusColors::light(),
// player: PlayerColors::light(),
// syntax: Arc::new(SyntaxTheme::light()),
// },
// }
// }
pub(crate) fn zed_pro_moonlight() -> Theme {
Theme {
id: "zed_pro_moonlight".to_string(),
name: "Zed Pro Moonlight".into(),
appearance: Appearance::Dark,
styles: ThemeStyles {
system: SystemColors::default(),
colors: ThemeColors::default_dark(),
status: StatusColors::default(),
player: PlayerColors::default(),
syntax: Arc::new(SyntaxTheme::default_dark()),
},
}
}
// pub(crate) fn zed_pro_moonlight() -> Theme {
// Theme {
// id: "zed_pro_moonlight".to_string(),
// name: "Zed Pro Moonlight".into(),
// appearance: Appearance::Dark,
// styles: ThemeStyles {
// system: SystemColors::default(),
// colors: ThemeColors::dark(),
// status: StatusColors::dark(),
// player: PlayerColors::dark(),
// syntax: Arc::new(SyntaxTheme::dark()),
// },
// }
// }
pub fn zed_pro_family() -> ThemeFamily {
ThemeFamily {
id: "zed_pro".to_string(),
name: "Zed Pro".into(),
author: "Zed Team".into(),
themes: vec![zed_pro_daylight(), zed_pro_moonlight()],
scales: default_color_scales(),
}
}
// pub fn zed_pro_family() -> ThemeFamily {
// ThemeFamily {
// id: "zed_pro".to_string(),
// name: "Zed Pro".into(),
// author: "Zed Team".into(),
// themes: vec![zed_pro_daylight(), zed_pro_moonlight()],
// scales: default_color_scales(),
// }
// }
impl Default for ThemeFamily {
fn default() -> Self {
zed_pro_family()
one_family()
}
}
impl Default for Theme {
fn default() -> Self {
zed_pro_daylight()
one_dark()
}
}
+198
View File
@@ -0,0 +1,198 @@
use std::sync::Arc;
use gpui::{hsla, FontStyle, FontWeight, HighlightStyle};
use crate::{
default_color_scales, Appearance, PlayerColors, StatusColors, SyntaxTheme, SystemColors, Theme,
ThemeColors, ThemeFamily, ThemeStyles,
};
pub fn one_family() -> ThemeFamily {
ThemeFamily {
id: "one".to_string(),
name: "One".into(),
author: "".into(),
themes: vec![one_dark()],
scales: default_color_scales(),
}
}
pub(crate) fn one_dark() -> Theme {
let bg = hsla(215. / 360., 12. / 100., 15. / 100., 1.);
let editor = hsla(220. / 360., 12. / 100., 18. / 100., 1.);
let elevated_surface = hsla(220. / 360., 12. / 100., 18. / 100., 1.);
let blue = hsla(207.8 / 360., 81. / 100., 66. / 100., 1.0);
let gray = hsla(218.8 / 360., 10. / 100., 40. / 100., 1.0);
let green = hsla(95. / 360., 38. / 100., 62. / 100., 1.0);
let orange = hsla(29. / 360., 54. / 100., 61. / 100., 1.0);
let purple = hsla(286. / 360., 51. / 100., 64. / 100., 1.0);
let red = hsla(355. / 360., 65. / 100., 65. / 100., 1.0);
let teal = hsla(187. / 360., 47. / 100., 55. / 100., 1.0);
let yellow = hsla(39. / 360., 67. / 100., 69. / 100., 1.0);
Theme {
id: "one_dark".to_string(),
name: "One Dark".into(),
appearance: Appearance::Dark,
styles: ThemeStyles {
system: SystemColors::default(),
colors: ThemeColors {
border: hsla(225. / 360., 13. / 100., 12. / 100., 1.),
border_variant: hsla(228. / 360., 8. / 100., 25. / 100., 1.),
border_focused: hsla(223. / 360., 78. / 100., 65. / 100., 1.),
border_selected: hsla(222.6 / 360., 77.5 / 100., 65.1 / 100., 1.0),
border_transparent: SystemColors::default().transparent,
border_disabled: hsla(222.0 / 360., 11.6 / 100., 33.7 / 100., 1.0),
elevated_surface_background: elevated_surface,
surface_background: bg,
background: bg,
element_background: elevated_surface,
element_hover: hsla(225.0 / 360., 11.8 / 100., 26.7 / 100., 1.0),
element_active: hsla(220.0 / 360., 11.8 / 100., 20.0 / 100., 1.0),
element_selected: hsla(224.0 / 360., 11.3 / 100., 26.1 / 100., 1.0),
element_disabled: hsla(224.0 / 360., 11.3 / 100., 26.1 / 100., 1.0),
drop_target_background: hsla(220.0 / 360., 8.3 / 100., 21.4 / 100., 1.0),
ghost_element_background: SystemColors::default().transparent,
ghost_element_hover: hsla(225.0 / 360., 11.8 / 100., 26.7 / 100., 1.0),
ghost_element_active: hsla(220.0 / 360., 11.8 / 100., 20.0 / 100., 1.0),
ghost_element_selected: hsla(224.0 / 360., 11.3 / 100., 26.1 / 100., 1.0),
ghost_element_disabled: hsla(224.0 / 360., 11.3 / 100., 26.1 / 100., 1.0),
text: hsla(222.9 / 360., 9.1 / 100., 84.9 / 100., 1.0),
text_muted: hsla(220.0 / 360., 6.4 / 100., 45.7 / 100., 1.0),
text_placeholder: hsla(220.0 / 360., 6.6 / 100., 44.5 / 100., 1.0),
text_disabled: hsla(220.0 / 360., 6.6 / 100., 44.5 / 100., 1.0),
text_accent: hsla(222.6 / 360., 77.5 / 100., 65.1 / 100., 1.0),
icon: hsla(222.9 / 360., 9.9 / 100., 86.1 / 100., 1.0),
icon_muted: hsla(220.0 / 360., 12.1 / 100., 66.1 / 100., 1.0),
icon_disabled: hsla(220.0 / 360., 6.4 / 100., 45.7 / 100., 1.0),
icon_placeholder: hsla(220.0 / 360., 6.4 / 100., 45.7 / 100., 1.0),
icon_accent: blue.into(),
status_bar_background: bg,
title_bar_background: bg,
toolbar_background: editor,
tab_bar_background: bg,
tab_inactive_background: bg,
tab_active_background: editor,
editor_background: editor,
editor_gutter_background: editor,
editor_subheader_background: bg,
editor_active_line_background: hsla(222.9 / 360., 13.5 / 100., 20.4 / 100., 1.0),
editor_highlighted_line_background: hsla(207.8 / 360., 81. / 100., 66. / 100., 0.1),
editor_line_number: hsla(222.0 / 360., 11.5 / 100., 34.1 / 100., 1.0),
editor_active_line_number: hsla(216.0 / 360., 5.9 / 100., 49.6 / 100., 1.0),
editor_invisible: hsla(222.0 / 360., 11.5 / 100., 34.1 / 100., 1.0),
editor_wrap_guide: gpui::black(),
editor_active_wrap_guide: gpui::red(),
editor_document_highlight_read_background: hsla(
207.8 / 360.,
81. / 100.,
66. / 100.,
0.2,
),
editor_document_highlight_write_background: gpui::red(),
terminal_background: bg,
// todo!("Use one colors for terminal")
terminal_ansi_black: crate::black().dark().step_12(),
terminal_ansi_red: crate::red().dark().step_11(),
terminal_ansi_green: crate::green().dark().step_11(),
terminal_ansi_yellow: crate::yellow().dark().step_11(),
terminal_ansi_blue: crate::blue().dark().step_11(),
terminal_ansi_magenta: crate::violet().dark().step_11(),
terminal_ansi_cyan: crate::cyan().dark().step_11(),
terminal_ansi_white: crate::neutral().dark().step_12(),
terminal_ansi_bright_black: crate::black().dark().step_11(),
terminal_ansi_bright_red: crate::red().dark().step_10(),
terminal_ansi_bright_green: crate::green().dark().step_10(),
terminal_ansi_bright_yellow: crate::yellow().dark().step_10(),
terminal_ansi_bright_blue: crate::blue().dark().step_10(),
terminal_ansi_bright_magenta: crate::violet().dark().step_10(),
terminal_ansi_bright_cyan: crate::cyan().dark().step_10(),
terminal_ansi_bright_white: crate::neutral().dark().step_11(),
},
status: StatusColors {
conflict: yellow,
created: green,
deleted: red,
error: red,
hidden: gray,
hint: blue,
ignored: gray,
info: blue,
modified: yellow,
predictive: gray,
renamed: blue,
success: green,
unreachable: gray,
warning: yellow,
},
player: PlayerColors::dark(),
syntax: Arc::new(SyntaxTheme {
highlights: vec![
("attribute".into(), purple.into()),
("boolean".into(), orange.into()),
("comment".into(), gray.into()),
("comment.doc".into(), gray.into()),
("constant".into(), yellow.into()),
("constructor".into(), blue.into()),
("embedded".into(), HighlightStyle::default()),
(
"emphasis".into(),
HighlightStyle {
font_style: Some(FontStyle::Italic),
..HighlightStyle::default()
},
),
(
"emphasis.strong".into(),
HighlightStyle {
font_weight: Some(FontWeight::BOLD),
..HighlightStyle::default()
},
),
("enum".into(), HighlightStyle::default()),
("function".into(), blue.into()),
("function.method".into(), blue.into()),
("function.definition".into(), blue.into()),
("hint".into(), blue.into()),
("keyword".into(), purple.into()),
("label".into(), HighlightStyle::default()),
("link_text".into(), blue.into()),
(
"link_uri".into(),
HighlightStyle {
color: Some(teal.into()),
font_style: Some(FontStyle::Italic),
..HighlightStyle::default()
},
),
("number".into(), orange.into()),
("operator".into(), HighlightStyle::default()),
("predictive".into(), HighlightStyle::default()),
("preproc".into(), HighlightStyle::default()),
("primary".into(), HighlightStyle::default()),
("property".into(), red.into()),
("punctuation".into(), HighlightStyle::default()),
("punctuation.bracket".into(), HighlightStyle::default()),
("punctuation.delimiter".into(), HighlightStyle::default()),
("punctuation.list_marker".into(), HighlightStyle::default()),
("punctuation.special".into(), HighlightStyle::default()),
("string".into(), green.into()),
("string.escape".into(), HighlightStyle::default()),
("string.regex".into(), red.into()),
("string.special".into(), HighlightStyle::default()),
("string.special.symbol".into(), HighlightStyle::default()),
("tag".into(), HighlightStyle::default()),
("text.literal".into(), HighlightStyle::default()),
("title".into(), HighlightStyle::default()),
("type".into(), teal.into()),
("variable".into(), HighlightStyle::default()),
("variable.special".into(), red.into()),
("variant".into(), HighlightStyle::default()),
],
inlay_style: HighlightStyle::default(),
suggestion_style: HighlightStyle::default(),
}),
},
}
}
+12 -9
View File
@@ -6,8 +6,8 @@ use gpui::{HighlightStyle, SharedString};
use refineable::Refineable;
use crate::{
zed_pro_family, Appearance, PlayerColors, StatusColors, SyntaxTheme, SystemColors, Theme,
ThemeColors, ThemeFamily, ThemeStyles, UserTheme, UserThemeFamily,
one_themes::one_family, Appearance, PlayerColors, StatusColors, SyntaxTheme, SystemColors,
Theme, ThemeColors, ThemeFamily, ThemeStyles, UserTheme, UserThemeFamily,
};
pub struct ThemeRegistry {
@@ -38,17 +38,17 @@ impl ThemeRegistry {
fn insert_user_themes(&mut self, themes: impl IntoIterator<Item = UserTheme>) {
self.insert_themes(themes.into_iter().map(|user_theme| {
let mut theme_colors = match user_theme.appearance {
Appearance::Light => ThemeColors::default_light(),
Appearance::Dark => ThemeColors::default_dark(),
Appearance::Light => ThemeColors::light(),
Appearance::Dark => ThemeColors::dark(),
};
theme_colors.refine(&user_theme.styles.colors);
let mut status_colors = StatusColors::default();
let mut status_colors = StatusColors::dark();
status_colors.refine(&user_theme.styles.status);
let mut syntax_colors = match user_theme.appearance {
Appearance::Light => SyntaxTheme::default_light(),
Appearance::Dark => SyntaxTheme::default_dark(),
Appearance::Light => SyntaxTheme::light(),
Appearance::Dark => SyntaxTheme::dark(),
};
if let Some(user_syntax) = user_theme.styles.syntax {
syntax_colors.highlights = user_syntax
@@ -76,7 +76,10 @@ impl ThemeRegistry {
system: SystemColors::default(),
colors: theme_colors,
status: status_colors,
player: PlayerColors::default(),
player: match user_theme.appearance {
Appearance::Light => PlayerColors::light(),
Appearance::Dark => PlayerColors::dark(),
},
syntax: Arc::new(syntax_colors),
},
}
@@ -105,7 +108,7 @@ impl Default for ThemeRegistry {
themes: HashMap::default(),
};
this.insert_theme_families([zed_pro_family()]);
this.insert_theme_families([one_family()]);
#[cfg(not(feature = "importing-themes"))]
this.insert_user_theme_familes(crate::all_user_themes());
+2 -1
View File
@@ -1,3 +1,4 @@
use crate::one_themes::one_dark;
use crate::{Theme, ThemeRegistry};
use anyhow::Result;
use gpui::{px, AppContext, Font, FontFeatures, FontStyle, FontWeight, Pixels};
@@ -129,7 +130,7 @@ impl settings::Settings for ThemeSettings {
buffer_line_height: defaults.buffer_line_height.unwrap(),
active_theme: themes
.get(defaults.theme.as_ref().unwrap())
.or(themes.get("Zed Pro Moonlight"))
.or(themes.get(&one_dark().name))
.unwrap(),
};
+11
View File
@@ -0,0 +1,11 @@
mod colors;
mod players;
mod status;
mod syntax;
mod system;
pub use colors::*;
pub use players::*;
pub use status::*;
pub use syntax::*;
pub use system::*;
@@ -1,31 +1,8 @@
use crate::{PlayerColors, SyntaxTheme};
use gpui::Hsla;
use refineable::Refineable;
use std::sync::Arc;
#[derive(Clone)]
pub struct SystemColors {
pub transparent: Hsla,
pub mac_os_traffic_light_red: Hsla,
pub mac_os_traffic_light_yellow: Hsla,
pub mac_os_traffic_light_green: Hsla,
}
#[derive(Refineable, Clone, Debug)]
#[refineable(Debug, serde::Deserialize)]
pub struct StatusColors {
pub conflict: Hsla,
pub created: Hsla,
pub deleted: Hsla,
pub error: Hsla,
pub hidden: Hsla,
pub ignored: Hsla,
pub info: Hsla,
pub modified: Hsla,
pub renamed: Hsla,
pub success: Hsla,
pub warning: Hsla,
}
use crate::{PlayerColors, StatusColors, SyntaxTheme, SystemColors};
#[derive(Refineable, Clone, Debug)]
#[refineable(Debug, serde::Deserialize)]
@@ -259,7 +236,7 @@ mod tests {
#[test]
fn override_a_single_theme_color() {
let mut colors = ThemeColors::default_light();
let mut colors = ThemeColors::light();
let magenta: Hsla = gpui::rgb(0xff00ff);
@@ -277,7 +254,7 @@ mod tests {
#[test]
fn override_multiple_theme_colors() {
let mut colors = ThemeColors::default_light();
let mut colors = ThemeColors::light();
let magenta: Hsla = gpui::rgb(0xff00ff);
let green: Hsla = gpui::rgb(0x00ff00);
@@ -16,6 +16,107 @@ pub struct PlayerColor {
#[derive(Clone)]
pub struct PlayerColors(pub Vec<PlayerColor>);
impl Default for PlayerColors {
/// Don't use this!
/// We have to have a default to be `[refineable::Refinable]`.
/// todo!("Find a way to not need this for Refinable")
fn default() -> Self {
Self::dark()
}
}
impl PlayerColors {
pub fn dark() -> Self {
Self(vec![
PlayerColor {
cursor: blue().dark().step_9(),
background: blue().dark().step_5(),
selection: blue().dark().step_3(),
},
PlayerColor {
cursor: orange().dark().step_9(),
background: orange().dark().step_5(),
selection: orange().dark().step_3(),
},
PlayerColor {
cursor: pink().dark().step_9(),
background: pink().dark().step_5(),
selection: pink().dark().step_3(),
},
PlayerColor {
cursor: lime().dark().step_9(),
background: lime().dark().step_5(),
selection: lime().dark().step_3(),
},
PlayerColor {
cursor: purple().dark().step_9(),
background: purple().dark().step_5(),
selection: purple().dark().step_3(),
},
PlayerColor {
cursor: amber().dark().step_9(),
background: amber().dark().step_5(),
selection: amber().dark().step_3(),
},
PlayerColor {
cursor: jade().dark().step_9(),
background: jade().dark().step_5(),
selection: jade().dark().step_3(),
},
PlayerColor {
cursor: red().dark().step_9(),
background: red().dark().step_5(),
selection: red().dark().step_3(),
},
])
}
pub fn light() -> Self {
Self(vec![
PlayerColor {
cursor: blue().light().step_9(),
background: blue().light().step_4(),
selection: blue().light().step_3(),
},
PlayerColor {
cursor: orange().light().step_9(),
background: orange().light().step_4(),
selection: orange().light().step_3(),
},
PlayerColor {
cursor: pink().light().step_9(),
background: pink().light().step_4(),
selection: pink().light().step_3(),
},
PlayerColor {
cursor: lime().light().step_9(),
background: lime().light().step_4(),
selection: lime().light().step_3(),
},
PlayerColor {
cursor: purple().light().step_9(),
background: purple().light().step_4(),
selection: purple().light().step_3(),
},
PlayerColor {
cursor: amber().light().step_9(),
background: amber().light().step_4(),
selection: amber().light().step_3(),
},
PlayerColor {
cursor: jade().light().step_9(),
background: jade().light().step_4(),
selection: jade().light().step_3(),
},
PlayerColor {
cursor: red().light().step_9(),
background: red().light().step_4(),
selection: red().light().step_3(),
},
])
}
}
impl PlayerColors {
pub fn local(&self) -> PlayerColor {
// todo!("use a valid color");
@@ -36,6 +137,8 @@ impl PlayerColors {
#[cfg(feature = "stories")]
pub use stories::*;
use crate::{amber, blue, jade, lime, orange, pink, purple, red};
#[cfg(feature = "stories")]
mod stories {
use super::*;
+134
View File
@@ -0,0 +1,134 @@
use gpui::Hsla;
use refineable::Refineable;
use crate::{blue, grass, neutral, red, yellow};
#[derive(Refineable, Clone, Debug)]
#[refineable(Debug, serde::Deserialize)]
pub struct StatusColors {
/// Indicates some kind of conflict, like a file changed on disk while it was open, or
/// merge conflicts in a Git repository.
pub conflict: Hsla,
/// Indicates something new, like a new file added to a Git repository.
pub created: Hsla,
/// Indicates that something no longer exists, like a deleted file.
pub deleted: Hsla,
/// Indicates a system error, a failed operation or a diagnostic error.
pub error: Hsla,
/// Represents a hidden status, such as a file being hidden in a file tree.
pub hidden: Hsla,
/// Indicates a hint or some kind of additional information.
pub hint: Hsla,
/// Indicates that something is deliberately ignored, such as a file or operation ignored by Git.
pub ignored: Hsla,
/// Represents informational status updates or messages.
pub info: Hsla,
/// Indicates a changed or altered status, like a file that has been edited.
pub modified: Hsla,
/// Indicates something that is predicted, like automatic code completion, or generated code.
pub predictive: Hsla,
/// Represents a renamed status, such as a file that has been renamed.
pub renamed: Hsla,
/// Indicates a successful operation or task completion.
pub success: Hsla,
/// Indicates some kind of unreachable status, like a block of code that can never be reached.
pub unreachable: Hsla,
/// Represents a warning status, like an operation that is about to fail.
pub warning: Hsla,
}
impl Default for StatusColors {
/// Don't use this!
/// We have to have a default to be `[refineable::Refinable]`.
/// todo!("Find a way to not need this for Refinable")
fn default() -> Self {
Self::dark()
}
}
pub struct DiagnosticColors {
pub error: Hsla,
pub warning: Hsla,
pub info: Hsla,
}
pub struct GitStatusColors {
pub created: Hsla,
pub deleted: Hsla,
pub modified: Hsla,
pub renamed: Hsla,
pub conflict: Hsla,
pub ignored: Hsla,
}
impl StatusColors {
pub fn dark() -> Self {
Self {
conflict: red().dark().step_9(),
created: grass().dark().step_9(),
deleted: red().dark().step_9(),
error: red().dark().step_9(),
hidden: neutral().dark().step_9(),
hint: blue().dark().step_9(),
ignored: neutral().dark().step_9(),
info: blue().dark().step_9(),
modified: yellow().dark().step_9(),
predictive: neutral().dark_alpha().step_9(),
renamed: blue().dark().step_9(),
success: grass().dark().step_9(),
unreachable: neutral().dark().step_10(),
warning: yellow().dark().step_9(),
}
}
pub fn light() -> Self {
Self {
conflict: red().light().step_9(),
created: grass().light().step_9(),
deleted: red().light().step_9(),
error: red().light().step_9(),
hidden: neutral().light().step_9(),
hint: blue().light().step_9(),
ignored: neutral().light().step_9(),
info: blue().light().step_9(),
modified: yellow().light().step_9(),
predictive: neutral().light_alpha().step_9(),
renamed: blue().light().step_9(),
success: grass().light().step_9(),
unreachable: neutral().light().step_10(),
warning: yellow().light().step_9(),
}
}
pub fn diagnostic(&self) -> DiagnosticColors {
DiagnosticColors {
error: self.error,
warning: self.warning,
info: self.info,
}
}
pub fn git(&self) -> GitStatusColors {
GitStatusColors {
created: self.created,
deleted: self.deleted,
modified: self.modified,
renamed: self.renamed,
conflict: self.conflict,
ignored: self.ignored,
}
}
}
+170
View File
@@ -0,0 +1,170 @@
use gpui::{HighlightStyle, Hsla};
use crate::{
blue, cyan, gold, indigo, iris, jade, lime, mint, neutral, orange, plum, purple, red, sky,
tomato, yellow,
};
#[derive(Clone, Default)]
pub struct SyntaxTheme {
pub highlights: Vec<(String, HighlightStyle)>,
// todo!("Remove this in favor of StatusColor.hint")
// If this should be overridable we should move it to ThemeColors
pub inlay_style: HighlightStyle,
// todo!("Remove this in favor of StatusColor.prediction")
// If this should be overridable we should move it to ThemeColors
pub suggestion_style: HighlightStyle,
}
impl SyntaxTheme {
pub fn light() -> Self {
Self {
highlights: vec![
("attribute".into(), cyan().light().step_11().into()),
("boolean".into(), tomato().light().step_11().into()),
("comment".into(), neutral().light().step_11().into()),
("comment.doc".into(), iris().light().step_12().into()),
("constant".into(), red().light().step_9().into()),
("constructor".into(), red().light().step_9().into()),
("embedded".into(), red().light().step_9().into()),
("emphasis".into(), red().light().step_9().into()),
("emphasis.strong".into(), red().light().step_9().into()),
("enum".into(), red().light().step_9().into()),
("function".into(), red().light().step_9().into()),
("hint".into(), red().light().step_9().into()),
("keyword".into(), orange().light().step_11().into()),
("label".into(), red().light().step_9().into()),
("link_text".into(), red().light().step_9().into()),
("link_uri".into(), red().light().step_9().into()),
("number".into(), red().light().step_9().into()),
("operator".into(), red().light().step_9().into()),
("predictive".into(), red().light().step_9().into()),
("preproc".into(), red().light().step_9().into()),
("primary".into(), red().light().step_9().into()),
("property".into(), red().light().step_9().into()),
("punctuation".into(), neutral().light().step_11().into()),
(
"punctuation.bracket".into(),
neutral().light().step_11().into(),
),
(
"punctuation.delimiter".into(),
neutral().light().step_11().into(),
),
(
"punctuation.list_marker".into(),
blue().light().step_11().into(),
),
("punctuation.special".into(), red().light().step_9().into()),
("string".into(), jade().light().step_11().into()),
("string.escape".into(), red().light().step_9().into()),
("string.regex".into(), tomato().light().step_11().into()),
("string.special".into(), red().light().step_9().into()),
(
"string.special.symbol".into(),
red().light().step_9().into(),
),
("tag".into(), red().light().step_9().into()),
("text.literal".into(), red().light().step_9().into()),
("title".into(), red().light().step_9().into()),
("type".into(), red().light().step_9().into()),
("variable".into(), red().light().step_9().into()),
("variable.special".into(), red().light().step_9().into()),
("variant".into(), red().light().step_9().into()),
],
inlay_style: tomato().light().step_1().into(), // todo!("nate: use a proper style")
suggestion_style: orange().light().step_1().into(), // todo!("nate: use proper style")
}
}
pub fn dark() -> Self {
Self {
highlights: vec![
("attribute".into(), tomato().dark().step_11().into()),
("boolean".into(), tomato().dark().step_11().into()),
("comment".into(), neutral().dark().step_11().into()),
("comment.doc".into(), iris().dark().step_12().into()),
("constant".into(), orange().dark().step_11().into()),
("constructor".into(), gold().dark().step_11().into()),
("embedded".into(), red().dark().step_11().into()),
("emphasis".into(), red().dark().step_11().into()),
("emphasis.strong".into(), red().dark().step_11().into()),
("enum".into(), yellow().dark().step_11().into()),
("function".into(), blue().dark().step_11().into()),
("hint".into(), indigo().dark().step_11().into()),
("keyword".into(), plum().dark().step_11().into()),
("label".into(), red().dark().step_11().into()),
("link_text".into(), red().dark().step_11().into()),
("link_uri".into(), red().dark().step_11().into()),
("number".into(), red().dark().step_11().into()),
("operator".into(), red().dark().step_11().into()),
("predictive".into(), red().dark().step_11().into()),
("preproc".into(), red().dark().step_11().into()),
("primary".into(), red().dark().step_11().into()),
("property".into(), red().dark().step_11().into()),
("punctuation".into(), neutral().dark().step_11().into()),
(
"punctuation.bracket".into(),
neutral().dark().step_11().into(),
),
(
"punctuation.delimiter".into(),
neutral().dark().step_11().into(),
),
(
"punctuation.list_marker".into(),
blue().dark().step_11().into(),
),
("punctuation.special".into(), red().dark().step_11().into()),
("string".into(), lime().dark().step_11().into()),
("string.escape".into(), orange().dark().step_11().into()),
("string.regex".into(), tomato().dark().step_11().into()),
("string.special".into(), red().dark().step_11().into()),
(
"string.special.symbol".into(),
red().dark().step_11().into(),
),
("tag".into(), red().dark().step_11().into()),
("text.literal".into(), purple().dark().step_11().into()),
("title".into(), sky().dark().step_11().into()),
("type".into(), mint().dark().step_11().into()),
("variable".into(), red().dark().step_11().into()),
("variable.special".into(), red().dark().step_11().into()),
("variant".into(), red().dark().step_11().into()),
],
inlay_style: neutral().dark().step_11().into(), // todo!("nate: use a proper style")
suggestion_style: orange().dark().step_11().into(), // todo!("nate: use a proper style")
}
}
// TOOD: Get this working with `#[cfg(test)]`. Why isn't it?
pub fn new_test(colors: impl IntoIterator<Item = (&'static str, Hsla)>) -> Self {
SyntaxTheme {
highlights: colors
.into_iter()
.map(|(key, color)| {
(
key.to_owned(),
HighlightStyle {
color: Some(color),
..Default::default()
},
)
})
.collect(),
inlay_style: HighlightStyle::default(),
suggestion_style: HighlightStyle::default(),
}
}
pub fn get(&self, name: &str) -> HighlightStyle {
self.highlights
.iter()
.find_map(|entry| if entry.0 == name { Some(entry.1) } else { None })
.unwrap_or_default()
}
pub fn color(&self, name: &str) -> Hsla {
self.get(name).color.unwrap_or_default()
}
}
+20
View File
@@ -0,0 +1,20 @@
use gpui::{hsla, Hsla};
#[derive(Clone)]
pub struct SystemColors {
pub transparent: Hsla,
pub mac_os_traffic_light_red: Hsla,
pub mac_os_traffic_light_yellow: Hsla,
pub mac_os_traffic_light_green: Hsla,
}
impl Default for SystemColors {
fn default() -> Self {
Self {
transparent: hsla(0.0, 0.0, 0.0, 0.0),
mac_os_traffic_light_red: hsla(0.0139, 0.79, 0.65, 1.0),
mac_os_traffic_light_yellow: hsla(0.114, 0.88, 0.63, 1.0),
mac_os_traffic_light_green: hsla(0.313, 0.49, 0.55, 1.0),
}
}
}
-41
View File
@@ -1,41 +0,0 @@
use gpui::{HighlightStyle, Hsla};
#[derive(Clone, Default)]
pub struct SyntaxTheme {
pub highlights: Vec<(String, HighlightStyle)>,
pub inlay_style: HighlightStyle,
pub suggestion_style: HighlightStyle,
}
impl SyntaxTheme {
// TOOD: Get this working with `#[cfg(test)]`. Why isn't it?
pub fn new_test(colors: impl IntoIterator<Item = (&'static str, Hsla)>) -> Self {
SyntaxTheme {
highlights: colors
.into_iter()
.map(|(key, color)| {
(
key.to_owned(),
HighlightStyle {
color: Some(color),
..Default::default()
},
)
})
.collect(),
inlay_style: HighlightStyle::default(),
suggestion_style: HighlightStyle::default(),
}
}
pub fn get(&self, name: &str) -> HighlightStyle {
self.highlights
.iter()
.find_map(|entry| if entry.0 == name { Some(entry.1) } else { None })
.unwrap_or_default()
}
pub fn color(&self, name: &str) -> Hsla {
self.get(name).color.unwrap_or_default()
}
}
+3 -6
View File
@@ -1,11 +1,10 @@
mod colors;
mod default_colors;
mod default_theme;
mod players;
mod one_themes;
mod registry;
mod scale;
mod settings;
mod syntax;
mod styles;
#[cfg(not(feature = "importing-themes"))]
mod themes;
mod user_theme;
@@ -13,14 +12,12 @@ mod user_theme;
use std::sync::Arc;
use ::settings::Settings;
pub use colors::*;
pub use default_colors::*;
pub use default_theme::*;
pub use players::*;
pub use registry::*;
pub use scale::*;
pub use settings::*;
pub use syntax::*;
pub use styles::*;
#[cfg(not(feature = "importing-themes"))]
pub use themes::*;
pub use user_theme::*;
+35
View File
@@ -0,0 +1,35 @@
import colorsys
import sys
def hex_to_rgb(hex):
hex = hex.lstrip('#')
if len(hex) == 8: # 8 digit hex color
r, g, b, a = (int(hex[i:i+2], 16) for i in (0, 2, 4, 6))
return r, g, b, a / 255.0
else: # 6 digit hex color
return tuple(int(hex[i:i+2], 16) for i in (0, 2, 4)) + (1.0,)
def rgb_to_hsla(rgb):
h, l, s = colorsys.rgb_to_hls(rgb[0]/255.0, rgb[1]/255.0, rgb[2]/255.0)
a = rgb[3] # alpha value
return (round(h * 360, 1), round(s * 100, 1), round(l * 100, 1), round(a, 3))
def hex_to_hsla(hex):
return rgb_to_hsla(hex_to_rgb(hex))
if len(sys.argv) != 2:
print("Usage: python util/hex_to_hsla.py <6 or 8 digit hex color or comma-separated list of colors>")
else:
input_arg = sys.argv[1]
if ',' in input_arg: # comma-separated list of colors
hex_colors = input_arg.split(',')
hslas = [] # output array
for hex_color in hex_colors:
hex_color = hex_color.strip("'\" ")
h, s, l, a = hex_to_hsla(hex_color)
hslas.append(f"hsla({h} / 360., {s} / 100., {l} / 100., {a})")
print(hslas)
else: # single color
hex_color = input_arg.strip("'\"")
h, s, l, a = hex_to_hsla(hex_color)
print(f"hsla({h} / 360., {s} / 100., {l} / 100., {a})")
+38 -119
View File
@@ -1,50 +1,42 @@
use std::collections::HashSet;
use strum::{EnumIter, IntoEnumIterator};
use gpui::Action;
use strum::EnumIter;
use crate::prelude::*;
#[derive(Component)]
pub struct Keybinding {
pub struct KeyBinding {
/// A keybinding consists of a key and a set of modifier keys.
/// More then one keybinding produces a chord.
///
/// This should always contain at least one element.
keybinding: Vec<(String, ModifierKeys)>,
key_binding: gpui::KeyBinding,
}
impl Keybinding {
pub fn new(key: String, modifiers: ModifierKeys) -> Self {
Self {
keybinding: vec![(key, modifiers)],
}
impl KeyBinding {
pub fn for_action(action: &dyn Action, cx: &mut WindowContext) -> Option<Self> {
// todo! this last is arbitrary, we want to prefer users key bindings over defaults,
// and vim over normal (in vim mode), etc.
let key_binding = cx.bindings_for_action(action).last().cloned()?;
Some(Self::new(key_binding))
}
pub fn new_chord(
first_note: (String, ModifierKeys),
second_note: (String, ModifierKeys),
) -> Self {
Self {
keybinding: vec![first_note, second_note],
}
pub fn new(key_binding: gpui::KeyBinding) -> Self {
Self { key_binding }
}
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
div()
.flex()
.gap_2()
.children(self.keybinding.iter().map(|(key, modifiers)| {
.children(self.key_binding.keystrokes().iter().map(|keystroke| {
div()
.flex()
.gap_1()
.children(ModifierKey::iter().filter_map(|modifier| {
if modifiers.0.contains(&modifier) {
Some(Key::new(modifier.glyph().to_string()))
} else {
None
}
}))
.child(Key::new(key.clone()))
.when(keystroke.modifiers.control, |el| el.child(Key::new("^")))
.when(keystroke.modifiers.alt, |el| el.child(Key::new("")))
.when(keystroke.modifiers.command, |el| el.child(Key::new("")))
.when(keystroke.modifiers.shift, |el| el.child(Key::new("")))
.child(Key::new(keystroke.key.clone()))
}))
}
}
@@ -81,76 +73,6 @@ pub enum ModifierKey {
Shift,
}
impl ModifierKey {
/// Returns the glyph for the [`ModifierKey`].
pub fn glyph(&self) -> char {
match self {
Self::Control => '^',
Self::Alt => '⌥',
Self::Command => '⌘',
Self::Shift => '⇧',
}
}
}
#[derive(Clone)]
pub struct ModifierKeys(HashSet<ModifierKey>);
impl ModifierKeys {
pub fn new() -> Self {
Self(HashSet::new())
}
pub fn all() -> Self {
Self(HashSet::from_iter(ModifierKey::iter()))
}
pub fn add(mut self, modifier: ModifierKey) -> Self {
self.0.insert(modifier);
self
}
pub fn control(mut self, control: bool) -> Self {
if control {
self.0.insert(ModifierKey::Control);
} else {
self.0.remove(&ModifierKey::Control);
}
self
}
pub fn alt(mut self, alt: bool) -> Self {
if alt {
self.0.insert(ModifierKey::Alt);
} else {
self.0.remove(&ModifierKey::Alt);
}
self
}
pub fn command(mut self, command: bool) -> Self {
if command {
self.0.insert(ModifierKey::Command);
} else {
self.0.remove(&ModifierKey::Command);
}
self
}
pub fn shift(mut self, shift: bool) -> Self {
if shift {
self.0.insert(ModifierKey::Shift);
} else {
self.0.remove(&ModifierKey::Shift);
}
self
}
}
#[cfg(feature = "stories")]
pub use stories::*;
@@ -158,29 +80,38 @@ pub use stories::*;
mod stories {
use super::*;
use crate::Story;
use gpui::{Div, Render};
use gpui::{action, Div, Render};
use itertools::Itertools;
pub struct KeybindingStory;
#[action]
struct NoAction {}
pub fn binding(key: &str) -> gpui::KeyBinding {
gpui::KeyBinding::new(key, NoAction {}, None)
}
impl Render for KeybindingStory {
type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
let all_modifier_permutations = ModifierKey::iter().permutations(2);
let all_modifier_permutations =
["ctrl", "alt", "cmd", "shift"].into_iter().permutations(2);
Story::container(cx)
.child(Story::title_for::<_, Keybinding>(cx))
.child(Story::title_for::<_, KeyBinding>(cx))
.child(Story::label(cx, "Single Key"))
.child(Keybinding::new("Z".to_string(), ModifierKeys::new()))
.child(KeyBinding::new(binding("Z")))
.child(Story::label(cx, "Single Key with Modifier"))
.child(
div()
.flex()
.gap_3()
.children(ModifierKey::iter().map(|modifier| {
Keybinding::new("C".to_string(), ModifierKeys::new().add(modifier))
})),
.child(KeyBinding::new(binding("ctrl-c")))
.child(KeyBinding::new(binding("alt-c")))
.child(KeyBinding::new(binding("cmd-c")))
.child(KeyBinding::new(binding("shift-c"))),
)
.child(Story::label(cx, "Single Key with Modifier (Permuted)"))
.child(
@@ -194,29 +125,17 @@ mod stories {
.gap_4()
.py_3()
.children(chunk.map(|permutation| {
let mut modifiers = ModifierKeys::new();
for modifier in permutation {
modifiers = modifiers.add(modifier);
}
Keybinding::new("X".to_string(), modifiers)
KeyBinding::new(binding(&*(permutation.join("-") + "-x")))
}))
}),
),
)
.child(Story::label(cx, "Single Key with All Modifiers"))
.child(Keybinding::new("Z".to_string(), ModifierKeys::all()))
.child(KeyBinding::new(binding("ctrl-alt-cmd-shift-z")))
.child(Story::label(cx, "Chord"))
.child(Keybinding::new_chord(
("A".to_string(), ModifierKeys::new()),
("Z".to_string(), ModifierKeys::new()),
))
.child(KeyBinding::new(binding("a z")))
.child(Story::label(cx, "Chord with Modifier"))
.child(Keybinding::new_chord(
("A".to_string(), ModifierKeys::new().control(true)),
("Z".to_string(), ModifierKeys::new().shift(true)),
))
.child(KeyBinding::new(binding("ctrl-a shift-z")))
}
}
}
+16 -38
View File
@@ -1,5 +1,5 @@
use crate::prelude::*;
use crate::{h_stack, v_stack, Keybinding, Label, LabelColor};
use crate::{h_stack, v_stack, KeyBinding, Label, LabelColor};
#[derive(Component)]
pub struct Palette {
@@ -108,7 +108,7 @@ impl Palette {
pub struct PaletteItem {
pub label: SharedString,
pub sublabel: Option<SharedString>,
pub keybinding: Option<Keybinding>,
pub keybinding: Option<KeyBinding>,
}
impl PaletteItem {
@@ -132,7 +132,7 @@ impl PaletteItem {
pub fn keybinding<K>(mut self, keybinding: K) -> Self
where
K: Into<Option<Keybinding>>,
K: Into<Option<KeyBinding>>,
{
self.keybinding = keybinding.into();
self
@@ -161,7 +161,7 @@ pub use stories::*;
mod stories {
use gpui::{Div, Render};
use crate::{ModifierKeys, Story};
use crate::{binding, Story};
use super::*;
@@ -181,46 +181,24 @@ mod stories {
Palette::new("palette-2")
.placeholder("Execute a command...")
.items(vec![
PaletteItem::new("theme selector: toggle").keybinding(
Keybinding::new_chord(
("k".to_string(), ModifierKeys::new().command(true)),
("t".to_string(), ModifierKeys::new().command(true)),
),
),
PaletteItem::new("assistant: inline assist").keybinding(
Keybinding::new(
"enter".to_string(),
ModifierKeys::new().command(true),
),
),
PaletteItem::new("assistant: quote selection").keybinding(
Keybinding::new(
">".to_string(),
ModifierKeys::new().command(true),
),
),
PaletteItem::new("assistant: toggle focus").keybinding(
Keybinding::new(
"?".to_string(),
ModifierKeys::new().command(true),
),
),
PaletteItem::new("theme selector: toggle")
.keybinding(KeyBinding::new(binding("cmd-k cmd-t"))),
PaletteItem::new("assistant: inline assist")
.keybinding(KeyBinding::new(binding("cmd-enter"))),
PaletteItem::new("assistant: quote selection")
.keybinding(KeyBinding::new(binding("cmd-<"))),
PaletteItem::new("assistant: toggle focus")
.keybinding(KeyBinding::new(binding("cmd-?"))),
PaletteItem::new("auto update: check"),
PaletteItem::new("auto update: view release notes"),
PaletteItem::new("branches: open recent").keybinding(
Keybinding::new(
"b".to_string(),
ModifierKeys::new().command(true).alt(true),
),
),
PaletteItem::new("branches: open recent")
.keybinding(KeyBinding::new(binding("cmd-alt-b"))),
PaletteItem::new("chat panel: toggle focus"),
PaletteItem::new("cli: install"),
PaletteItem::new("client: sign in"),
PaletteItem::new("client: sign out"),
PaletteItem::new("editor: cancel").keybinding(Keybinding::new(
"escape".to_string(),
ModifierKeys::new(),
)),
PaletteItem::new("editor: cancel")
.keybinding(KeyBinding::new(binding("escape"))),
]),
)
}
+7 -8
View File
@@ -1,6 +1,8 @@
use gpui::{div, Div, ParentElement, Render, SharedString, Styled, ViewContext};
use theme2::ActiveTheme;
use crate::StyledExt;
#[derive(Clone, Debug)]
pub struct TextTooltip {
title: SharedString,
@@ -16,16 +18,13 @@ impl Render for TextTooltip {
type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
let theme = cx.theme();
div()
.bg(theme.colors().background)
.rounded_lg()
.border()
.elevation_2(cx)
.font("Zed Sans")
.border_color(theme.colors().border)
.text_color(theme.colors().text)
.pl_2()
.pr_2()
.text_ui()
.text_color(cx.theme().colors().text)
.py_1()
.px_2()
.child(self.title.clone())
}
}
+14 -44
View File
@@ -7,12 +7,12 @@ use gpui::{AppContext, ViewContext};
use rand::Rng;
use theme2::ActiveTheme;
use crate::HighlightedText;
use crate::{binding, HighlightedText};
use crate::{
Buffer, BufferRow, BufferRows, Button, EditorPane, FileSystemStatus, GitStatus,
HighlightedLine, Icon, Keybinding, Label, LabelColor, ListEntry, ListEntrySize, Livestream,
MicStatus, ModifierKeys, Notification, PaletteItem, Player, PlayerCallStatus,
PlayerWithCallStatus, PublicPlayer, ScreenShareStatus, Symbol, Tab, Toggle, VideoStatus,
HighlightedLine, Icon, KeyBinding, Label, LabelColor, ListEntry, ListEntrySize, Livestream,
MicStatus, Notification, PaletteItem, Player, PlayerCallStatus, PlayerWithCallStatus,
PublicPlayer, ScreenShareStatus, Symbol, Tab, Toggle, VideoStatus,
};
use crate::{ListItem, NotificationAction};
@@ -701,46 +701,16 @@ pub fn static_collab_panel_channels() -> Vec<ListItem> {
pub fn example_editor_actions() -> Vec<PaletteItem> {
vec![
PaletteItem::new("New File").keybinding(Keybinding::new(
"N".to_string(),
ModifierKeys::new().command(true),
)),
PaletteItem::new("Open File").keybinding(Keybinding::new(
"O".to_string(),
ModifierKeys::new().command(true),
)),
PaletteItem::new("Save File").keybinding(Keybinding::new(
"S".to_string(),
ModifierKeys::new().command(true),
)),
PaletteItem::new("Cut").keybinding(Keybinding::new(
"X".to_string(),
ModifierKeys::new().command(true),
)),
PaletteItem::new("Copy").keybinding(Keybinding::new(
"C".to_string(),
ModifierKeys::new().command(true),
)),
PaletteItem::new("Paste").keybinding(Keybinding::new(
"V".to_string(),
ModifierKeys::new().command(true),
)),
PaletteItem::new("Undo").keybinding(Keybinding::new(
"Z".to_string(),
ModifierKeys::new().command(true),
)),
PaletteItem::new("Redo").keybinding(Keybinding::new(
"Z".to_string(),
ModifierKeys::new().command(true).shift(true),
)),
PaletteItem::new("Find").keybinding(Keybinding::new(
"F".to_string(),
ModifierKeys::new().command(true),
)),
PaletteItem::new("Replace").keybinding(Keybinding::new(
"R".to_string(),
ModifierKeys::new().command(true),
)),
PaletteItem::new("New File").keybinding(KeyBinding::new(binding("cmd-n"))),
PaletteItem::new("Open File").keybinding(KeyBinding::new(binding("cmd-o"))),
PaletteItem::new("Save File").keybinding(KeyBinding::new(binding("cmd-s"))),
PaletteItem::new("Cut").keybinding(KeyBinding::new(binding("cmd-x"))),
PaletteItem::new("Copy").keybinding(KeyBinding::new(binding("cmd-c"))),
PaletteItem::new("Paste").keybinding(KeyBinding::new(binding("cmd-v"))),
PaletteItem::new("Undo").keybinding(KeyBinding::new(binding("cmd-z"))),
PaletteItem::new("Redo").keybinding(KeyBinding::new(binding("cmd-shift-z"))),
PaletteItem::new("Find").keybinding(KeyBinding::new(binding("cmd-f"))),
PaletteItem::new("Replace").keybinding(KeyBinding::new(binding("cmd-r"))),
PaletteItem::new("Jump to Line"),
PaletteItem::new("Select All"),
PaletteItem::new("Deselect All"),
+2 -2
View File
@@ -1,4 +1,4 @@
use gpui::{Div, ElementFocus, ElementInteractivity, Styled, UniformList, ViewContext};
use gpui::{Div, ElementInteractivity, KeyDispatch, Styled, UniformList, ViewContext};
use theme2::ActiveTheme;
use crate::{ElevationIndex, UITextSize};
@@ -96,7 +96,7 @@ pub trait StyledExt: Styled + Sized {
impl<V, I, F> StyledExt for Div<V, I, F>
where
I: ElementInteractivity<V>,
F: ElementFocus<V>,
F: KeyDispatch<V>,
{
}
+18 -6
View File
@@ -1401,20 +1401,32 @@ impl Pane {
// .on_drop(|_view, state: View<DraggedTab>, cx| {
// eprintln!("{:?}", state.read(cx));
// })
.px_2()
.py_0p5()
.flex()
.items_center()
.justify_center()
// todo!("Nate - I need to do some work to balance all the items in the tab once things stablize")
.map(|this| {
if close_right {
this.pl_3().pr_1()
} else {
this.pr_1().pr_3()
}
})
.py_1()
.bg(tab_bg)
.hover(|h| h.bg(tab_hover_bg))
.active(|a| a.bg(tab_active_bg))
.border_color(cx.theme().colors().border)
.map(|this| match ix.cmp(&self.active_item_index) {
cmp::Ordering::Less => this.border_l(),
cmp::Ordering::Equal => this.border_r(),
cmp::Ordering::Greater => this.border_l().border_r(),
})
// .hover(|h| h.bg(tab_hover_bg))
// .active(|a| a.bg(tab_active_bg))
.child(
div()
.px_1()
.flex()
.items_center()
.gap_1p5()
.gap_1()
.text_color(text_color)
.children(if item.has_conflict(cx) {
Some(
+157 -156
View File
@@ -37,10 +37,10 @@ use futures::{
};
use gpui::{
actions, div, point, rems, size, Action, AnyModel, AnyView, AnyWeakView, AppContext,
AsyncAppContext, AsyncWindowContext, Bounds, Component, DispatchContext, Div, Entity, EntityId,
EventEmitter, FocusHandle, GlobalPixels, Model, ModelContext, ParentElement, Point, Render,
Size, StatefulInteractive, StatefulInteractivity, StatelessInteractive, Styled, Subscription,
Task, View, ViewContext, VisualContext, WeakView, WindowBounds, WindowContext, WindowHandle,
AsyncAppContext, AsyncWindowContext, Bounds, Component, Div, Entity, EntityId, EventEmitter,
FocusHandle, GlobalPixels, KeyContext, Model, ModelContext, ParentElement, Point, Render, Size,
StatefulInteractive, StatelessInteractive, StatelessInteractivity, Styled, Subscription, Task,
View, ViewContext, VisualContext, WeakView, WindowBounds, WindowContext, WindowHandle,
WindowOptions,
};
use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings, ProjectItem};
@@ -534,8 +534,8 @@ pub struct Workspace {
workspace_actions: Vec<
Box<
dyn Fn(
Div<Workspace, StatefulInteractivity<Workspace>>,
) -> Div<Workspace, StatefulInteractivity<Workspace>>,
Div<Workspace, StatelessInteractivity<Workspace>>,
) -> Div<Workspace, StatelessInteractivity<Workspace>>,
>,
>,
zoomed: Option<AnyWeakView>,
@@ -3517,8 +3517,8 @@ impl Workspace {
fn add_workspace_actions_listeners(
&self,
mut div: Div<Workspace, StatefulInteractivity<Workspace>>,
) -> Div<Workspace, StatefulInteractivity<Workspace>> {
mut div: Div<Workspace, StatelessInteractivity<Workspace>>,
) -> Div<Workspace, StatelessInteractivity<Workspace>> {
for action in self.workspace_actions.iter() {
div = (action)(div)
}
@@ -3746,158 +3746,159 @@ impl Render for Workspace {
type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
let mut context = DispatchContext::default();
context.insert("Workspace");
cx.with_key_dispatch_context(context, |cx| {
div()
.relative()
.size_full()
.flex()
.flex_col()
.font("Zed Sans")
.gap_0()
.justify_start()
.items_start()
.text_color(cx.theme().colors().text)
.bg(cx.theme().colors().background)
.child(self.render_titlebar(cx))
.child(
// todo! should this be a component a view?
self.add_workspace_actions_listeners(div().id("workspace"))
.relative()
.flex_1()
.w_full()
.flex()
.overflow_hidden()
.border_t()
.border_b()
.border_color(cx.theme().colors().border)
.child(self.modal_layer.clone())
// .children(
// Some(
// Panel::new("project-panel-outer", cx)
// .side(PanelSide::Left)
// .child(ProjectPanel::new("project-panel-inner")),
// )
// .filter(|_| self.is_project_panel_open()),
// )
// .children(
// Some(
// Panel::new("collab-panel-outer", cx)
// .child(CollabPanel::new("collab-panel-inner"))
// .side(PanelSide::Left),
// )
// .filter(|_| self.is_collab_panel_open()),
// )
// .child(NotificationToast::new(
// "maxbrunsfeld has requested to add you as a contact.".into(),
// ))
.child(
div().flex().flex_col().flex_1().h_full().child(
div().flex().flex_1().child(self.center.render(
&self.project,
&self.follower_states,
self.active_call(),
&self.active_pane,
self.zoomed.as_ref(),
&self.app_state,
cx,
)),
), // .children(
// Some(
// Panel::new("terminal-panel", cx)
// .child(Terminal::new())
// .allowed_sides(PanelAllowedSides::BottomOnly)
// .side(PanelSide::Bottom),
// )
// .filter(|_| self.is_terminal_open()),
// ),
let mut context = KeyContext::default();
context.add("Workspace");
self.add_workspace_actions_listeners(div())
.context(context)
.relative()
.size_full()
.flex()
.flex_col()
.font("Zed Sans")
.gap_0()
.justify_start()
.items_start()
.text_color(cx.theme().colors().text)
.bg(cx.theme().colors().background)
.child(self.render_titlebar(cx))
.child(
// todo! should this be a component a view?
div()
.id("workspace")
.relative()
.flex_1()
.w_full()
.flex()
.overflow_hidden()
.border_t()
.border_b()
.border_color(cx.theme().colors().border)
.child(self.modal_layer.clone())
// .children(
// Some(
// Panel::new("project-panel-outer", cx)
// .side(PanelSide::Left)
// .child(ProjectPanel::new("project-panel-inner")),
// )
// .filter(|_| self.is_project_panel_open()),
// )
// .children(
// Some(
// Panel::new("collab-panel-outer", cx)
// .child(CollabPanel::new("collab-panel-inner"))
// .side(PanelSide::Left),
// )
// .filter(|_| self.is_collab_panel_open()),
// )
// .child(NotificationToast::new(
// "maxbrunsfeld has requested to add you as a contact.".into(),
// ))
.child(
div().flex().flex_col().flex_1().h_full().child(
div().flex().flex_1().child(self.center.render(
&self.project,
&self.follower_states,
self.active_call(),
&self.active_pane,
self.zoomed.as_ref(),
&self.app_state,
cx,
)),
), // .children(
// Some(
// Panel::new("chat-panel-outer", cx)
// .side(PanelSide::Right)
// .child(ChatPanel::new("chat-panel-inner").messages(vec![
// ChatMessage::new(
// "osiewicz".to_string(),
// "is this thing on?".to_string(),
// DateTime::parse_from_rfc3339("2023-09-27T15:40:52.707Z")
// .unwrap()
// .naive_local(),
// ),
// ChatMessage::new(
// "maxdeviant".to_string(),
// "Reading you loud and clear!".to_string(),
// DateTime::parse_from_rfc3339("2023-09-28T15:40:52.707Z")
// .unwrap()
// .naive_local(),
// ),
// ])),
// Panel::new("terminal-panel", cx)
// .child(Terminal::new())
// .allowed_sides(PanelAllowedSides::BottomOnly)
// .side(PanelSide::Bottom),
// )
// .filter(|_| self.is_chat_panel_open()),
// )
// .children(
// Some(
// Panel::new("notifications-panel-outer", cx)
// .side(PanelSide::Right)
// .child(NotificationsPanel::new("notifications-panel-inner")),
// )
// .filter(|_| self.is_notifications_panel_open()),
// )
// .children(
// Some(
// Panel::new("assistant-panel-outer", cx)
// .child(AssistantPanel::new("assistant-panel-inner")),
// )
// .filter(|_| self.is_assistant_panel_open()),
// .filter(|_| self.is_terminal_open()),
// ),
)
.child(self.status_bar.clone())
// .when(self.debug.show_toast, |this| {
// this.child(Toast::new(ToastOrigin::Bottom).child(Label::new("A toast")))
// })
// .children(
// Some(
// div()
// .absolute()
// .top(px(50.))
// .left(px(640.))
// .z_index(8)
// .child(LanguageSelector::new("language-selector")),
// )
// .filter(|_| self.is_language_selector_open()),
// )
.z_index(8)
// Debug
.child(
div()
.flex()
.flex_col()
.z_index(9)
.absolute()
.top_20()
.left_1_4()
.w_40()
.gap_2(), // .when(self.show_debug, |this| {
// this.child(Button::<Workspace>::new("Toggle User Settings").on_click(
// Arc::new(|workspace, cx| workspace.debug_toggle_user_settings(cx)),
// ))
// .child(
// Button::<Workspace>::new("Toggle Toasts").on_click(Arc::new(
// |workspace, cx| workspace.debug_toggle_toast(cx),
// )),
// )
// .child(
// Button::<Workspace>::new("Toggle Livestream").on_click(Arc::new(
// |workspace, cx| workspace.debug_toggle_livestream(cx),
// )),
// )
// })
// .child(
// Button::<Workspace>::new("Toggle Debug")
// .on_click(Arc::new(|workspace, cx| workspace.toggle_debug(cx))),
// ),
)
})
), // .children(
// Some(
// Panel::new("chat-panel-outer", cx)
// .side(PanelSide::Right)
// .child(ChatPanel::new("chat-panel-inner").messages(vec![
// ChatMessage::new(
// "osiewicz".to_string(),
// "is this thing on?".to_string(),
// DateTime::parse_from_rfc3339("2023-09-27T15:40:52.707Z")
// .unwrap()
// .naive_local(),
// ),
// ChatMessage::new(
// "maxdeviant".to_string(),
// "Reading you loud and clear!".to_string(),
// DateTime::parse_from_rfc3339("2023-09-28T15:40:52.707Z")
// .unwrap()
// .naive_local(),
// ),
// ])),
// )
// .filter(|_| self.is_chat_panel_open()),
// )
// .children(
// Some(
// Panel::new("notifications-panel-outer", cx)
// .side(PanelSide::Right)
// .child(NotificationsPanel::new("notifications-panel-inner")),
// )
// .filter(|_| self.is_notifications_panel_open()),
// )
// .children(
// Some(
// Panel::new("assistant-panel-outer", cx)
// .child(AssistantPanel::new("assistant-panel-inner")),
// )
// .filter(|_| self.is_assistant_panel_open()),
// ),
)
.child(self.status_bar.clone())
// .when(self.debug.show_toast, |this| {
// this.child(Toast::new(ToastOrigin::Bottom).child(Label::new("A toast")))
// })
// .children(
// Some(
// div()
// .absolute()
// .top(px(50.))
// .left(px(640.))
// .z_index(8)
// .child(LanguageSelector::new("language-selector")),
// )
// .filter(|_| self.is_language_selector_open()),
// )
.z_index(8)
// Debug
.child(
div()
.flex()
.flex_col()
.z_index(9)
.absolute()
.top_20()
.left_1_4()
.w_40()
.gap_2(), // .when(self.show_debug, |this| {
// this.child(Button::<Workspace>::new("Toggle User Settings").on_click(
// Arc::new(|workspace, cx| workspace.debug_toggle_user_settings(cx)),
// ))
// .child(
// Button::<Workspace>::new("Toggle Toasts").on_click(Arc::new(
// |workspace, cx| workspace.debug_toggle_toast(cx),
// )),
// )
// .child(
// Button::<Workspace>::new("Toggle Livestream").on_click(Arc::new(
// |workspace, cx| workspace.debug_toggle_livestream(cx),
// )),
// )
// })
// .child(
// Button::<Workspace>::new("Toggle Debug")
// .on_click(Arc::new(|workspace, cx| workspace.toggle_debug(cx))),
// ),
)
}
}
// todo!()