{
- div()
- .id("acp-thread-scrollbar")
- .occlude()
- .on_mouse_move(cx.listener(|_, _, _, cx| {
- cx.notify();
- cx.stop_propagation()
- }))
- .on_hover(|_, _, cx| {
- cx.stop_propagation();
- })
- .on_any_mouse_down(|_, _, cx| {
- cx.stop_propagation();
- })
- .on_mouse_up(
- MouseButton::Left,
- cx.listener(|_, _, _, cx| {
- cx.stop_propagation();
- }),
- )
- .on_scroll_wheel(cx.listener(|_, _, _, cx| {
- cx.notify();
- }))
- .h_full()
- .absolute()
- .right_1()
- .top_1()
- .bottom_0()
- .w(px(12.))
- .cursor_default()
- .children(Scrollbar::vertical(self.scrollbar_state.clone()).map(|s| s.auto_hide(cx)))
- }
-
fn render_token_limit_callout(
&self,
line_height: Pixels,
@@ -4950,23 +4830,27 @@ impl Render for AcpThreadView {
configuration_view,
pending_auth_method,
..
- } => self.render_auth_required_state(
- connection,
- description.as_ref(),
- configuration_view.as_ref(),
- pending_auth_method.as_ref(),
- window,
- cx,
- ),
+ } => self
+ .render_auth_required_state(
+ connection,
+ description.as_ref(),
+ configuration_view.as_ref(),
+ pending_auth_method.as_ref(),
+ window,
+ cx,
+ )
+ .into_any(),
ThreadState::Loading { .. } => v_flex()
.flex_1()
- .child(self.render_recent_history(window, cx)),
+ .child(self.render_recent_history(window, cx))
+ .into_any(),
ThreadState::LoadError(e) => v_flex()
.flex_1()
.size_full()
.items_center()
.justify_end()
- .child(self.render_load_error(e, window, cx)),
+ .child(self.render_load_error(e, cx))
+ .into_any(),
ThreadState::Ready { .. } => v_flex().flex_1().map(|this| {
if has_messages {
this.child(
@@ -4986,9 +4870,11 @@ impl Render for AcpThreadView {
.flex_grow()
.into_any(),
)
- .child(self.render_vertical_scrollbar(cx))
+ .vertical_scrollbar_for(self.list_state.clone(), window, cx)
+ .into_any()
} else {
this.child(self.render_recent_history(window, cx))
+ .into_any()
}
}),
})
diff --git a/crates/agent_ui/src/active_thread.rs b/crates/agent_ui/src/active_thread.rs
index e0cecad6e2..575d8a3a56 100644
--- a/crates/agent_ui/src/active_thread.rs
+++ b/crates/agent_ui/src/active_thread.rs
@@ -22,10 +22,9 @@ use editor::{Editor, EditorElement, EditorEvent, EditorStyle, MultiBuffer, Selec
use gpui::{
AbsoluteLength, Animation, AnimationExt, AnyElement, App, ClickEvent, ClipboardEntry,
ClipboardItem, DefiniteLength, EdgesRefinement, Empty, Entity, EventEmitter, Focusable, Hsla,
- ListAlignment, ListOffset, ListState, MouseButton, PlatformDisplay, ScrollHandle, Stateful,
- StyleRefinement, Subscription, Task, TextStyle, TextStyleRefinement, Transformation,
- UnderlineStyle, WeakEntity, WindowHandle, linear_color_stop, linear_gradient, list, percentage,
- pulsating_between,
+ ListAlignment, ListOffset, ListState, PlatformDisplay, ScrollHandle, Stateful, StyleRefinement,
+ Subscription, Task, TextStyle, TextStyleRefinement, Transformation, UnderlineStyle, WeakEntity,
+ WindowHandle, linear_color_stop, linear_gradient, list, percentage, pulsating_between,
};
use language::{Buffer, Language, LanguageRegistry};
use language_model::{
@@ -46,8 +45,7 @@ use std::time::Duration;
use text::ToPoint;
use theme::ThemeSettings;
use ui::{
- Banner, Disclosure, KeyBinding, PopoverMenuHandle, Scrollbar, ScrollbarState, TextSize,
- Tooltip, prelude::*,
+ Banner, Disclosure, KeyBinding, PopoverMenuHandle, TextSize, Tooltip, WithScrollbar, prelude::*,
};
use util::ResultExt as _;
use util::markdown::MarkdownCodeBlock;
@@ -68,7 +66,6 @@ pub struct ActiveThread {
save_thread_task: Option
>,
messages: Vec,
list_state: ListState,
- scrollbar_state: ScrollbarState,
rendered_messages_by_id: HashMap,
rendered_tool_uses: HashMap,
editing_message: Option<(MessageId, EditingMessageState)>,
@@ -799,8 +796,7 @@ impl ActiveThread {
expanded_tool_uses: HashMap::default(),
expanded_thinking_segments: HashMap::default(),
expanded_code_blocks: HashMap::default(),
- list_state: list_state.clone(),
- scrollbar_state: ScrollbarState::new(list_state).parent_entity(&cx.entity()),
+ list_state,
editing_message: None,
last_error: None,
copied_code_block_ids: HashSet::default(),
@@ -3491,39 +3487,6 @@ impl ActiveThread {
}
}
- fn render_vertical_scrollbar(&self, cx: &mut Context) -> Stateful {
- div()
- .occlude()
- .id("active-thread-scrollbar")
- .on_mouse_move(cx.listener(|_, _, _, cx| {
- cx.notify();
- cx.stop_propagation()
- }))
- .on_hover(|_, _, cx| {
- cx.stop_propagation();
- })
- .on_any_mouse_down(|_, _, cx| {
- cx.stop_propagation();
- })
- .on_mouse_up(
- MouseButton::Left,
- cx.listener(|_, _, _, cx| {
- cx.stop_propagation();
- }),
- )
- .on_scroll_wheel(cx.listener(|_, _, _, cx| {
- cx.notify();
- }))
- .h_full()
- .absolute()
- .right_1()
- .top_1()
- .bottom_0()
- .w(px(12.))
- .cursor_default()
- .children(Scrollbar::vertical(self.scrollbar_state.clone()).map(|s| s.auto_hide(cx)))
- }
-
pub fn is_codeblock_expanded(&self, message_id: MessageId, ix: usize) -> bool {
self.expanded_code_blocks
.get(&(message_id, ix))
@@ -3557,13 +3520,13 @@ pub enum ActiveThreadEvent {
impl EventEmitter
for ActiveThread {}
impl Render for ActiveThread {
- fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement {
+ fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement {
v_flex()
.size_full()
.relative()
.bg(cx.theme().colors().panel_background)
.child(list(self.list_state.clone(), cx.processor(Self::render_message)).flex_grow())
- .child(self.render_vertical_scrollbar(cx))
+ .vertical_scrollbar_for(self.list_state.clone(), window, cx)
}
}
diff --git a/crates/agent_ui/src/agent_configuration.rs b/crates/agent_ui/src/agent_configuration.rs
index 224f49cc3e..2b2cec5539 100644
--- a/crates/agent_ui/src/agent_configuration.rs
+++ b/crates/agent_ui/src/agent_configuration.rs
@@ -3,23 +3,20 @@ mod configure_context_server_modal;
mod manage_profiles_modal;
mod tool_picker;
-use std::{ops::Range, sync::Arc, time::Duration};
+use std::{sync::Arc, time::Duration};
-use agent_servers::{AgentServerCommand, AgentServerSettings, AllAgentServersSettings, Gemini};
+use agent_servers::{AgentServerCommand, AllAgentServersSettings, Gemini};
use agent_settings::AgentSettings;
-use anyhow::Result;
use assistant_tool::{ToolSource, ToolWorkingSet};
use cloud_llm_client::Plan;
use collections::HashMap;
use context_server::ContextServerId;
-use editor::{Editor, SelectionEffects, scroll::Autoscroll};
use extension::ExtensionManifest;
use extension_host::ExtensionStore;
use fs::Fs;
use gpui::{
- Action, Animation, AnimationExt as _, AnyView, App, AsyncWindowContext, Corner, Entity,
- EventEmitter, FocusHandle, Focusable, Hsla, ScrollHandle, Subscription, Task, Transformation,
- WeakEntity, percentage,
+ Action, Animation, AnimationExt as _, AnyView, App, Corner, Entity, EventEmitter, FocusHandle,
+ Focusable, Hsla, ScrollHandle, Subscription, Task, Transformation, WeakEntity, percentage,
};
use language::LanguageRegistry;
use language_model::{
@@ -34,10 +31,10 @@ use project::{
use settings::{Settings, SettingsStore, update_settings_file};
use ui::{
Chip, ContextMenu, Disclosure, Divider, DividerColor, ElevationIndex, Indicator, PopoverMenu,
- Scrollbar, ScrollbarState, Switch, SwitchColor, SwitchField, Tooltip, prelude::*,
+ Switch, SwitchColor, SwitchField, Tooltip, WithScrollbar, prelude::*,
};
use util::ResultExt as _;
-use workspace::{Workspace, create_and_open_local_file};
+use workspace::Workspace;
use zed_actions::ExtensionCategoryFilter;
pub(crate) use configure_context_server_modal::ConfigureContextServerModal;
@@ -61,7 +58,6 @@ pub struct AgentConfiguration {
tools: Entity,
_registry_subscription: Subscription,
scroll_handle: ScrollHandle,
- scrollbar_state: ScrollbarState,
gemini_is_installed: bool,
_check_for_gemini: Task<()>,
}
@@ -105,7 +101,6 @@ impl AgentConfiguration {
.detach();
let scroll_handle = ScrollHandle::new();
- let scrollbar_state = ScrollbarState::new(scroll_handle.clone());
let mut this = Self {
fs,
@@ -120,7 +115,6 @@ impl AgentConfiguration {
tools,
_registry_subscription: registry_subscription,
scroll_handle,
- scrollbar_state,
gemini_is_installed: false,
_check_for_gemini: Task::ready(()),
};
@@ -1061,39 +1055,10 @@ impl AgentConfiguration {
.child(
v_flex()
.gap_0p5()
- .child(
- h_flex()
- .w_full()
- .gap_2()
- .justify_between()
- .child(Headline::new("External Agents"))
- .child(
- Button::new("add-agent", "Add Agent")
- .icon_position(IconPosition::Start)
- .icon(IconName::Plus)
- .icon_size(IconSize::Small)
- .icon_color(Color::Muted)
- .label_size(LabelSize::Small)
- .on_click(
- move |_, window, cx| {
- if let Some(workspace) = window.root().flatten() {
- let workspace = workspace.downgrade();
- window
- .spawn(cx, async |cx| {
- open_new_agent_servers_entry_in_settings_editor(
- workspace,
- cx,
- ).await
- })
- .detach_and_log_err(cx);
- }
- }
- ),
- )
- )
+ .child(Headline::new("External Agents"))
.child(
Label::new(
- "Bring the agent of your choice to Zed via our new Agent Client Protocol.",
+ "Use the full power of Zed's UI with your favorite agent, connected via the Agent Client Protocol.",
)
.color(Color::Muted),
),
@@ -1241,32 +1206,7 @@ impl Render for AgentConfiguration {
.child(self.render_context_servers_section(window, cx))
.child(self.render_provider_configuration_section(cx)),
)
- .child(
- div()
- .id("assistant-configuration-scrollbar")
- .occlude()
- .absolute()
- .right(px(3.))
- .top_0()
- .bottom_0()
- .pb_6()
- .w(px(12.))
- .cursor_default()
- .on_mouse_move(cx.listener(|_, _, _window, cx| {
- cx.notify();
- cx.stop_propagation()
- }))
- .on_hover(|_, _window, cx| {
- cx.stop_propagation();
- })
- .on_any_mouse_down(|_, _window, cx| {
- cx.stop_propagation();
- })
- .on_scroll_wheel(cx.listener(|_, _, _window, cx| {
- cx.notify();
- }))
- .children(Scrollbar::vertical(self.scrollbar_state.clone())),
- )
+ .vertical_scrollbar(window, cx)
}
}
@@ -1356,109 +1296,3 @@ fn show_unable_to_uninstall_extension_with_context_server(
workspace.toggle_status_toast(status_toast, cx);
}
-
-async fn open_new_agent_servers_entry_in_settings_editor(
- workspace: WeakEntity,
- cx: &mut AsyncWindowContext,
-) -> Result<()> {
- let settings_editor = workspace
- .update_in(cx, |_, window, cx| {
- create_and_open_local_file(paths::settings_file(), window, cx, || {
- settings::initial_user_settings_content().as_ref().into()
- })
- })?
- .await?
- .downcast::()
- .unwrap();
-
- settings_editor
- .downgrade()
- .update_in(cx, |item, window, cx| {
- let text = item.buffer().read(cx).snapshot(cx).text();
-
- let settings = cx.global::();
-
- let mut unique_server_name = None;
- let edits = settings.edits_for_update::(&text, |file| {
- let server_name: Option = (0..u8::MAX)
- .map(|i| {
- if i == 0 {
- "your_agent".into()
- } else {
- format!("your_agent_{}", i).into()
- }
- })
- .find(|name| !file.custom.contains_key(name));
- if let Some(server_name) = server_name {
- unique_server_name = Some(server_name.clone());
- file.custom.insert(
- server_name,
- AgentServerSettings {
- command: AgentServerCommand {
- path: "path_to_executable".into(),
- args: vec![],
- env: Some(HashMap::default()),
- },
- },
- );
- }
- });
-
- if edits.is_empty() {
- return;
- }
-
- let ranges = edits
- .iter()
- .map(|(range, _)| range.clone())
- .collect::>();
-
- item.edit(edits, cx);
- if let Some((unique_server_name, buffer)) =
- unique_server_name.zip(item.buffer().read(cx).as_singleton())
- {
- let snapshot = buffer.read(cx).snapshot();
- if let Some(range) =
- find_text_in_buffer(&unique_server_name, ranges[0].start, &snapshot)
- {
- item.change_selections(
- SelectionEffects::scroll(Autoscroll::newest()),
- window,
- cx,
- |selections| {
- selections.select_ranges(vec![range]);
- },
- );
- }
- }
- })
-}
-
-fn find_text_in_buffer(
- text: &str,
- start: usize,
- snapshot: &language::BufferSnapshot,
-) -> Option> {
- let chars = text.chars().collect::>();
-
- let mut offset = start;
- let mut char_offset = 0;
- for c in snapshot.chars_at(start) {
- if char_offset >= chars.len() {
- break;
- }
- offset += 1;
-
- if c == chars[char_offset] {
- char_offset += 1;
- } else {
- char_offset = 0;
- }
- }
-
- if char_offset == chars.len() {
- Some(offset.saturating_sub(chars.len())..offset)
- } else {
- None
- }
-}
diff --git a/crates/agent_ui/src/agent_panel.rs b/crates/agent_ui/src/agent_panel.rs
index d1cf748733..269aec3365 100644
--- a/crates/agent_ui/src/agent_panel.rs
+++ b/crates/agent_ui/src/agent_panel.rs
@@ -14,7 +14,6 @@ use zed_actions::agent::ReauthenticateAgent;
use crate::acp::{AcpThreadHistory, ThreadHistoryEvent};
use crate::agent_diff::AgentDiffThread;
-use crate::ui::AcpOnboardingModal;
use crate::{
AddContextServer, AgentDiffPane, ContinueThread, ContinueWithBurnMode,
DeleteRecentlyOpenThread, ExpandMessageEditor, Follow, InlineAssistant, NewTextThread,
@@ -78,10 +77,7 @@ use workspace::{
};
use zed_actions::{
DecreaseBufferFontSize, IncreaseBufferFontSize, ResetBufferFontSize,
- agent::{
- OpenAcpOnboardingModal, OpenOnboardingModal, OpenSettings, ResetOnboarding,
- ToggleModelSelector,
- },
+ agent::{OpenOnboardingModal, OpenSettings, ResetOnboarding, ToggleModelSelector},
assistant::{OpenRulesLibrary, ToggleFocus},
};
@@ -205,9 +201,6 @@ pub fn init(cx: &mut App) {
.register_action(|workspace, _: &OpenOnboardingModal, window, cx| {
AgentOnboardingModal::toggle(workspace, window, cx)
})
- .register_action(|workspace, _: &OpenAcpOnboardingModal, window, cx| {
- AcpOnboardingModal::toggle(workspace, window, cx)
- })
.register_action(|_workspace, _: &ResetOnboarding, window, cx| {
window.dispatch_action(workspace::RestoreBanner.boxed_clone(), cx);
window.refresh();
@@ -598,6 +591,17 @@ impl AgentPanel {
None
};
+ // Wait for the Gemini/Native feature flag to be available.
+ let client = workspace.read_with(cx, |workspace, _| workspace.client().clone())?;
+ if !client.status().borrow().is_signed_out() {
+ cx.update(|_, cx| {
+ cx.wait_for_flag_or_timeout::(
+ Duration::from_secs(2),
+ )
+ })?
+ .await;
+ }
+
let panel = workspace.update_in(cx, |workspace, window, cx| {
let panel = cx.new(|cx| {
Self::new(
@@ -1848,6 +1852,19 @@ impl AgentPanel {
menu
}
+ pub fn set_selected_agent(
+ &mut self,
+ agent: AgentType,
+ window: &mut Window,
+ cx: &mut Context,
+ ) {
+ if self.selected_agent != agent {
+ self.selected_agent = agent.clone();
+ self.serialize(cx);
+ }
+ self.new_agent_thread(agent, window, cx);
+ }
+
pub fn selected_agent(&self) -> AgentType {
self.selected_agent.clone()
}
@@ -1858,11 +1875,6 @@ impl AgentPanel {
window: &mut Window,
cx: &mut Context,
) {
- if self.selected_agent != agent {
- self.selected_agent = agent.clone();
- self.serialize(cx);
- }
-
match agent {
AgentType::Zed => {
window.dispatch_action(
@@ -2543,7 +2555,7 @@ impl AgentPanel {
workspace.panel::(cx)
{
panel.update(cx, |panel, cx| {
- panel.new_agent_thread(
+ panel.set_selected_agent(
AgentType::NativeAgent,
window,
cx,
@@ -2569,7 +2581,7 @@ impl AgentPanel {
workspace.panel::(cx)
{
panel.update(cx, |panel, cx| {
- panel.new_agent_thread(
+ panel.set_selected_agent(
AgentType::TextThread,
window,
cx,
@@ -2597,7 +2609,7 @@ impl AgentPanel {
workspace.panel::(cx)
{
panel.update(cx, |panel, cx| {
- panel.new_agent_thread(
+ panel.set_selected_agent(
AgentType::Gemini,
window,
cx,
@@ -2624,7 +2636,7 @@ impl AgentPanel {
workspace.panel::(cx)
{
panel.update(cx, |panel, cx| {
- panel.new_agent_thread(
+ panel.set_selected_agent(
AgentType::ClaudeCode,
window,
cx,
@@ -2657,7 +2669,7 @@ impl AgentPanel {
workspace.panel::(cx)
{
panel.update(cx, |panel, cx| {
- panel.new_agent_thread(
+ panel.set_selected_agent(
AgentType::Custom {
name: agent_name
.clone(),
@@ -2681,9 +2693,9 @@ impl AgentPanel {
})
.when(cx.has_flag::(), |menu| {
menu.separator().link(
- "Add Other Agents",
+ "Add Your Own Agent",
OpenBrowser {
- url: zed_urls::external_agents_docs(cx),
+ url: "https://agentclientprotocol.com/".into(),
}
.boxed_clone(),
)
diff --git a/crates/agent_ui/src/language_model_selector.rs b/crates/agent_ui/src/language_model_selector.rs
index 3633e533da..aceca79dbf 100644
--- a/crates/agent_ui/src/language_model_selector.rs
+++ b/crates/agent_ui/src/language_model_selector.rs
@@ -6,8 +6,7 @@ use feature_flags::ZedProFeatureFlag;
use fuzzy::{StringMatch, StringMatchCandidate, match_strings};
use gpui::{Action, AnyElement, App, BackgroundExecutor, DismissEvent, Subscription, Task};
use language_model::{
- AuthenticateError, ConfiguredModel, LanguageModel, LanguageModelProviderId,
- LanguageModelRegistry,
+ ConfiguredModel, LanguageModel, LanguageModelProviderId, LanguageModelRegistry,
};
use ordered_float::OrderedFloat;
use picker::{Picker, PickerDelegate};
@@ -77,7 +76,6 @@ pub struct LanguageModelPickerDelegate {
all_models: Arc,
filtered_entries: Vec,
selected_index: usize,
- _authenticate_all_providers_task: Task<()>,
_subscriptions: Vec,
}
@@ -98,7 +96,6 @@ impl LanguageModelPickerDelegate {
selected_index: Self::get_active_model_index(&entries, get_active_model(cx)),
filtered_entries: entries,
get_active_model: Arc::new(get_active_model),
- _authenticate_all_providers_task: Self::authenticate_all_providers(cx),
_subscriptions: vec![cx.subscribe_in(
&LanguageModelRegistry::global(cx),
window,
@@ -142,56 +139,6 @@ impl LanguageModelPickerDelegate {
.unwrap_or(0)
}
- /// Authenticates all providers in the [`LanguageModelRegistry`].
- ///
- /// We do this so that we can populate the language selector with all of the
- /// models from the configured providers.
- fn authenticate_all_providers(cx: &mut App) -> Task<()> {
- let authenticate_all_providers = LanguageModelRegistry::global(cx)
- .read(cx)
- .providers()
- .iter()
- .map(|provider| (provider.id(), provider.name(), provider.authenticate(cx)))
- .collect::>();
-
- cx.spawn(async move |_cx| {
- for (provider_id, provider_name, authenticate_task) in authenticate_all_providers {
- if let Err(err) = authenticate_task.await {
- if matches!(err, AuthenticateError::CredentialsNotFound) {
- // Since we're authenticating these providers in the
- // background for the purposes of populating the
- // language selector, we don't care about providers
- // where the credentials are not found.
- } else {
- // Some providers have noisy failure states that we
- // don't want to spam the logs with every time the
- // language model selector is initialized.
- //
- // Ideally these should have more clear failure modes
- // that we know are safe to ignore here, like what we do
- // with `CredentialsNotFound` above.
- match provider_id.0.as_ref() {
- "lmstudio" | "ollama" => {
- // LM Studio and Ollama both make fetch requests to the local APIs to determine if they are "authenticated".
- //
- // These fail noisily, so we don't log them.
- }
- "copilot_chat" => {
- // Copilot Chat returns an error if Copilot is not enabled, so we don't log those errors.
- }
- _ => {
- log::error!(
- "Failed to authenticate provider: {}: {err}",
- provider_name.0
- );
- }
- }
- }
- }
- }
- })
- }
-
pub fn active_model(&self, cx: &App) -> Option {
(self.get_active_model)(cx)
}
diff --git a/crates/agent_ui/src/thread_history.rs b/crates/agent_ui/src/thread_history.rs
index 4ec2078e5d..32c4de1d42 100644
--- a/crates/agent_ui/src/thread_history.rs
+++ b/crates/agent_ui/src/thread_history.rs
@@ -4,14 +4,14 @@ use chrono::{Datelike as _, Local, NaiveDate, TimeDelta};
use editor::{Editor, EditorEvent};
use fuzzy::{StringMatch, StringMatchCandidate};
use gpui::{
- App, ClickEvent, Empty, Entity, FocusHandle, Focusable, ScrollStrategy, Stateful, Task,
+ App, ClickEvent, Empty, Entity, FocusHandle, Focusable, ScrollStrategy, Task,
UniformListScrollHandle, WeakEntity, Window, uniform_list,
};
use std::{fmt::Display, ops::Range, sync::Arc};
use time::{OffsetDateTime, UtcOffset};
use ui::{
- HighlightedLabel, IconButtonShape, ListItem, ListItemSpacing, Scrollbar, ScrollbarState,
- Tooltip, prelude::*,
+ HighlightedLabel, IconButtonShape, ListItem, ListItemSpacing, ScrollAxes, Scrollbars, Tooltip,
+ WithScrollbar, prelude::*,
};
use util::ResultExt;
@@ -30,8 +30,6 @@ pub struct ThreadHistory {
separated_item_indexes: Vec,
_separated_items_task: Option>,
search_state: SearchState,
- scrollbar_visibility: bool,
- scrollbar_state: ScrollbarState,
_subscriptions: Vec,
}
@@ -90,7 +88,6 @@ impl ThreadHistory {
});
let scroll_handle = UniformListScrollHandle::default();
- let scrollbar_state = ScrollbarState::new(scroll_handle.clone());
let mut this = Self {
agent_panel,
@@ -103,8 +100,6 @@ impl ThreadHistory {
separated_items: Default::default(),
separated_item_indexes: Default::default(),
search_editor,
- scrollbar_visibility: true,
- scrollbar_state,
_subscriptions: vec![search_editor_subscription, history_store_subscription],
_separated_items_task: None,
};
@@ -363,43 +358,6 @@ impl ThreadHistory {
cx.notify();
}
- fn render_scrollbar(&self, cx: &mut Context) -> Option> {
- if !(self.scrollbar_visibility || self.scrollbar_state.is_dragging()) {
- return None;
- }
-
- Some(
- div()
- .occlude()
- .id("thread-history-scroll")
- .h_full()
- .bg(cx.theme().colors().panel_background.opacity(0.8))
- .border_l_1()
- .border_color(cx.theme().colors().border_variant)
- .absolute()
- .right_1()
- .top_0()
- .bottom_0()
- .w_4()
- .pl_1()
- .cursor_default()
- .on_mouse_move(cx.listener(|_, _, _window, cx| {
- cx.notify();
- cx.stop_propagation()
- }))
- .on_hover(|_, _window, cx| {
- cx.stop_propagation();
- })
- .on_any_mouse_down(|_, _window, cx| {
- cx.stop_propagation();
- })
- .on_scroll_wheel(cx.listener(|_, _, _window, cx| {
- cx.notify();
- }))
- .children(Scrollbar::vertical(self.scrollbar_state.clone())),
- )
- }
-
fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context) {
if let Some(entry) = self.get_match(self.selected_index) {
let task_result = match entry {
@@ -536,7 +494,7 @@ impl Focusable for ThreadHistory {
}
impl Render for ThreadHistory {
- fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement {
+ fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement {
v_flex()
.key_context("ThreadHistory")
.size_full()
@@ -601,9 +559,14 @@ impl Render for ThreadHistory {
.track_scroll(self.scroll_handle.clone())
.flex_grow(),
)
- .when_some(self.render_scrollbar(cx), |div, scrollbar| {
- div.child(scrollbar)
- })
+ .custom_scrollbars(
+ Scrollbars::new(ScrollAxes::Vertical)
+ .tracked_scroll_handle(self.scroll_handle.clone())
+ .width_sm()
+ .with_track_along(ScrollAxes::Vertical),
+ window,
+ cx,
+ )
}
})
}
diff --git a/crates/agent_ui/src/ui.rs b/crates/agent_ui/src/ui.rs
index 600698b07e..ada973cddf 100644
--- a/crates/agent_ui/src/ui.rs
+++ b/crates/agent_ui/src/ui.rs
@@ -1,4 +1,3 @@
-mod acp_onboarding_modal;
mod agent_notification;
mod burn_mode_tooltip;
mod context_pill;
@@ -7,7 +6,6 @@ mod onboarding_modal;
pub mod preview;
mod unavailable_editing_tooltip;
-pub use acp_onboarding_modal::*;
pub use agent_notification::*;
pub use burn_mode_tooltip::*;
pub use context_pill::*;
diff --git a/crates/agent_ui/src/ui/acp_onboarding_modal.rs b/crates/agent_ui/src/ui/acp_onboarding_modal.rs
deleted file mode 100644
index 0ed9de7221..0000000000
--- a/crates/agent_ui/src/ui/acp_onboarding_modal.rs
+++ /dev/null
@@ -1,254 +0,0 @@
-use client::zed_urls;
-use gpui::{
- ClickEvent, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, MouseDownEvent, Render,
- linear_color_stop, linear_gradient,
-};
-use ui::{TintColor, Vector, VectorName, prelude::*};
-use workspace::{ModalView, Workspace};
-
-use crate::agent_panel::{AgentPanel, AgentType};
-
-macro_rules! acp_onboarding_event {
- ($name:expr) => {
- telemetry::event!($name, source = "ACP Onboarding");
- };
- ($name:expr, $($key:ident $(= $value:expr)?),+ $(,)?) => {
- telemetry::event!($name, source = "ACP Onboarding", $($key $(= $value)?),+);
- };
-}
-
-pub struct AcpOnboardingModal {
- focus_handle: FocusHandle,
- workspace: Entity,
-}
-
-impl AcpOnboardingModal {
- pub fn toggle(workspace: &mut Workspace, window: &mut Window, cx: &mut Context) {
- let workspace_entity = cx.entity();
- workspace.toggle_modal(window, cx, |_window, cx| Self {
- workspace: workspace_entity,
- focus_handle: cx.focus_handle(),
- });
- }
-
- fn open_panel(&mut self, _: &ClickEvent, window: &mut Window, cx: &mut Context) {
- self.workspace.update(cx, |workspace, cx| {
- workspace.focus_panel::(window, cx);
-
- if let Some(panel) = workspace.panel::(cx) {
- panel.update(cx, |panel, cx| {
- panel.new_agent_thread(AgentType::Gemini, window, cx);
- });
- }
- });
-
- cx.emit(DismissEvent);
-
- acp_onboarding_event!("Open Panel Clicked");
- }
-
- fn view_docs(&mut self, _: &ClickEvent, _: &mut Window, cx: &mut Context) {
- cx.open_url(&zed_urls::external_agents_docs(cx));
- cx.notify();
-
- acp_onboarding_event!("Documentation Link Clicked");
- }
-
- fn cancel(&mut self, _: &menu::Cancel, _: &mut Window, cx: &mut Context) {
- cx.emit(DismissEvent);
- }
-}
-
-impl EventEmitter for AcpOnboardingModal {}
-
-impl Focusable for AcpOnboardingModal {
- fn focus_handle(&self, _cx: &App) -> FocusHandle {
- self.focus_handle.clone()
- }
-}
-
-impl ModalView for AcpOnboardingModal {}
-
-impl Render for AcpOnboardingModal {
- fn render(&mut self, _: &mut Window, cx: &mut Context) -> impl IntoElement {
- let illustration_element = |label: bool, opacity: f32| {
- h_flex()
- .px_1()
- .py_0p5()
- .gap_1()
- .rounded_sm()
- .bg(cx.theme().colors().element_active.opacity(0.05))
- .border_1()
- .border_color(cx.theme().colors().border)
- .border_dashed()
- .child(
- Icon::new(IconName::Stop)
- .size(IconSize::Small)
- .color(Color::Custom(cx.theme().colors().text_muted.opacity(0.15))),
- )
- .map(|this| {
- if label {
- this.child(
- Label::new("Your Agent Here")
- .size(LabelSize::Small)
- .color(Color::Muted),
- )
- } else {
- this.child(
- div().w_16().h_1().rounded_full().bg(cx
- .theme()
- .colors()
- .element_active
- .opacity(0.6)),
- )
- }
- })
- .opacity(opacity)
- };
-
- let illustration = h_flex()
- .relative()
- .h(rems_from_px(126.))
- .bg(cx.theme().colors().editor_background)
- .border_b_1()
- .border_color(cx.theme().colors().border_variant)
- .justify_center()
- .gap_8()
- .rounded_t_md()
- .overflow_hidden()
- .child(
- div().absolute().inset_0().w(px(515.)).h(px(126.)).child(
- Vector::new(VectorName::AcpGrid, rems_from_px(515.), rems_from_px(126.))
- .color(ui::Color::Custom(cx.theme().colors().text.opacity(0.02))),
- ),
- )
- .child(div().absolute().inset_0().size_full().bg(linear_gradient(
- 0.,
- linear_color_stop(
- cx.theme().colors().elevated_surface_background.opacity(0.1),
- 0.9,
- ),
- linear_color_stop(
- cx.theme().colors().elevated_surface_background.opacity(0.),
- 0.,
- ),
- )))
- .child(
- div()
- .absolute()
- .inset_0()
- .size_full()
- .bg(gpui::black().opacity(0.15)),
- )
- .child(
- h_flex()
- .gap_4()
- .child(
- Vector::new(VectorName::AcpLogo, rems_from_px(106.), rems_from_px(40.))
- .color(ui::Color::Custom(cx.theme().colors().text.opacity(0.8))),
- )
- .child(
- Vector::new(
- VectorName::AcpLogoSerif,
- rems_from_px(111.),
- rems_from_px(41.),
- )
- .color(ui::Color::Custom(cx.theme().colors().text.opacity(0.8))),
- ),
- )
- .child(
- v_flex()
- .gap_1p5()
- .child(illustration_element(false, 0.15))
- .child(illustration_element(true, 0.3))
- .child(
- h_flex()
- .pl_1()
- .pr_2()
- .py_0p5()
- .gap_1()
- .rounded_sm()
- .bg(cx.theme().colors().element_active.opacity(0.2))
- .border_1()
- .border_color(cx.theme().colors().border)
- .child(
- Icon::new(IconName::AiGemini)
- .size(IconSize::Small)
- .color(Color::Muted),
- )
- .child(Label::new("New Gemini CLI Thread").size(LabelSize::Small)),
- )
- .child(illustration_element(true, 0.3))
- .child(illustration_element(false, 0.15)),
- );
-
- let heading = v_flex()
- .w_full()
- .gap_1()
- .child(
- Label::new("Now Available")
- .size(LabelSize::Small)
- .color(Color::Muted),
- )
- .child(Headline::new("Bring Your Own Agent to Zed").size(HeadlineSize::Large));
-
- let copy = "Bring the agent of your choice to Zed via our new Agent Client Protocol (ACP), starting with Google's Gemini CLI integration.";
-
- let open_panel_button = Button::new("open-panel", "Start with Gemini CLI")
- .icon_size(IconSize::Indicator)
- .style(ButtonStyle::Tinted(TintColor::Accent))
- .full_width()
- .on_click(cx.listener(Self::open_panel));
-
- let docs_button = Button::new("add-other-agents", "Add Other Agents")
- .icon(IconName::ArrowUpRight)
- .icon_size(IconSize::Indicator)
- .icon_color(Color::Muted)
- .full_width()
- .on_click(cx.listener(Self::view_docs));
-
- let close_button = h_flex().absolute().top_2().right_2().child(
- IconButton::new("cancel", IconName::Close).on_click(cx.listener(
- |_, _: &ClickEvent, _window, cx| {
- acp_onboarding_event!("Canceled", trigger = "X click");
- cx.emit(DismissEvent);
- },
- )),
- );
-
- v_flex()
- .id("acp-onboarding")
- .key_context("AcpOnboardingModal")
- .relative()
- .w(rems(34.))
- .h_full()
- .elevation_3(cx)
- .track_focus(&self.focus_handle(cx))
- .overflow_hidden()
- .on_action(cx.listener(Self::cancel))
- .on_action(cx.listener(|_, _: &menu::Cancel, _window, cx| {
- acp_onboarding_event!("Canceled", trigger = "Action");
- cx.emit(DismissEvent);
- }))
- .on_any_mouse_down(cx.listener(|this, _: &MouseDownEvent, window, _cx| {
- this.focus_handle.focus(window);
- }))
- .child(illustration)
- .child(
- v_flex()
- .p_4()
- .gap_2()
- .child(heading)
- .child(Label::new(copy).color(Color::Muted))
- .child(
- v_flex()
- .w_full()
- .mt_2()
- .gap_1()
- .child(open_panel_button)
- .child(docs_button),
- ),
- )
- .child(close_button)
- }
-}
diff --git a/crates/client/src/zed_urls.rs b/crates/client/src/zed_urls.rs
index 7193c09947..9df41906d7 100644
--- a/crates/client/src/zed_urls.rs
+++ b/crates/client/src/zed_urls.rs
@@ -43,11 +43,3 @@ pub fn ai_privacy_and_security(cx: &App) -> String {
server_url = server_url(cx)
)
}
-
-/// Returns the URL to Zed AI's external agents documentation.
-pub fn external_agents_docs(cx: &App) -> String {
- format!(
- "{server_url}/docs/ai/external-agents",
- server_url = server_url(cx)
- )
-}
diff --git a/crates/command_palette/src/persistence.rs b/crates/command_palette/src/persistence.rs
index 01cf403083..5be97c36bc 100644
--- a/crates/command_palette/src/persistence.rs
+++ b/crates/command_palette/src/persistence.rs
@@ -1,10 +1,7 @@
use anyhow::Result;
use db::{
- query,
- sqlez::{
- bindable::Column, domain::Domain, statement::Statement,
- thread_safe_connection::ThreadSafeConnection,
- },
+ define_connection, query,
+ sqlez::{bindable::Column, statement::Statement},
sqlez_macros::sql,
};
use serde::{Deserialize, Serialize};
@@ -53,11 +50,8 @@ impl Column for SerializedCommandInvocation {
}
}
-pub struct CommandPaletteDB(ThreadSafeConnection);
-
-impl Domain for CommandPaletteDB {
- const NAME: &str = stringify!(CommandPaletteDB);
- const MIGRATIONS: &[&str] = &[sql!(
+define_connection!(pub static ref COMMAND_PALETTE_HISTORY: CommandPaletteDB<()> =
+ &[sql!(
CREATE TABLE IF NOT EXISTS command_invocations(
id INTEGER PRIMARY KEY AUTOINCREMENT,
command_name TEXT NOT NULL,
@@ -65,9 +59,7 @@ impl Domain for CommandPaletteDB {
last_invoked INTEGER DEFAULT (unixepoch()) NOT NULL
) STRICT;
)];
-}
-
-db::static_connection!(COMMAND_PALETTE_HISTORY, CommandPaletteDB, []);
+);
impl CommandPaletteDB {
pub async fn write_command_invocation(
diff --git a/crates/db/src/db.rs b/crates/db/src/db.rs
index 0802bd8bb7..8b790cbec8 100644
--- a/crates/db/src/db.rs
+++ b/crates/db/src/db.rs
@@ -110,14 +110,11 @@ pub async fn open_test_db(db_name: &str) -> ThreadSafeConnection {
}
/// Implements a basic DB wrapper for a given domain
-///
-/// Arguments:
-/// - static variable name for connection
-/// - type of connection wrapper
-/// - dependencies, whose migrations should be run prior to this domain's migrations
#[macro_export]
-macro_rules! static_connection {
- ($id:ident, $t:ident, [ $($d:ty),* ] $(, $global:ident)?) => {
+macro_rules! define_connection {
+ (pub static ref $id:ident: $t:ident<()> = $migrations:expr; $($global:ident)?) => {
+ pub struct $t($crate::sqlez::thread_safe_connection::ThreadSafeConnection);
+
impl ::std::ops::Deref for $t {
type Target = $crate::sqlez::thread_safe_connection::ThreadSafeConnection;
@@ -126,6 +123,16 @@ macro_rules! static_connection {
}
}
+ impl $crate::sqlez::domain::Domain for $t {
+ fn name() -> &'static str {
+ stringify!($t)
+ }
+
+ fn migrations() -> &'static [&'static str] {
+ $migrations
+ }
+ }
+
impl $t {
#[cfg(any(test, feature = "test-support"))]
pub async fn open_test_db(name: &'static str) -> Self {
@@ -135,8 +142,7 @@ macro_rules! static_connection {
#[cfg(any(test, feature = "test-support"))]
pub static $id: std::sync::LazyLock<$t> = std::sync::LazyLock::new(|| {
- #[allow(unused_parens)]
- $t($crate::smol::block_on($crate::open_test_db::<($($d,)* $t)>(stringify!($id))))
+ $t($crate::smol::block_on($crate::open_test_db::<$t>(stringify!($id))))
});
#[cfg(not(any(test, feature = "test-support")))]
@@ -147,10 +153,46 @@ macro_rules! static_connection {
} else {
$crate::RELEASE_CHANNEL.dev_name()
};
- #[allow(unused_parens)]
- $t($crate::smol::block_on($crate::open_db::<($($d,)* $t)>(db_dir, scope)))
+ $t($crate::smol::block_on($crate::open_db::<$t>(db_dir, scope)))
});
- }
+ };
+ (pub static ref $id:ident: $t:ident<$($d:ty),+> = $migrations:expr; $($global:ident)?) => {
+ pub struct $t($crate::sqlez::thread_safe_connection::ThreadSafeConnection);
+
+ impl ::std::ops::Deref for $t {
+ type Target = $crate::sqlez::thread_safe_connection::ThreadSafeConnection;
+
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+ }
+
+ impl $crate::sqlez::domain::Domain for $t {
+ fn name() -> &'static str {
+ stringify!($t)
+ }
+
+ fn migrations() -> &'static [&'static str] {
+ $migrations
+ }
+ }
+
+ #[cfg(any(test, feature = "test-support"))]
+ pub static $id: std::sync::LazyLock<$t> = std::sync::LazyLock::new(|| {
+ $t($crate::smol::block_on($crate::open_test_db::<($($d),+, $t)>(stringify!($id))))
+ });
+
+ #[cfg(not(any(test, feature = "test-support")))]
+ pub static $id: std::sync::LazyLock<$t> = std::sync::LazyLock::new(|| {
+ let db_dir = $crate::database_dir();
+ let scope = if false $(|| stringify!($global) == "global")? {
+ "global"
+ } else {
+ $crate::RELEASE_CHANNEL.dev_name()
+ };
+ $t($crate::smol::block_on($crate::open_db::<($($d),+, $t)>(db_dir, scope)))
+ });
+ };
}
pub fn write_and_log(cx: &App, db_write: impl FnOnce() -> F + Send + 'static)
@@ -177,12 +219,17 @@ mod tests {
enum BadDB {}
impl Domain for BadDB {
- const NAME: &str = "db_tests";
- const MIGRATIONS: &[&str] = &[
- sql!(CREATE TABLE test(value);),
- // failure because test already exists
- sql!(CREATE TABLE test(value);),
- ];
+ fn name() -> &'static str {
+ "db_tests"
+ }
+
+ fn migrations() -> &'static [&'static str] {
+ &[
+ sql!(CREATE TABLE test(value);),
+ // failure because test already exists
+ sql!(CREATE TABLE test(value);),
+ ]
+ }
}
let tempdir = tempfile::Builder::new()
@@ -204,15 +251,25 @@ mod tests {
enum CorruptedDB {}
impl Domain for CorruptedDB {
- const NAME: &str = "db_tests";
- const MIGRATIONS: &[&str] = &[sql!(CREATE TABLE test(value);)];
+ fn name() -> &'static str {
+ "db_tests"
+ }
+
+ fn migrations() -> &'static [&'static str] {
+ &[sql!(CREATE TABLE test(value);)]
+ }
}
enum GoodDB {}
impl Domain for GoodDB {
- const NAME: &str = "db_tests"; //Notice same name
- const MIGRATIONS: &[&str] = &[sql!(CREATE TABLE test2(value);)];
+ fn name() -> &'static str {
+ "db_tests" //Notice same name
+ }
+
+ fn migrations() -> &'static [&'static str] {
+ &[sql!(CREATE TABLE test2(value);)] //But different migration
+ }
}
let tempdir = tempfile::Builder::new()
@@ -248,16 +305,25 @@ mod tests {
enum CorruptedDB {}
impl Domain for CorruptedDB {
- const NAME: &str = "db_tests";
+ fn name() -> &'static str {
+ "db_tests"
+ }
- const MIGRATIONS: &[&str] = &[sql!(CREATE TABLE test(value);)];
+ fn migrations() -> &'static [&'static str] {
+ &[sql!(CREATE TABLE test(value);)]
+ }
}
enum GoodDB {}
impl Domain for GoodDB {
- const NAME: &str = "db_tests"; //Notice same name
- const MIGRATIONS: &[&str] = &[sql!(CREATE TABLE test2(value);)]; // But different migration
+ fn name() -> &'static str {
+ "db_tests" //Notice same name
+ }
+
+ fn migrations() -> &'static [&'static str] {
+ &[sql!(CREATE TABLE test2(value);)] //But different migration
+ }
}
let tempdir = tempfile::Builder::new()
diff --git a/crates/db/src/kvp.rs b/crates/db/src/kvp.rs
index 8ea877b35b..256b789c9b 100644
--- a/crates/db/src/kvp.rs
+++ b/crates/db/src/kvp.rs
@@ -2,26 +2,16 @@ use gpui::App;
use sqlez_macros::sql;
use util::ResultExt as _;
-use crate::{
- query,
- sqlez::{domain::Domain, thread_safe_connection::ThreadSafeConnection},
- write_and_log,
-};
+use crate::{define_connection, query, write_and_log};
-pub struct KeyValueStore(crate::sqlez::thread_safe_connection::ThreadSafeConnection);
-
-impl Domain for KeyValueStore {
- const NAME: &str = stringify!(KeyValueStore);
-
- const MIGRATIONS: &[&str] = &[sql!(
+define_connection!(pub static ref KEY_VALUE_STORE: KeyValueStore<()> =
+ &[sql!(
CREATE TABLE IF NOT EXISTS kv_store(
key TEXT PRIMARY KEY,
value TEXT NOT NULL
) STRICT;
)];
-}
-
-crate::static_connection!(KEY_VALUE_STORE, KeyValueStore, []);
+);
pub trait Dismissable {
const KEY: &'static str;
@@ -101,19 +91,15 @@ mod tests {
}
}
-pub struct GlobalKeyValueStore(ThreadSafeConnection);
-
-impl Domain for GlobalKeyValueStore {
- const NAME: &str = stringify!(GlobalKeyValueStore);
- const MIGRATIONS: &[&str] = &[sql!(
+define_connection!(pub static ref GLOBAL_KEY_VALUE_STORE: GlobalKeyValueStore<()> =
+ &[sql!(
CREATE TABLE IF NOT EXISTS kv_store(
key TEXT PRIMARY KEY,
value TEXT NOT NULL
) STRICT;
)];
-}
-
-crate::static_connection!(GLOBAL_KEY_VALUE_STORE, GlobalKeyValueStore, [], global);
+ global
+);
impl GlobalKeyValueStore {
query! {
diff --git a/crates/debugger_ui/src/session/running/breakpoint_list.rs b/crates/debugger_ui/src/session/running/breakpoint_list.rs
index 233dba4c52..a9518314e3 100644
--- a/crates/debugger_ui/src/session/running/breakpoint_list.rs
+++ b/crates/debugger_ui/src/session/running/breakpoint_list.rs
@@ -10,7 +10,7 @@ use db::kvp::KEY_VALUE_STORE;
use editor::Editor;
use gpui::{
Action, AppContext, ClickEvent, Entity, FocusHandle, Focusable, MouseButton, ScrollStrategy,
- Stateful, Task, UniformListScrollHandle, WeakEntity, actions, uniform_list,
+ Task, UniformListScrollHandle, WeakEntity, actions, uniform_list,
};
use language::Point;
use project::{
@@ -23,8 +23,8 @@ use project::{
worktree_store::WorktreeStore,
};
use ui::{
- Divider, DividerColor, FluentBuilder as _, Indicator, IntoElement, ListItem, Render, Scrollbar,
- ScrollbarState, StatefulInteractiveElement, Tooltip, prelude::*,
+ Divider, DividerColor, FluentBuilder as _, Indicator, IntoElement, ListItem, Render,
+ StatefulInteractiveElement, Tooltip, WithScrollbar, prelude::*,
};
use workspace::Workspace;
use zed_actions::{ToggleEnableBreakpoint, UnsetBreakpoint};
@@ -49,7 +49,6 @@ pub(crate) struct BreakpointList {
breakpoint_store: Entity,
dap_store: Entity,
worktree_store: Entity,
- scrollbar_state: ScrollbarState,
breakpoints: Vec,
session: Option>,
focus_handle: FocusHandle,
@@ -87,7 +86,6 @@ impl BreakpointList {
let dap_store = project.dap_store();
let focus_handle = cx.focus_handle();
let scroll_handle = UniformListScrollHandle::new();
- let scrollbar_state = ScrollbarState::new(scroll_handle.clone());
let adapter_name = session.as_ref().map(|session| session.read(cx).adapter());
cx.new(|cx| {
@@ -95,7 +93,6 @@ impl BreakpointList {
breakpoint_store,
dap_store,
worktree_store,
- scrollbar_state,
breakpoints: Default::default(),
workspace,
session,
@@ -576,39 +573,6 @@ impl BreakpointList {
.flex_1()
}
- fn render_vertical_scrollbar(&self, cx: &mut Context) -> Stateful {
- div()
- .occlude()
- .id("breakpoint-list-vertical-scrollbar")
- .on_mouse_move(cx.listener(|_, _, _, cx| {
- cx.notify();
- cx.stop_propagation()
- }))
- .on_hover(|_, _, cx| {
- cx.stop_propagation();
- })
- .on_any_mouse_down(|_, _, cx| {
- cx.stop_propagation();
- })
- .on_mouse_up(
- MouseButton::Left,
- cx.listener(|_, _, _, cx| {
- cx.stop_propagation();
- }),
- )
- .on_scroll_wheel(cx.listener(|_, _, _, cx| {
- cx.notify();
- }))
- .h_full()
- .absolute()
- .right_1()
- .top_1()
- .bottom_0()
- .w(px(12.))
- .cursor_default()
- .children(Scrollbar::vertical(self.scrollbar_state.clone()).map(|s| s.auto_hide(cx)))
- }
-
pub(crate) fn render_control_strip(&self) -> AnyElement {
let selection_kind = self.selection_kind();
let focus_handle = self.focus_handle.clone();
@@ -789,7 +753,7 @@ impl Render for BreakpointList {
.size_full()
.pt_1()
.child(self.render_list(cx))
- .child(self.render_vertical_scrollbar(cx))
+ .vertical_scrollbar_for(self.scroll_handle.clone(), window, cx)
.when_some(self.strip_mode, |this, _| {
this.child(Divider::horizontal().color(DividerColor::Border))
.child(
diff --git a/crates/debugger_ui/src/session/running/memory_view.rs b/crates/debugger_ui/src/session/running/memory_view.rs
index e7b7963d3f..65dfd5fe0a 100644
--- a/crates/debugger_ui/src/session/running/memory_view.rs
+++ b/crates/debugger_ui/src/session/running/memory_view.rs
@@ -9,7 +9,7 @@ use std::{
use editor::{Editor, EditorElement, EditorStyle};
use gpui::{
Action, AppContext, DismissEvent, DragMoveEvent, Empty, Entity, FocusHandle, Focusable,
- MouseButton, Point, ScrollStrategy, ScrollWheelEvent, Stateful, Subscription, Task, TextStyle,
+ MouseButton, Point, ScrollStrategy, ScrollWheelEvent, Subscription, Task, TextStyle,
UniformList, UniformListScrollHandle, WeakEntity, actions, anchored, deferred, point,
uniform_list,
};
@@ -19,7 +19,7 @@ use settings::Settings;
use theme::ThemeSettings;
use ui::{
ContextMenu, Divider, DropdownMenu, FluentBuilder, IntoElement, PopoverMenuHandle, Render,
- Scrollbar, ScrollbarState, StatefulInteractiveElement, Tooltip, prelude::*,
+ ScrollableHandle, StatefulInteractiveElement, Tooltip, WithScrollbar, prelude::*,
};
use workspace::Workspace;
@@ -30,7 +30,6 @@ actions!(debugger, [GoToSelectedAddress]);
pub(crate) struct MemoryView {
workspace: WeakEntity
,
scroll_handle: UniformListScrollHandle,
- scroll_state: ScrollbarState,
stack_frame_list: WeakEntity,
focus_handle: FocusHandle,
view_state: ViewState,
@@ -121,11 +120,10 @@ impl ViewState {
}
}
-struct ScrollbarDragging;
-
static HEX_BYTES_MEMOIZED: LazyLock<[SharedString; 256]> =
LazyLock::new(|| std::array::from_fn(|byte| SharedString::from(format!("{byte:02X}"))));
static UNKNOWN_BYTE: SharedString = SharedString::new_static("??");
+
impl MemoryView {
pub(crate) fn new(
session: Entity,
@@ -139,10 +137,8 @@ impl MemoryView {
let query_editor = cx.new(|cx| Editor::single_line(window, cx));
- let scroll_state = ScrollbarState::new(scroll_handle.clone());
let mut this = Self {
workspace,
- scroll_state,
scroll_handle,
stack_frame_list,
focus_handle: cx.focus_handle(),
@@ -162,43 +158,6 @@ impl MemoryView {
this
}
- fn render_vertical_scrollbar(&self, cx: &mut Context) -> Stateful {
- div()
- .occlude()
- .id("memory-view-vertical-scrollbar")
- .on_drag_move(cx.listener(|this, evt, _, cx| {
- let did_handle = this.handle_scroll_drag(evt);
- cx.notify();
- if did_handle {
- cx.stop_propagation()
- }
- }))
- .on_drag(ScrollbarDragging, |_, _, _, cx| cx.new(|_| Empty))
- .on_hover(|_, _, cx| {
- cx.stop_propagation();
- })
- .on_any_mouse_down(|_, _, cx| {
- cx.stop_propagation();
- })
- .on_mouse_up(
- MouseButton::Left,
- cx.listener(|_, _, _, cx| {
- cx.stop_propagation();
- }),
- )
- .on_scroll_wheel(cx.listener(|_, _, _, cx| {
- cx.notify();
- }))
- .h_full()
- .absolute()
- .right_1()
- .top_1()
- .bottom_0()
- .w(px(12.))
- .cursor_default()
- .children(Scrollbar::vertical(self.scroll_state.clone()).map(|s| s.auto_hide(cx)))
- }
-
fn render_memory(&self, cx: &mut Context
) -> UniformList {
let weak = cx.weak_entity();
let session = self.session.clone();
@@ -233,10 +192,9 @@ impl MemoryView {
.track_scroll(self.scroll_handle.clone())
.on_scroll_wheel(cx.listener(|this, evt: &ScrollWheelEvent, window, _| {
let delta = evt.delta.pixel_delta(window.line_height());
- let scroll_handle = this.scroll_state.scroll_handle();
- let size = scroll_handle.content_size();
- let viewport = scroll_handle.viewport();
- let current_offset = scroll_handle.offset();
+ let size = this.scroll_handle.content_size();
+ let viewport = this.scroll_handle.viewport();
+ let current_offset = this.scroll_handle.offset();
let first_entry_offset_boundary = size.height / this.view_state.row_count() as f32;
let last_entry_offset_boundary = size.height - first_entry_offset_boundary;
if first_entry_offset_boundary + viewport.size.height > current_offset.y.abs() {
@@ -245,7 +203,8 @@ impl MemoryView {
} else if last_entry_offset_boundary < current_offset.y.abs() + viewport.size.height {
this.view_state.schedule_scroll_down();
}
- scroll_handle.set_offset(current_offset + point(px(0.), delta.y));
+ this.scroll_handle
+ .set_offset(current_offset + point(px(0.), delta.y));
}))
}
fn render_query_bar(&self, cx: &Context) -> impl IntoElement {
@@ -297,7 +256,7 @@ impl MemoryView {
}
let row_count = self.view_state.row_count();
debug_assert!(row_count > 1);
- let scroll_handle = self.scroll_state.scroll_handle();
+ let scroll_handle = &self.scroll_handle;
let viewport = scroll_handle.viewport();
if viewport.bottom() < evt.event.position.y {
@@ -307,13 +266,15 @@ impl MemoryView {
}
}
- fn handle_scroll_drag(&mut self, evt: &DragMoveEvent) -> bool {
- if !self.scroll_state.is_dragging() {
- return false;
- }
+ #[allow(unused)]
+ fn handle_scroll_drag(&mut self, evt: &DragMoveEvent<()>) -> bool {
+ // todo!
+ // if !self.scroll_state.is_dragging() {
+ // return false;
+ // }
let row_count = self.view_state.row_count();
debug_assert!(row_count > 1);
- let scroll_handle = self.scroll_state.scroll_handle();
+ let scroll_handle = &self.scroll_handle;
let viewport = scroll_handle.viewport();
if viewport.bottom() < evt.event.position.y {
@@ -943,7 +904,7 @@ impl Render for MemoryView {
)
.with_priority(1)
}))
- .child(self.render_vertical_scrollbar(cx)),
+ .vertical_scrollbar_for(self.scroll_handle.clone(), window, cx),
)
}
}
diff --git a/crates/debugger_ui/src/session/running/module_list.rs b/crates/debugger_ui/src/session/running/module_list.rs
index 7743cfbdee..4ea763c92c 100644
--- a/crates/debugger_ui/src/session/running/module_list.rs
+++ b/crates/debugger_ui/src/session/running/module_list.rs
@@ -1,15 +1,15 @@
use anyhow::anyhow;
use dap::Module;
use gpui::{
- AnyElement, Entity, FocusHandle, Focusable, MouseButton, ScrollStrategy, Stateful,
- Subscription, Task, UniformListScrollHandle, WeakEntity, uniform_list,
+ AnyElement, Entity, FocusHandle, Focusable, ScrollStrategy, Subscription, Task,
+ UniformListScrollHandle, WeakEntity, uniform_list,
};
use project::{
ProjectItem as _, ProjectPath,
debugger::session::{Session, SessionEvent},
};
use std::{ops::Range, path::Path, sync::Arc};
-use ui::{Scrollbar, ScrollbarState, prelude::*};
+use ui::{WithScrollbar, prelude::*};
use workspace::Workspace;
pub struct ModuleList {
@@ -18,7 +18,6 @@ pub struct ModuleList {
session: Entity,
workspace: WeakEntity,
focus_handle: FocusHandle,
- scrollbar_state: ScrollbarState,
entries: Vec,
_rebuild_task: Option>,
_subscription: Subscription,
@@ -44,7 +43,6 @@ impl ModuleList {
let scroll_handle = UniformListScrollHandle::new();
Self {
- scrollbar_state: ScrollbarState::new(scroll_handle.clone()),
scroll_handle,
session,
workspace,
@@ -167,38 +165,6 @@ impl ModuleList {
self.session
.update(cx, |session, cx| session.modules(cx).to_vec())
}
- fn render_vertical_scrollbar(&self, cx: &mut Context) -> Stateful {
- div()
- .occlude()
- .id("module-list-vertical-scrollbar")
- .on_mouse_move(cx.listener(|_, _, _, cx| {
- cx.notify();
- cx.stop_propagation()
- }))
- .on_hover(|_, _, cx| {
- cx.stop_propagation();
- })
- .on_any_mouse_down(|_, _, cx| {
- cx.stop_propagation();
- })
- .on_mouse_up(
- MouseButton::Left,
- cx.listener(|_, _, _, cx| {
- cx.stop_propagation();
- }),
- )
- .on_scroll_wheel(cx.listener(|_, _, _, cx| {
- cx.notify();
- }))
- .h_full()
- .absolute()
- .right_1()
- .top_1()
- .bottom_0()
- .w(px(12.))
- .cursor_default()
- .children(Scrollbar::vertical(self.scrollbar_state.clone()))
- }
fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context
) {
let Some(ix) = self.selected_ix else { return };
@@ -313,6 +279,6 @@ impl Render for ModuleList {
.size_full()
.p_1()
.child(self.render_list(window, cx))
- .child(self.render_vertical_scrollbar(cx))
+ .vertical_scrollbar_for(self.scroll_handle.clone(), window, cx)
}
}
diff --git a/crates/debugger_ui/src/session/running/stack_frame_list.rs b/crates/debugger_ui/src/session/running/stack_frame_list.rs
index a4ea4ab654..b3c500b919 100644
--- a/crates/debugger_ui/src/session/running/stack_frame_list.rs
+++ b/crates/debugger_ui/src/session/running/stack_frame_list.rs
@@ -5,8 +5,8 @@ use std::time::Duration;
use anyhow::{Context as _, Result, anyhow};
use dap::StackFrameId;
use gpui::{
- AnyElement, Entity, EventEmitter, FocusHandle, Focusable, FontWeight, ListState, MouseButton,
- Stateful, Subscription, Task, WeakEntity, list,
+ AnyElement, Entity, EventEmitter, FocusHandle, Focusable, FontWeight, ListState, Subscription,
+ Task, WeakEntity, list,
};
use util::debug_panic;
@@ -15,7 +15,7 @@ use language::PointUtf16;
use project::debugger::breakpoint_store::ActiveStackFrame;
use project::debugger::session::{Session, SessionEvent, StackFrame};
use project::{ProjectItem, ProjectPath};
-use ui::{Scrollbar, ScrollbarState, Tooltip, prelude::*};
+use ui::{Tooltip, WithScrollbar, prelude::*};
use workspace::{ItemHandle, Workspace};
use super::RunningState;
@@ -35,7 +35,6 @@ pub struct StackFrameList {
workspace: WeakEntity,
selected_ix: Option,
opened_stack_frame_id: Option,
- scrollbar_state: ScrollbarState,
list_state: ListState,
error: Option,
_refresh_task: Task<()>,
@@ -71,7 +70,6 @@ impl StackFrameList {
});
let list_state = ListState::new(0, gpui::ListAlignment::Top, px(1000.));
- let scrollbar_state = ScrollbarState::new(list_state.clone());
let mut this = Self {
session,
@@ -84,7 +82,6 @@ impl StackFrameList {
selected_ix: None,
opened_stack_frame_id: None,
list_state,
- scrollbar_state,
_refresh_task: Task::ready(()),
};
this.schedule_refresh(true, window, cx);
@@ -581,39 +578,6 @@ impl StackFrameList {
}
}
- fn render_vertical_scrollbar(&self, cx: &mut Context) -> Stateful {
- div()
- .occlude()
- .id("stack-frame-list-vertical-scrollbar")
- .on_mouse_move(cx.listener(|_, _, _, cx| {
- cx.notify();
- cx.stop_propagation()
- }))
- .on_hover(|_, _, cx| {
- cx.stop_propagation();
- })
- .on_any_mouse_down(|_, _, cx| {
- cx.stop_propagation();
- })
- .on_mouse_up(
- MouseButton::Left,
- cx.listener(|_, _, _, cx| {
- cx.stop_propagation();
- }),
- )
- .on_scroll_wheel(cx.listener(|_, _, _, cx| {
- cx.notify();
- }))
- .h_full()
- .absolute()
- .right_1()
- .top_1()
- .bottom_0()
- .w(px(12.))
- .cursor_default()
- .children(Scrollbar::vertical(self.scrollbar_state.clone()))
- }
-
fn select_ix(&mut self, ix: Option
, cx: &mut Context) {
self.selected_ix = ix;
cx.notify();
@@ -740,7 +704,7 @@ impl Render for StackFrameList {
)
})
.child(self.render_list(window, cx))
- .child(self.render_vertical_scrollbar(cx))
+ .vertical_scrollbar_for(self.list_state.clone(), window, cx)
}
}
diff --git a/crates/debugger_ui/src/session/running/variable_list.rs b/crates/debugger_ui/src/session/running/variable_list.rs
index b396f0921e..494e6c7f86 100644
--- a/crates/debugger_ui/src/session/running/variable_list.rs
+++ b/crates/debugger_ui/src/session/running/variable_list.rs
@@ -8,9 +8,8 @@ use dap::{
use editor::Editor;
use gpui::{
Action, AnyElement, ClickEvent, ClipboardItem, Context, DismissEvent, Empty, Entity,
- FocusHandle, Focusable, Hsla, MouseButton, MouseDownEvent, Point, Stateful, Subscription,
- TextStyleRefinement, UniformListScrollHandle, WeakEntity, actions, anchored, deferred,
- uniform_list,
+ FocusHandle, Focusable, Hsla, MouseDownEvent, Point, Subscription, TextStyleRefinement,
+ UniformListScrollHandle, WeakEntity, actions, anchored, deferred, uniform_list,
};
use menu::{SelectFirst, SelectLast, SelectNext, SelectPrevious};
use project::debugger::{
@@ -18,7 +17,7 @@ use project::debugger::{
session::{Session, SessionEvent, Watcher},
};
use std::{collections::HashMap, ops::Range, sync::Arc};
-use ui::{ContextMenu, ListItem, ScrollableHandle, Scrollbar, ScrollbarState, Tooltip, prelude::*};
+use ui::{ContextMenu, ListItem, ScrollableHandle, Tooltip, WithScrollbar, prelude::*};
use util::{debug_panic, maybe};
actions!(
@@ -189,7 +188,6 @@ pub struct VariableList {
entry_states: HashMap,
selected_stack_frame_id: Option,
list_handle: UniformListScrollHandle,
- scrollbar_state: ScrollbarState,
session: Entity,
selection: Option,
open_context_menu: Option<(Entity, Point, Subscription)>,
@@ -235,7 +233,6 @@ impl VariableList {
let list_state = UniformListScrollHandle::default();
Self {
- scrollbar_state: ScrollbarState::new(list_state.clone()),
list_handle: list_state,
session,
focus_handle,
@@ -1500,39 +1497,6 @@ impl VariableList {
)
.into_any()
}
-
- fn render_vertical_scrollbar(&self, cx: &mut Context) -> Stateful {
- div()
- .occlude()
- .id("variable-list-vertical-scrollbar")
- .on_mouse_move(cx.listener(|_, _, _, cx| {
- cx.notify();
- cx.stop_propagation()
- }))
- .on_hover(|_, _, cx| {
- cx.stop_propagation();
- })
- .on_any_mouse_down(|_, _, cx| {
- cx.stop_propagation();
- })
- .on_mouse_up(
- MouseButton::Left,
- cx.listener(|_, _, _, cx| {
- cx.stop_propagation();
- }),
- )
- .on_scroll_wheel(cx.listener(|_, _, _, cx| {
- cx.notify();
- }))
- .h_full()
- .absolute()
- .right_1()
- .top_1()
- .bottom_0()
- .w(px(12.))
- .cursor_default()
- .children(Scrollbar::vertical(self.scrollbar_state.clone()))
- }
}
impl Focusable for VariableList {
@@ -1542,7 +1506,7 @@ impl Focusable for VariableList {
}
impl Render for VariableList {
- fn render(&mut self, _window: &mut Window, cx: &mut Context
) -> impl IntoElement {
+ fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement {
v_flex()
.track_focus(&self.focus_handle)
.key_context("VariableList")
@@ -1587,7 +1551,7 @@ impl Render for VariableList {
)
.with_priority(1)
}))
- .child(self.render_vertical_scrollbar(cx))
+ .vertical_scrollbar_for(self.list_handle.clone(), window, cx)
}
}
diff --git a/crates/docs_preprocessor/src/main.rs b/crates/docs_preprocessor/src/main.rs
index c8c3dc54b7..c900eb692a 100644
--- a/crates/docs_preprocessor/src/main.rs
+++ b/crates/docs_preprocessor/src/main.rs
@@ -19,10 +19,6 @@ static KEYMAP_LINUX: LazyLock = LazyLock::new(|| {
load_keymap("keymaps/default-linux.json").expect("Failed to load Linux keymap")
});
-static KEYMAP_WINDOWS: LazyLock = LazyLock::new(|| {
- load_keymap("keymaps/default-windows.json").expect("Failed to load Windows keymap")
-});
-
static ALL_ACTIONS: LazyLock> = LazyLock::new(dump_all_gpui_actions);
const FRONT_MATTER_COMMENT: &str = "";
@@ -220,7 +216,6 @@ fn find_binding(os: &str, action: &str) -> Option {
let keymap = match os {
"macos" => &KEYMAP_MACOS,
"linux" | "freebsd" => &KEYMAP_LINUX,
- "windows" => &KEYMAP_WINDOWS,
_ => unreachable!("Not a valid OS: {}", os),
};
diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs
index 80680ae9c0..448852430e 100644
--- a/crates/editor/src/editor.rs
+++ b/crates/editor/src/editor.rs
@@ -55,7 +55,7 @@ pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPla
pub use edit_prediction::Direction;
pub use editor_settings::{
CurrentLineHighlight, DocumentColorsRenderMode, EditorSettings, HideMouseMode,
- ScrollBeyondLastLine, ScrollbarAxes, SearchSettings, ShowMinimap, ShowScrollbar,
+ ScrollBeyondLastLine, ScrollbarAxes, SearchSettings, ShowMinimap,
};
pub use editor_settings_controls::*;
pub use element::{
@@ -165,7 +165,7 @@ use project::{
};
use rand::{seq::SliceRandom, thread_rng};
use rpc::{ErrorCode, ErrorExt, proto::PeerId};
-use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide};
+use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager};
use selections_collection::{
MutableSelectionsCollection, SelectionsCollection, resolve_selections,
};
@@ -198,7 +198,7 @@ use theme::{
};
use ui::{
ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
- IconSize, Indicator, Key, Tooltip, h_flex, prelude::*,
+ IconSize, Indicator, Key, Tooltip, h_flex, prelude::*, scrollbars::ScrollbarAutoHide,
};
use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
use workspace::{
@@ -2588,7 +2588,7 @@ impl Editor {
|| binding
.keystrokes()
.first()
- .is_some_and(|keystroke| keystroke.display_modifiers.modified())
+ .is_some_and(|keystroke| keystroke.modifiers.modified())
}))
}
@@ -7686,16 +7686,16 @@ impl Editor {
.keystroke()
{
modifiers_held = modifiers_held
- || (&accept_keystroke.display_modifiers == modifiers
- && accept_keystroke.display_modifiers.modified());
+ || (&accept_keystroke.modifiers == modifiers
+ && accept_keystroke.modifiers.modified());
};
if let Some(accept_partial_keystroke) = self
.accept_edit_prediction_keybind(true, window, cx)
.keystroke()
{
modifiers_held = modifiers_held
- || (&accept_partial_keystroke.display_modifiers == modifiers
- && accept_partial_keystroke.display_modifiers.modified());
+ || (&accept_partial_keystroke.modifiers == modifiers
+ && accept_partial_keystroke.modifiers.modified());
}
if modifiers_held {
@@ -9044,7 +9044,7 @@ impl Editor {
let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
- let modifiers_color = if accept_keystroke.display_modifiers == window.modifiers() {
+ let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
Color::Accent
} else {
Color::Muted
@@ -9056,19 +9056,19 @@ impl Editor {
.font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
.text_size(TextSize::XSmall.rems(cx))
.child(h_flex().children(ui::render_modifiers(
- &accept_keystroke.display_modifiers,
+ &accept_keystroke.modifiers,
PlatformStyle::platform(),
Some(modifiers_color),
Some(IconSize::XSmall.rems().into()),
true,
)))
.when(is_platform_style_mac, |parent| {
- parent.child(accept_keystroke.display_key.clone())
+ parent.child(accept_keystroke.key.clone())
})
.when(!is_platform_style_mac, |parent| {
parent.child(
Key::new(
- util::capitalize(&accept_keystroke.display_key),
+ util::capitalize(&accept_keystroke.key),
Some(Color::Default),
)
.size(Some(IconSize::XSmall.rems().into())),
@@ -9171,7 +9171,7 @@ impl Editor {
max_width: Pixels,
cursor_point: Point,
style: &EditorStyle,
- accept_keystroke: Option<&gpui::KeybindingKeystroke>,
+ accept_keystroke: Option<&gpui::Keystroke>,
_window: &Window,
cx: &mut Context,
) -> Option {
@@ -9249,7 +9249,7 @@ impl Editor {
accept_keystroke.as_ref(),
|el, accept_keystroke| {
el.child(h_flex().children(ui::render_modifiers(
- &accept_keystroke.display_modifiers,
+ &accept_keystroke.modifiers,
PlatformStyle::platform(),
Some(Color::Default),
Some(IconSize::XSmall.rems().into()),
@@ -9319,7 +9319,7 @@ impl Editor {
.child(completion),
)
.when_some(accept_keystroke, |el, accept_keystroke| {
- if !accept_keystroke.display_modifiers.modified() {
+ if !accept_keystroke.modifiers.modified() {
return el;
}
@@ -9338,7 +9338,7 @@ impl Editor {
.font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
.when(is_platform_style_mac, |parent| parent.gap_1())
.child(h_flex().children(ui::render_modifiers(
- &accept_keystroke.display_modifiers,
+ &accept_keystroke.modifiers,
PlatformStyle::platform(),
Some(if !has_completion {
Color::Muted
diff --git a/crates/editor/src/editor_settings.rs b/crates/editor/src/editor_settings.rs
index 1d7e04cae0..b52353c86d 100644
--- a/crates/editor/src/editor_settings.rs
+++ b/crates/editor/src/editor_settings.rs
@@ -7,6 +7,7 @@ use project::project_settings::DiagnosticSeverity;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsSources, VsCodeSettings};
+use ui::scrollbars::{ScrollbarVisibilitySetting, ShowScrollbar};
use util::serde::default_true;
/// Imports from the VSCode settings at
@@ -196,23 +197,6 @@ pub struct Gutter {
pub folds: bool,
}
-/// When to show the scrollbar in the editor.
-///
-/// Default: auto
-#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
-#[serde(rename_all = "snake_case")]
-pub enum ShowScrollbar {
- /// Show the scrollbar if there's important information or
- /// follow the system's configured behavior.
- Auto,
- /// Match the system's configured behavior.
- System,
- /// Always show the scrollbar.
- Always,
- /// Never show the scrollbar.
- Never,
-}
-
/// When to show the minimap in the editor.
///
/// Default: never
@@ -735,6 +719,12 @@ impl EditorSettings {
}
}
+impl ScrollbarVisibilitySetting for EditorSettings {
+ fn scrollbar_visibility(&self, _cx: &App) -> ShowScrollbar {
+ self.scrollbar.show
+ }
+}
+
impl Settings for EditorSettings {
const KEY: Option<&'static str> = None;
diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs
index 91034829f7..70cd413c01 100644
--- a/crates/editor/src/element.rs
+++ b/crates/editor/src/element.rs
@@ -18,7 +18,7 @@ use crate::{
editor_settings::{
CurrentLineHighlight, DocumentColorsRenderMode, DoubleClickInMultibuffer, Minimap,
MinimapThumb, MinimapThumbBorder, ScrollBeyondLastLine, ScrollbarAxes,
- ScrollbarDiagnostics, ShowMinimap, ShowScrollbar,
+ ScrollbarDiagnostics, ShowMinimap,
},
git::blame::{BlameRenderer, GitBlame, GlobalBlameRenderer},
hover_popover::{
@@ -43,10 +43,10 @@ use gpui::{
Bounds, ClickEvent, ClipboardItem, ContentMask, Context, Corner, Corners, CursorStyle,
DispatchPhase, Edges, Element, ElementInputHandler, Entity, Focusable as _, FontId,
GlobalElementId, Hitbox, HitboxBehavior, Hsla, InteractiveElement, IntoElement, IsZero,
- KeybindingKeystroke, Length, ModifiersChangedEvent, MouseButton, MouseClickEvent,
- MouseDownEvent, MouseMoveEvent, MouseUpEvent, PaintQuad, ParentElement, Pixels, ScrollDelta,
- ScrollHandle, ScrollWheelEvent, ShapedLine, SharedString, Size, StatefulInteractiveElement,
- Style, Styled, TextRun, TextStyleRefinement, WeakEntity, Window, anchored, deferred, div, fill,
+ Keystroke, Length, ModifiersChangedEvent, MouseButton, MouseClickEvent, MouseDownEvent,
+ MouseMoveEvent, MouseUpEvent, PaintQuad, ParentElement, Pixels, ScrollDelta, ScrollHandle,
+ ScrollWheelEvent, ShapedLine, SharedString, Size, StatefulInteractiveElement, Style, Styled,
+ TextRun, TextStyleRefinement, WeakEntity, Window, anchored, deferred, div, fill,
linear_color_stop, linear_gradient, outline, point, px, quad, relative, size, solid_background,
transparent_black,
};
@@ -84,7 +84,7 @@ use text::{BufferId, SelectionGoal};
use theme::{ActiveTheme, Appearance, BufferLineHeight, PlayerColor};
use ui::{
ButtonLike, ContextMenu, Indicator, KeyBinding, POPOVER_Y_PADDING, Tooltip, h_flex, prelude::*,
- right_click_menu,
+ right_click_menu, scrollbars::ShowScrollbar,
};
use unicode_segmentation::UnicodeSegmentation;
use util::post_inc;
@@ -7150,7 +7150,7 @@ fn header_jump_data(
pub struct AcceptEditPredictionBinding(pub(crate) Option);
impl AcceptEditPredictionBinding {
- pub fn keystroke(&self) -> Option<&KeybindingKeystroke> {
+ pub fn keystroke(&self) -> Option<&Keystroke> {
if let Some(binding) = self.0.as_ref() {
match &binding.keystrokes() {
[keystroke, ..] => Some(keystroke),
diff --git a/crates/editor/src/hover_popover.rs b/crates/editor/src/hover_popover.rs
index fab5345787..e65c6b1807 100644
--- a/crates/editor/src/hover_popover.rs
+++ b/crates/editor/src/hover_popover.rs
@@ -9,8 +9,8 @@ use anyhow::Context as _;
use gpui::{
AnyElement, AsyncWindowContext, Context, Entity, Focusable as _, FontWeight, Hsla,
InteractiveElement, IntoElement, MouseButton, ParentElement, Pixels, ScrollHandle, Size,
- Stateful, StatefulInteractiveElement, StyleRefinement, Styled, Subscription, Task,
- TextStyleRefinement, Window, div, px,
+ StatefulInteractiveElement, StyleRefinement, Styled, Subscription, Task, TextStyleRefinement,
+ Window, div, px,
};
use itertools::Itertools;
use language::{DiagnosticEntry, Language, LanguageRegistry};
@@ -23,7 +23,7 @@ use std::{borrow::Cow, cell::RefCell};
use std::{ops::Range, sync::Arc, time::Duration};
use std::{path::PathBuf, rc::Rc};
use theme::ThemeSettings;
-use ui::{Scrollbar, ScrollbarState, prelude::*, theme_is_transparent};
+use ui::{Scrollbars, WithScrollbar, prelude::*, theme_is_transparent};
use url::Url;
use util::TryFutureExt;
use workspace::{OpenOptions, OpenVisible, Workspace};
@@ -184,7 +184,6 @@ pub fn hover_at_inlay(
let hover_popover = InfoPopover {
symbol_range: RangeInEditor::Inlay(inlay_hover.range.clone()),
parsed_content,
- scrollbar_state: ScrollbarState::new(scroll_handle.clone()),
scroll_handle,
keyboard_grace: Rc::new(RefCell::new(false)),
anchor: None,
@@ -387,7 +386,6 @@ fn show_hover(
local_diagnostic,
markdown,
border_color,
- scrollbar_state: ScrollbarState::new(scroll_handle.clone()),
scroll_handle,
background_color,
keyboard_grace: Rc::new(RefCell::new(ignore_timeout)),
@@ -457,7 +455,6 @@ fn show_hover(
info_popovers.push(InfoPopover {
symbol_range: RangeInEditor::Text(range),
parsed_content,
- scrollbar_state: ScrollbarState::new(scroll_handle.clone()),
scroll_handle,
keyboard_grace: Rc::new(RefCell::new(ignore_timeout)),
anchor: Some(anchor),
@@ -507,7 +504,6 @@ fn show_hover(
info_popovers.push(InfoPopover {
symbol_range: RangeInEditor::Text(range),
parsed_content,
- scrollbar_state: ScrollbarState::new(scroll_handle.clone()),
scroll_handle,
keyboard_grace: Rc::new(RefCell::new(ignore_timeout)),
anchor: Some(anchor),
@@ -846,7 +842,6 @@ pub struct InfoPopover {
pub symbol_range: RangeInEditor,
pub parsed_content: Option>,
pub scroll_handle: ScrollHandle,
- pub scrollbar_state: ScrollbarState,
pub keyboard_grace: Rc>,
pub anchor: Option,
_subscription: Option,
@@ -891,7 +886,12 @@ impl InfoPopover {
.on_url_click(open_markdown_url),
),
)
- .child(self.render_vertical_scrollbar(cx))
+ .custom_scrollbars(
+ Scrollbars::for_settings::()
+ .tracked_scroll_handle(self.scroll_handle.clone()),
+ window,
+ cx,
+ )
})
.into_any_element()
}
@@ -905,39 +905,6 @@ impl InfoPopover {
cx.notify();
self.scroll_handle.set_offset(current);
}
-
- fn render_vertical_scrollbar(&self, cx: &mut Context) -> Stateful {
- div()
- .occlude()
- .id("info-popover-vertical-scroll")
- .on_mouse_move(cx.listener(|_, _, _, cx| {
- cx.notify();
- cx.stop_propagation()
- }))
- .on_hover(|_, _, cx| {
- cx.stop_propagation();
- })
- .on_any_mouse_down(|_, _, cx| {
- cx.stop_propagation();
- })
- .on_mouse_up(
- MouseButton::Left,
- cx.listener(|_, _, _, cx| {
- cx.stop_propagation();
- }),
- )
- .on_scroll_wheel(cx.listener(|_, _, _, cx| {
- cx.notify();
- }))
- .h_full()
- .absolute()
- .right_1()
- .top_1()
- .bottom_0()
- .w(px(12.))
- .cursor_default()
- .children(Scrollbar::vertical(self.scrollbar_state.clone()))
- }
}
pub struct DiagnosticPopover {
@@ -949,7 +916,6 @@ pub struct DiagnosticPopover {
pub anchor: Anchor,
_subscription: Subscription,
pub scroll_handle: ScrollHandle,
- pub scrollbar_state: ScrollbarState,
}
impl DiagnosticPopover {
@@ -1013,43 +979,15 @@ impl DiagnosticPopover {
),
),
)
- .child(self.render_vertical_scrollbar(cx)),
+ .custom_scrollbars(
+ Scrollbars::for_settings::
()
+ .tracked_scroll_handle(self.scroll_handle.clone()),
+ window,
+ cx,
+ ),
)
.into_any_element()
}
-
- fn render_vertical_scrollbar(&self, cx: &mut Context) -> Stateful {
- div()
- .occlude()
- .id("diagnostic-popover-vertical-scroll")
- .on_mouse_move(cx.listener(|_, _, _, cx| {
- cx.notify();
- cx.stop_propagation()
- }))
- .on_hover(|_, _, cx| {
- cx.stop_propagation();
- })
- .on_any_mouse_down(|_, _, cx| {
- cx.stop_propagation();
- })
- .on_mouse_up(
- MouseButton::Left,
- cx.listener(|_, _, _, cx| {
- cx.stop_propagation();
- }),
- )
- .on_scroll_wheel(cx.listener(|_, _, _, cx| {
- cx.notify();
- }))
- .h_full()
- .absolute()
- .right_1()
- .top_1()
- .bottom_0()
- .w(px(12.))
- .cursor_default()
- .children(Scrollbar::vertical(self.scrollbar_state.clone()))
- }
}
#[cfg(test)]
diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs
index b7110190fd..641e8a97ed 100644
--- a/crates/editor/src/items.rs
+++ b/crates/editor/src/items.rs
@@ -1404,7 +1404,7 @@ impl ProjectItem for Editor {
}
fn for_broken_project_item(
- abs_path: &Path,
+ abs_path: PathBuf,
is_local: bool,
e: &anyhow::Error,
window: &mut Window,
diff --git a/crates/editor/src/persistence.rs b/crates/editor/src/persistence.rs
index ec7c149b4e..88fde53947 100644
--- a/crates/editor/src/persistence.rs
+++ b/crates/editor/src/persistence.rs
@@ -1,17 +1,13 @@
use anyhow::Result;
-use db::{
- query,
- sqlez::{
- bindable::{Bind, Column, StaticColumnCount},
- domain::Domain,
- statement::Statement,
- },
- sqlez_macros::sql,
-};
+use db::sqlez::bindable::{Bind, Column, StaticColumnCount};
+use db::sqlez::statement::Statement;
use fs::MTime;
use itertools::Itertools as _;
use std::path::PathBuf;
+use db::sqlez_macros::sql;
+use db::{define_connection, query};
+
use workspace::{ItemId, WorkspaceDb, WorkspaceId};
#[derive(Clone, Debug, PartialEq, Default)]
@@ -87,11 +83,7 @@ impl Column for SerializedEditor {
}
}
-pub struct EditorDb(db::sqlez::thread_safe_connection::ThreadSafeConnection);
-
-impl Domain for EditorDb {
- const NAME: &str = stringify!(EditorDb);
-
+define_connection!(
// Current schema shape using pseudo-rust syntax:
// editors(
// item_id: usize,
@@ -121,8 +113,7 @@ impl Domain for EditorDb {
// start: usize,
// end: usize,
// )
-
- const MIGRATIONS: &[&str] = &[
+ pub static ref DB: EditorDb
= &[
sql! (
CREATE TABLE editors(
item_id INTEGER NOT NULL,
@@ -198,9 +189,7 @@ impl Domain for EditorDb {
) STRICT;
),
];
-}
-
-db::static_connection!(DB, EditorDb, [WorkspaceDb]);
+);
// https://www.sqlite.org/limits.html
// > <..> the maximum value of a host parameter number is SQLITE_MAX_VARIABLE_NUMBER,
diff --git a/crates/editor/src/scroll.rs b/crates/editor/src/scroll.rs
index 8231448618..828ab0594d 100644
--- a/crates/editor/src/scroll.rs
+++ b/crates/editor/src/scroll.rs
@@ -12,7 +12,7 @@ use crate::{
};
pub use autoscroll::{Autoscroll, AutoscrollStrategy};
use core::fmt::Debug;
-use gpui::{Along, App, Axis, Context, Global, Pixels, Task, Window, point, px};
+use gpui::{Along, App, Axis, Context, Pixels, Task, Window, point, px};
use language::language_settings::{AllLanguageSettings, SoftWrap};
use language::{Bias, Point};
pub use scroll_amount::ScrollAmount;
@@ -21,6 +21,7 @@ use std::{
cmp::Ordering,
time::{Duration, Instant},
};
+use ui::scrollbars::ScrollbarAutoHide;
use util::ResultExt;
use workspace::{ItemId, WorkspaceId};
@@ -29,11 +30,6 @@ const SCROLLBAR_SHOW_INTERVAL: Duration = Duration::from_secs(1);
pub struct WasScrolled(pub(crate) bool);
-#[derive(Default)]
-pub struct ScrollbarAutoHide(pub bool);
-
-impl Global for ScrollbarAutoHide {}
-
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct ScrollAnchor {
pub offset: gpui::Point,
@@ -327,7 +323,7 @@ impl ScrollManager {
cx.notify();
}
- if cx.default_global::().0 {
+ if cx.default_global::().should_hide() {
self.hide_scrollbar_task = Some(cx.spawn_in(window, async move |editor, cx| {
cx.background_executor()
.timer(SCROLLBAR_SHOW_INTERVAL)
diff --git a/crates/editor/src/signature_help.rs b/crates/editor/src/signature_help.rs
index cb21f35d7e..54d8a50115 100644
--- a/crates/editor/src/signature_help.rs
+++ b/crates/editor/src/signature_help.rs
@@ -2,8 +2,8 @@ use crate::actions::ShowSignatureHelp;
use crate::hover_popover::open_markdown_url;
use crate::{Editor, EditorSettings, ToggleAutoSignatureHelp, hover_markdown_style};
use gpui::{
- App, Context, Div, Entity, HighlightStyle, MouseButton, ScrollHandle, Size, Stateful,
- StyledText, Task, TextStyle, Window, combine_highlights,
+ App, Context, Entity, HighlightStyle, MouseButton, ScrollHandle, Size, StyledText, Task,
+ TextStyle, Window, combine_highlights,
};
use language::BufferSnapshot;
use markdown::{Markdown, MarkdownElement};
@@ -15,8 +15,8 @@ use theme::ThemeSettings;
use ui::{
ActiveTheme, AnyElement, ButtonCommon, ButtonStyle, Clickable, FluentBuilder, IconButton,
IconButtonShape, IconName, IconSize, InteractiveElement, IntoElement, Label, LabelCommon,
- LabelSize, ParentElement, Pixels, Scrollbar, ScrollbarState, SharedString,
- StatefulInteractiveElement, Styled, StyledExt, div, px, relative,
+ LabelSize, ParentElement, Pixels, SharedString, StatefulInteractiveElement, Styled, StyledExt,
+ WithScrollbar, div, relative,
};
// Language-specific settings may define quotes as "brackets", so filter them out separately.
@@ -243,7 +243,6 @@ impl Editor {
.min(signatures.len().saturating_sub(1));
let signature_help_popover = SignatureHelpPopover {
- scrollbar_state: ScrollbarState::new(scroll_handle.clone()),
style,
signatures,
current_signature,
@@ -330,7 +329,6 @@ pub struct SignatureHelpPopover {
pub signatures: Vec,
pub current_signature: usize,
scroll_handle: ScrollHandle,
- scrollbar_state: ScrollbarState,
}
impl SignatureHelpPopover {
@@ -391,7 +389,8 @@ impl SignatureHelpPopover {
)
}),
)
- .child(self.render_vertical_scrollbar(cx));
+ .vertical_scrollbar(window, cx);
+
let controls = if self.signatures.len() > 1 {
let prev_button = IconButton::new("signature_help_prev", IconName::ChevronUp)
.shape(IconButtonShape::Square)
@@ -460,26 +459,4 @@ impl SignatureHelpPopover {
.child(main_content)
.into_any_element()
}
-
- fn render_vertical_scrollbar(&self, cx: &mut Context) -> Stateful {
- div()
- .occlude()
- .id("signature_help_scrollbar")
- .on_mouse_move(cx.listener(|_, _, _, cx| {
- cx.notify();
- cx.stop_propagation()
- }))
- .on_hover(|_, _, cx| cx.stop_propagation())
- .on_any_mouse_down(|_, _, cx| cx.stop_propagation())
- .on_mouse_up(MouseButton::Left, |_, _, cx| cx.stop_propagation())
- .on_scroll_wheel(cx.listener(|_, _, _, cx| cx.notify()))
- .h_full()
- .absolute()
- .right_1()
- .top_1()
- .bottom_1()
- .w(px(12.))
- .cursor_default()
- .children(Scrollbar::vertical(self.scrollbar_state.clone()))
- }
}
diff --git a/crates/extensions_ui/src/extensions_ui.rs b/crates/extensions_ui/src/extensions_ui.rs
index fd504764b6..427e1a02aa 100644
--- a/crates/extensions_ui/src/extensions_ui.rs
+++ b/crates/extensions_ui/src/extensions_ui.rs
@@ -24,8 +24,8 @@ use settings::Settings;
use strum::IntoEnumIterator as _;
use theme::ThemeSettings;
use ui::{
- CheckboxWithLabel, Chip, ContextMenu, PopoverMenu, ScrollableHandle, Scrollbar, ScrollbarState,
- ToggleButton, Tooltip, prelude::*,
+ CheckboxWithLabel, Chip, ContextMenu, PopoverMenu, ScrollableHandle, ToggleButton, Tooltip,
+ WithScrollbar, prelude::*,
};
use vim_mode_setting::VimModeSetting;
use workspace::{
@@ -290,7 +290,6 @@ pub struct ExtensionsPage {
_subscriptions: [gpui::Subscription; 2],
extension_fetch_task: Option
>,
upsells: BTreeSet,
- scrollbar_state: ScrollbarState,
}
impl ExtensionsPage {
@@ -339,7 +338,7 @@ impl ExtensionsPage {
let mut this = Self {
workspace: workspace.weak_handle(),
- list: scroll_handle.clone(),
+ list: scroll_handle,
is_fetching_extensions: false,
filter: ExtensionFilter::All,
dev_extension_entries: Vec::new(),
@@ -351,7 +350,6 @@ impl ExtensionsPage {
_subscriptions: subscriptions,
query_editor,
upsells: BTreeSet::default(),
- scrollbar_state: ScrollbarState::new(scroll_handle),
};
this.fetch_extensions(
this.search_query(cx),
@@ -1375,7 +1373,7 @@ impl ExtensionsPage {
}
impl Render for ExtensionsPage {
- fn render(&mut self, _: &mut Window, cx: &mut Context) -> impl IntoElement {
+ fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement {
v_flex()
.size_full()
.bg(cx.theme().colors().editor_background)
@@ -1520,25 +1518,24 @@ impl Render for ExtensionsPage {
}
if count == 0 {
- return this.py_4().child(self.render_empty_state(cx));
- }
-
- let scroll_handle = self.list.clone();
- this.child(
- uniform_list("entries", count, cx.processor(Self::render_extensions))
+ this.py_4()
+ .child(self.render_empty_state(cx))
+ .into_any_element()
+ } else {
+ let scroll_handle = self.list.clone();
+ this.child(
+ uniform_list(
+ "entries",
+ count,
+ cx.processor(Self::render_extensions),
+ )
.flex_grow()
.pb_4()
- .track_scroll(scroll_handle),
- )
- .child(
- div()
- .absolute()
- .right_1()
- .top_0()
- .bottom_0()
- .w(px(12.))
- .children(Scrollbar::vertical(self.scrollbar_state.clone())),
- )
+ .track_scroll(scroll_handle.clone()),
+ )
+ .vertical_scrollbar_for(scroll_handle, window, cx)
+ .into_any_element()
+ }
}),
)
}
diff --git a/crates/feature_flags/src/feature_flags.rs b/crates/feature_flags/src/feature_flags.rs
index f5f7fc42b3..422979c429 100644
--- a/crates/feature_flags/src/feature_flags.rs
+++ b/crates/feature_flags/src/feature_flags.rs
@@ -98,10 +98,6 @@ impl FeatureFlag for GeminiAndNativeFeatureFlag {
// integration too, and we'd like to turn Gemini/Native on in new builds
// without enabling Claude Code in old builds.
const NAME: &'static str = "gemini-and-native";
-
- fn enabled_for_all() -> bool {
- true
- }
}
pub struct ClaudeCodeFeatureFlag;
@@ -205,7 +201,7 @@ impl FeatureFlagAppExt for App {
fn has_flag(&self) -> bool {
self.try_global::()
.map(|flags| flags.has_flag::())
- .unwrap_or(T::enabled_for_all())
+ .unwrap_or(false)
}
fn is_staff(&self) -> bool {
diff --git a/crates/git_ui/src/git_panel.rs b/crates/git_ui/src/git_panel.rs
index 4ecb4a8829..ef0ab34394 100644
--- a/crates/git_ui/src/git_panel.rs
+++ b/crates/git_ui/src/git_panel.rs
@@ -13,10 +13,7 @@ use agent_settings::AgentSettings;
use anyhow::Context as _;
use askpass::AskPassDelegate;
use db::kvp::KEY_VALUE_STORE;
-use editor::{
- Editor, EditorElement, EditorMode, EditorSettings, MultiBuffer, ShowScrollbar,
- scroll::ScrollbarAutoHide,
-};
+use editor::{Editor, EditorElement, EditorMode, MultiBuffer};
use futures::StreamExt as _;
use git::blame::ParsedCommitMessage;
use git::repository::{
@@ -31,7 +28,7 @@ use git::{
UnstageAll,
};
use gpui::{
- Action, Animation, AnimationExt as _, AsyncApp, AsyncWindowContext, Axis, ClickEvent, Corner,
+ Action, Animation, AnimationExt as _, AsyncApp, AsyncWindowContext, ClickEvent, Corner,
DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, KeyContext,
ListHorizontalSizingBehavior, ListSizingBehavior, MouseButton, MouseDownEvent, Point,
PromptLevel, ScrollStrategy, Subscription, Task, Transformation, UniformListScrollHandle,
@@ -63,9 +60,10 @@ use std::{collections::HashSet, sync::Arc, time::Duration, usize};
use strum::{IntoEnumIterator, VariantNames};
use time::OffsetDateTime;
use ui::{
- Checkbox, ContextMenu, ElevationIndex, IconPosition, Label, LabelSize, PopoverMenu, Scrollbar,
- ScrollbarState, SplitButton, Tooltip, prelude::*,
+ Checkbox, ContextMenu, ElevationIndex, IconPosition, Label, LabelSize, PopoverMenu,
+ SplitButton, Tooltip, prelude::*,
};
+use ui::{ScrollAxes, Scrollbars, WithScrollbar};
use util::{ResultExt, TryFutureExt, maybe};
use workspace::SERIALIZATION_THROTTLE_TIME;
@@ -276,61 +274,6 @@ struct PendingOperation {
op_id: usize,
}
-// computed state related to how to render scrollbars
-// one per axis
-// on render we just read this off the panel
-// we update it when
-// - settings change
-// - on focus in, on focus out, on hover, etc.
-#[derive(Debug)]
-struct ScrollbarProperties {
- axis: Axis,
- show_scrollbar: bool,
- show_track: bool,
- auto_hide: bool,
- hide_task: Option>,
- state: ScrollbarState,
-}
-
-impl ScrollbarProperties {
- // Shows the scrollbar and cancels any pending hide task
- fn show(&mut self, cx: &mut Context) {
- if !self.auto_hide {
- return;
- }
- self.show_scrollbar = true;
- self.hide_task.take();
- cx.notify();
- }
-
- fn hide(&mut self, window: &mut Window, cx: &mut Context) {
- const SCROLLBAR_SHOW_INTERVAL: Duration = Duration::from_secs(1);
-
- if !self.auto_hide {
- return;
- }
-
- let axis = self.axis;
- self.hide_task = Some(cx.spawn_in(window, async move |panel, cx| {
- cx.background_executor()
- .timer(SCROLLBAR_SHOW_INTERVAL)
- .await;
-
- if let Some(panel) = panel.upgrade() {
- panel
- .update(cx, |panel, cx| {
- match axis {
- Axis::Vertical => panel.vertical_scrollbar.show_scrollbar = false,
- Axis::Horizontal => panel.horizontal_scrollbar.show_scrollbar = false,
- }
- cx.notify();
- })
- .log_err();
- }
- }));
- }
-}
-
pub struct GitPanel {
pub(crate) active_repository: Option>,
pub(crate) commit_editor: Entity,
@@ -343,8 +286,6 @@ pub struct GitPanel {
single_tracked_entry: Option,
focus_handle: FocusHandle,
fs: Arc,
- horizontal_scrollbar: ScrollbarProperties,
- vertical_scrollbar: ScrollbarProperties,
new_count: usize,
entry_count: usize,
new_staged_count: usize,
@@ -429,10 +370,6 @@ impl GitPanel {
cx.new(|cx| {
let focus_handle = cx.focus_handle();
cx.on_focus(&focus_handle, window, Self::focus_in).detach();
- cx.on_focus_out(&focus_handle, window, |this, _, window, cx| {
- this.hide_scrollbars(window, cx);
- })
- .detach();
let mut was_sort_by_path = GitPanelSettings::get_global(cx).sort_by_path;
cx.observe_global::(move |this, cx| {
@@ -457,24 +394,6 @@ impl GitPanel {
let scroll_handle = UniformListScrollHandle::new();
- let vertical_scrollbar = ScrollbarProperties {
- axis: Axis::Vertical,
- state: ScrollbarState::new(scroll_handle.clone()).parent_entity(&cx.entity()),
- show_scrollbar: false,
- show_track: false,
- auto_hide: false,
- hide_task: None,
- };
-
- let horizontal_scrollbar = ScrollbarProperties {
- axis: Axis::Horizontal,
- state: ScrollbarState::new(scroll_handle.clone()).parent_entity(&cx.entity()),
- show_scrollbar: false,
- show_track: false,
- auto_hide: false,
- hide_task: None,
- };
-
let mut assistant_enabled = AgentSettings::get_global(cx).enabled;
let mut was_ai_disabled = DisableAiSettings::get_global(cx).disable_ai;
let _settings_subscription = cx.observe_global::(move |_, cx| {
@@ -555,8 +474,6 @@ impl GitPanel {
workspace: workspace.weak_handle(),
modal_open: false,
entry_count: 0,
- horizontal_scrollbar,
- vertical_scrollbar,
bulk_staging: None,
_settings_subscription,
};
@@ -566,86 +483,6 @@ impl GitPanel {
})
}
- fn hide_scrollbars(&mut self, window: &mut Window, cx: &mut Context) {
- self.horizontal_scrollbar.hide(window, cx);
- self.vertical_scrollbar.hide(window, cx);
- }
-
- fn update_scrollbar_properties(&mut self, _window: &mut Window, cx: &mut Context) {
- // TODO: This PR should have defined Editor's `scrollbar.axis`
- // as an Option, not a ScrollbarAxes as it would allow you to
- // `.unwrap_or(EditorSettings::get_global(cx).scrollbar.show)`.
- //
- // Once this is fixed we can extend the GitPanelSettings with a `scrollbar.axis`
- // so we can show each axis based on the settings.
- //
- // We should fix this. PR: https://github.com/zed-industries/zed/pull/19495
-
- let show_setting = GitPanelSettings::get_global(cx)
- .scrollbar
- .show
- .unwrap_or(EditorSettings::get_global(cx).scrollbar.show);
-
- let scroll_handle = self.scroll_handle.0.borrow();
-
- let autohide = |show: ShowScrollbar, cx: &mut Context| match show {
- ShowScrollbar::Auto => true,
- ShowScrollbar::System => cx
- .try_global::()
- .map_or_else(|| cx.should_auto_hide_scrollbars(), |autohide| autohide.0),
- ShowScrollbar::Always => false,
- ShowScrollbar::Never => false,
- };
-
- let longest_item_width = scroll_handle.last_item_size.and_then(|size| {
- (size.contents.width > size.item.width).then_some(size.contents.width)
- });
-
- // is there an item long enough that we should show a horizontal scrollbar?
- let item_wider_than_container = if let Some(longest_item_width) = longest_item_width {
- longest_item_width > px(scroll_handle.base_handle.bounds().size.width.0)
- } else {
- true
- };
-
- let show_horizontal = match (show_setting, item_wider_than_container) {
- (_, false) => false,
- (ShowScrollbar::Auto | ShowScrollbar::System | ShowScrollbar::Always, true) => true,
- (ShowScrollbar::Never, true) => false,
- };
-
- let show_vertical = match show_setting {
- ShowScrollbar::Auto | ShowScrollbar::System | ShowScrollbar::Always => true,
- ShowScrollbar::Never => false,
- };
-
- let show_horizontal_track =
- show_horizontal && matches!(show_setting, ShowScrollbar::Always);
-
- // TODO: we probably should hide the scroll track when the list doesn't need to scroll
- let show_vertical_track = show_vertical && matches!(show_setting, ShowScrollbar::Always);
-
- self.vertical_scrollbar = ScrollbarProperties {
- axis: self.vertical_scrollbar.axis,
- state: self.vertical_scrollbar.state.clone(),
- show_scrollbar: show_vertical,
- show_track: show_vertical_track,
- auto_hide: autohide(show_setting, cx),
- hide_task: None,
- };
-
- self.horizontal_scrollbar = ScrollbarProperties {
- axis: self.horizontal_scrollbar.axis,
- state: self.horizontal_scrollbar.state.clone(),
- show_scrollbar: show_horizontal,
- show_track: show_horizontal_track,
- auto_hide: autohide(show_setting, cx),
- hide_task: None,
- };
-
- cx.notify();
- }
-
pub fn entry_by_path(&self, path: &RepoPath, cx: &App) -> Option {
if GitPanelSettings::get_global(cx).sort_by_path {
return self
@@ -2594,12 +2431,11 @@ impl GitPanel {
cx.background_executor().timer(UPDATE_DEBOUNCE).await;
if let Some(git_panel) = handle.upgrade() {
git_panel
- .update_in(cx, |git_panel, window, cx| {
+ .update(cx, |git_panel, cx| {
if clear_pending {
git_panel.clear_pending();
}
git_panel.update_visible_entries(cx);
- git_panel.update_scrollbar_properties(window, cx);
})
.ok();
}
@@ -3710,110 +3546,6 @@ impl GitPanel {
)
}
- fn render_vertical_scrollbar(
- &self,
- show_horizontal_scrollbar_container: bool,
- cx: &mut Context,
- ) -> impl IntoElement {
- div()
- .id("git-panel-vertical-scroll")
- .occlude()
- .flex_none()
- .h_full()
- .cursor_default()
- .absolute()
- .right_0()
- .top_0()
- .bottom_0()
- .w(px(12.))
- .when(show_horizontal_scrollbar_container, |this| {
- this.pb_neg_3p5()
- })
- .on_mouse_move(cx.listener(|_, _, _, cx| {
- cx.notify();
- cx.stop_propagation()
- }))
- .on_hover(|_, _, cx| {
- cx.stop_propagation();
- })
- .on_any_mouse_down(|_, _, cx| {
- cx.stop_propagation();
- })
- .on_mouse_up(
- MouseButton::Left,
- cx.listener(|this, _, window, cx| {
- if !this.vertical_scrollbar.state.is_dragging()
- && !this.focus_handle.contains_focused(window, cx)
- {
- this.vertical_scrollbar.hide(window, cx);
- cx.notify();
- }
-
- cx.stop_propagation();
- }),
- )
- .on_scroll_wheel(cx.listener(|_, _, _, cx| {
- cx.notify();
- }))
- .children(Scrollbar::vertical(
- // percentage as f32..end_offset as f32,
- self.vertical_scrollbar.state.clone(),
- ))
- }
-
- /// Renders the horizontal scrollbar.
- ///
- /// The right offset is used to determine how far to the right the
- /// scrollbar should extend to, useful for ensuring it doesn't collide
- /// with the vertical scrollbar when visible.
- fn render_horizontal_scrollbar(
- &self,
- right_offset: Pixels,
- cx: &mut Context,
- ) -> impl IntoElement {
- div()
- .id("git-panel-horizontal-scroll")
- .occlude()
- .flex_none()
- .w_full()
- .cursor_default()
- .absolute()
- .bottom_neg_px()
- .left_0()
- .right_0()
- .pr(right_offset)
- .on_mouse_move(cx.listener(|_, _, _, cx| {
- cx.notify();
- cx.stop_propagation()
- }))
- .on_hover(|_, _, cx| {
- cx.stop_propagation();
- })
- .on_any_mouse_down(|_, _, cx| {
- cx.stop_propagation();
- })
- .on_mouse_up(
- MouseButton::Left,
- cx.listener(|this, _, window, cx| {
- if !this.horizontal_scrollbar.state.is_dragging()
- && !this.focus_handle.contains_focused(window, cx)
- {
- this.horizontal_scrollbar.hide(window, cx);
- cx.notify();
- }
-
- cx.stop_propagation();
- }),
- )
- .on_scroll_wheel(cx.listener(|_, _, _, cx| {
- cx.notify();
- }))
- .children(Scrollbar::horizontal(
- // percentage as f32..end_offset as f32,
- self.horizontal_scrollbar.state.clone(),
- ))
- }
-
fn render_buffer_header_controls(
&self,
entity: &Entity,
@@ -3861,33 +3593,16 @@ impl GitPanel {
fn render_entries(
&self,
has_write_access: bool,
- _: &Window,
+ window: &mut Window,
cx: &mut Context,
) -> impl IntoElement {
let entry_count = self.entries.len();
- let scroll_track_size = px(16.);
-
- let h_scroll_offset = if self.vertical_scrollbar.show_scrollbar {
- // magic number
- px(3.)
- } else {
- px(0.)
- };
-
v_flex()
.flex_1()
.size_full()
.overflow_hidden()
.relative()
- // Show a border on the top and bottom of the container when
- // the vertical scrollbar container is visible so we don't have a
- // floating left border in the panel.
- .when(self.vertical_scrollbar.show_track, |this| {
- this.border_t_1()
- .border_b_1()
- .border_color(cx.theme().colors().border)
- })
.child(
h_flex()
.flex_1()
@@ -3928,15 +3643,6 @@ impl GitPanel {
items
}),
)
- .when(
- !self.horizontal_scrollbar.show_track
- && self.horizontal_scrollbar.show_scrollbar,
- |this| {
- // when not showing the horizontal scrollbar track, make sure we don't
- // obscure the last entry
- this.pb(scroll_track_size)
- },
- )
.size_full()
.flex_grow()
.with_sizing_behavior(ListSizingBehavior::Auto)
@@ -3952,72 +3658,14 @@ impl GitPanel {
this.deploy_panel_context_menu(event.position, window, cx)
}),
)
- .when(self.vertical_scrollbar.show_track, |this| {
- this.child(
- v_flex()
- .h_full()
- .flex_none()
- .w(scroll_track_size)
- .bg(cx.theme().colors().panel_background)
- .child(
- div()
- .size_full()
- .flex_1()
- .border_l_1()
- .border_color(cx.theme().colors().border),
- ),
- )
- })
- .when(self.vertical_scrollbar.show_scrollbar, |this| {
- this.child(
- self.render_vertical_scrollbar(
- self.horizontal_scrollbar.show_track,
- cx,
- ),
- )
- }),
+ .custom_scrollbars(
+ Scrollbars::for_settings::()
+ .tracked_scroll_handle(self.scroll_handle.clone())
+ .with_track_along(ScrollAxes::Horizontal),
+ window,
+ cx,
+ ),
)
- .when(self.horizontal_scrollbar.show_track, |this| {
- this.child(
- h_flex()
- .w_full()
- .h(scroll_track_size)
- .flex_none()
- .relative()
- .child(
- div()
- .w_full()
- .flex_1()
- // for some reason the horizontal scrollbar is 1px
- // taller than the vertical scrollbar??
- .h(scroll_track_size - px(1.))
- .bg(cx.theme().colors().panel_background)
- .border_t_1()
- .border_color(cx.theme().colors().border),
- )
- .when(self.vertical_scrollbar.show_track, |this| {
- this.child(
- div()
- .flex_none()
- // -1px prevents a missing pixel between the two container borders
- .w(scroll_track_size - px(1.))
- .h_full(),
- )
- .child(
- // HACK: Fill the missing 1px 🥲
- div()
- .absolute()
- .right(scroll_track_size - px(1.))
- .bottom(scroll_track_size - px(1.))
- .size_px()
- .bg(cx.theme().colors().border),
- )
- }),
- )
- })
- .when(self.horizontal_scrollbar.show_scrollbar, |this| {
- this.child(self.render_horizontal_scrollbar(h_scroll_offset, cx))
- })
}
fn entry_label(&self, label: impl Into, color: Color) -> Label {
@@ -4466,7 +4114,7 @@ fn current_language_model(cx: &Context<'_, GitPanel>) -> Option ShowScrollbar {
+ // TODO: This PR should have defined Editor's `scrollbar.axis`
+ // as an Option, not a ScrollbarAxes as it would allow you to
+ // `.unwrap_or(EditorSettings::get_global(cx).scrollbar.show)`.
+ //
+ // Once this is fixed we can extend the GitPanelSettings with a `scrollbar.axis`
+ // so we can show each axis based on the settings.
+ //
+ // We should fix this. PR: https://github.com/zed-industries/zed/pull/19495
+ self.scrollbar
+ .show
+ .unwrap_or_else(|| EditorSettings::get_global(cx).scrollbar.show)
+ }
+}
+
impl Settings for GitPanelSettings {
const KEY: Option<&'static str> = Some("git_panel");
diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs
index b59d7e717a..bbd59fa7bc 100644
--- a/crates/gpui/src/app.rs
+++ b/crates/gpui/src/app.rs
@@ -37,10 +37,10 @@ use crate::{
AssetSource, BackgroundExecutor, Bounds, ClipboardItem, CursorStyle, DispatchPhase, DisplayId,
EventEmitter, FocusHandle, FocusMap, ForegroundExecutor, Global, KeyBinding, KeyContext,
Keymap, Keystroke, LayoutId, Menu, MenuItem, OwnedMenu, PathPromptOptions, Pixels, Platform,
- PlatformDisplay, PlatformKeyboardLayout, PlatformKeyboardMapper, Point, PromptBuilder,
- PromptButton, PromptHandle, PromptLevel, Render, RenderImage, RenderablePromptHandle,
- Reservation, ScreenCaptureSource, SubscriberSet, Subscription, SvgRenderer, Task, TextSystem,
- Window, WindowAppearance, WindowHandle, WindowId, WindowInvalidator,
+ PlatformDisplay, PlatformKeyboardLayout, Point, PromptBuilder, PromptButton, PromptHandle,
+ PromptLevel, Render, RenderImage, RenderablePromptHandle, Reservation, ScreenCaptureSource,
+ SubscriberSet, Subscription, SvgRenderer, Task, TextSystem, Window, WindowAppearance,
+ WindowHandle, WindowId, WindowInvalidator,
colors::{Colors, GlobalColors},
current_platform, hash, init_app_menus,
};
@@ -263,7 +263,6 @@ pub struct App {
pub(crate) focus_handles: Arc,
pub(crate) keymap: Rc>,
pub(crate) keyboard_layout: Box,
- pub(crate) keyboard_mapper: Rc,
pub(crate) global_action_listeners:
FxHashMap>>,
pending_effects: VecDeque,
@@ -313,7 +312,6 @@ impl App {
let text_system = Arc::new(TextSystem::new(platform.text_system()));
let entities = EntityMap::new();
let keyboard_layout = platform.keyboard_layout();
- let keyboard_mapper = platform.keyboard_mapper();
let app = Rc::new_cyclic(|this| AppCell {
app: RefCell::new(App {
@@ -339,7 +337,6 @@ impl App {
focus_handles: Arc::new(RwLock::new(SlotMap::with_key())),
keymap: Rc::new(RefCell::new(Keymap::default())),
keyboard_layout,
- keyboard_mapper,
global_action_listeners: FxHashMap::default(),
pending_effects: VecDeque::new(),
pending_notifications: FxHashSet::default(),
@@ -379,7 +376,6 @@ impl App {
if let Some(app) = app.upgrade() {
let cx = &mut app.borrow_mut();
cx.keyboard_layout = cx.platform.keyboard_layout();
- cx.keyboard_mapper = cx.platform.keyboard_mapper();
cx.keyboard_layout_observers
.clone()
.retain(&(), move |callback| (callback)(cx));
@@ -428,11 +424,6 @@ impl App {
self.keyboard_layout.as_ref()
}
- /// Get the current keyboard mapper.
- pub fn keyboard_mapper(&self) -> &Rc {
- &self.keyboard_mapper
- }
-
/// Invokes a handler when the current keyboard layout changes
pub fn on_keyboard_layout_change(&self, mut callback: F) -> Subscription
where
diff --git a/crates/gpui/src/elements/div.rs b/crates/gpui/src/elements/div.rs
index c9826b704e..de5b720d46 100644
--- a/crates/gpui/src/elements/div.rs
+++ b/crates/gpui/src/elements/div.rs
@@ -16,10 +16,10 @@
//! constructed by combining these two systems into an all-in-one element.
use crate::{
- Action, AnyDrag, AnyElement, AnyTooltip, AnyView, App, Bounds, ClickEvent, DispatchPhase,
- Element, ElementId, Entity, FocusHandle, Global, GlobalElementId, Hitbox, HitboxBehavior,
- HitboxId, InspectorElementId, IntoElement, IsZero, KeyContext, KeyDownEvent, KeyUpEvent,
- KeyboardButton, KeyboardClickEvent, LayoutId, ModifiersChangedEvent, MouseButton,
+ AbsoluteLength, Action, AnyDrag, AnyElement, AnyTooltip, AnyView, App, Bounds, ClickEvent,
+ DispatchPhase, Element, ElementId, Entity, FocusHandle, Global, GlobalElementId, Hitbox,
+ HitboxBehavior, HitboxId, InspectorElementId, IntoElement, IsZero, KeyContext, KeyDownEvent,
+ KeyUpEvent, KeyboardButton, KeyboardClickEvent, LayoutId, ModifiersChangedEvent, MouseButton,
MouseClickEvent, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Overflow, ParentElement, Pixels,
Point, Render, ScrollWheelEvent, SharedString, Size, Style, StyleRefinement, Styled, Task,
TooltipId, Visibility, Window, WindowControlArea, point, px, size,
@@ -1036,6 +1036,15 @@ pub trait StatefulInteractiveElement: InteractiveElement {
self
}
+ /// Set the space to be reserved for rendering the scrollbar.
+ ///
+ /// This will only affect the layout of the element when overflow for this element is set to
+ /// `Overflow::Scroll`.
+ fn scrollbar_width(mut self, width: impl Into) -> Self {
+ self.interactivity().base_style.scrollbar_width = Some(width.into());
+ self
+ }
+
/// Track the scroll state of this element with the given handle.
fn track_scroll(mut self, scroll_handle: &ScrollHandle) -> Self {
self.interactivity().tracked_scroll_handle = Some(scroll_handle.clone());
diff --git a/crates/gpui/src/elements/uniform_list.rs b/crates/gpui/src/elements/uniform_list.rs
index cdf90d4eb8..9c601aac1d 100644
--- a/crates/gpui/src/elements/uniform_list.rs
+++ b/crates/gpui/src/elements/uniform_list.rs
@@ -5,10 +5,10 @@
//! elements with uniform height.
use crate::{
- AnyElement, App, AvailableSpace, Bounds, ContentMask, Element, ElementId, GlobalElementId,
- Hitbox, InspectorElementId, InteractiveElement, Interactivity, IntoElement, IsZero, LayoutId,
- ListSizingBehavior, Overflow, Pixels, Point, ScrollHandle, Size, StyleRefinement, Styled,
- Window, point, size,
+ AnyElement, App, AvailableSpace, Bounds, ContentMask, Element, ElementId, Entity,
+ GlobalElementId, Hitbox, InspectorElementId, InteractiveElement, Interactivity, IntoElement,
+ IsZero, LayoutId, ListSizingBehavior, Overflow, Pixels, Point, ScrollHandle, Size,
+ StyleRefinement, Styled, Window, point, size,
};
use smallvec::SmallVec;
use std::{cell::RefCell, cmp, ops::Range, rc::Rc};
@@ -71,7 +71,7 @@ pub struct UniformList {
/// Frame state used by the [UniformList].
pub struct UniformListFrameState {
items: SmallVec<[AnyElement; 32]>,
- decorations: SmallVec<[AnyElement; 1]>,
+ decorations: SmallVec<[AnyElement; 2]>,
}
/// A handle for controlling the scroll position of a uniform list.
@@ -529,6 +529,31 @@ pub trait UniformListDecoration {
) -> AnyElement;
}
+impl UniformListDecoration for Entity {
+ fn compute(
+ &self,
+ visible_range: Range,
+ bounds: Bounds,
+ scroll_offset: Point,
+ item_height: Pixels,
+ item_count: usize,
+ window: &mut Window,
+ cx: &mut App,
+ ) -> AnyElement {
+ self.update(cx, |inner, cx| {
+ inner.compute(
+ visible_range,
+ bounds,
+ scroll_offset,
+ item_height,
+ item_count,
+ window,
+ cx,
+ )
+ })
+ }
+}
+
impl UniformList {
/// Selects a specific list item for measurement.
pub fn with_width_from_item(mut self, item_index: Option) -> Self {
diff --git a/crates/gpui/src/keymap.rs b/crates/gpui/src/keymap.rs
index b3db09d821..757205fcc3 100644
--- a/crates/gpui/src/keymap.rs
+++ b/crates/gpui/src/keymap.rs
@@ -4,7 +4,7 @@ mod context;
pub use binding::*;
pub use context::*;
-use crate::{Action, AsKeystroke, Keystroke, is_no_action};
+use crate::{Action, Keystroke, is_no_action};
use collections::{HashMap, HashSet};
use smallvec::SmallVec;
use std::any::TypeId;
@@ -141,7 +141,7 @@ impl Keymap {
/// only.
pub fn bindings_for_input(
&self,
- input: &[impl AsKeystroke],
+ input: &[Keystroke],
context_stack: &[KeyContext],
) -> (SmallVec<[KeyBinding; 1]>, bool) {
let mut matched_bindings = SmallVec::<[(usize, BindingIndex, &KeyBinding); 1]>::new();
@@ -192,6 +192,7 @@ impl Keymap {
(bindings, !pending.is_empty())
}
+
/// Check if the given binding is enabled, given a certain key context.
/// Returns the deepest depth at which the binding matches, or None if it doesn't match.
fn binding_enabled(&self, binding: &KeyBinding, contexts: &[KeyContext]) -> Option {
@@ -638,7 +639,7 @@ mod tests {
fn assert_bindings(keymap: &Keymap, action: &dyn Action, expected: &[&str]) {
let actual = keymap
.bindings_for_action(action)
- .map(|binding| binding.keystrokes[0].inner.unparse())
+ .map(|binding| binding.keystrokes[0].unparse())
.collect::>();
assert_eq!(actual, expected, "{:?}", action);
}
diff --git a/crates/gpui/src/keymap/binding.rs b/crates/gpui/src/keymap/binding.rs
index a7cf9d5c54..729498d153 100644
--- a/crates/gpui/src/keymap/binding.rs
+++ b/crates/gpui/src/keymap/binding.rs
@@ -1,15 +1,14 @@
use std::rc::Rc;
-use crate::{
- Action, AsKeystroke, DummyKeyboardMapper, InvalidKeystrokeError, KeyBindingContextPredicate,
- KeybindingKeystroke, Keystroke, PlatformKeyboardMapper, SharedString,
-};
+use collections::HashMap;
+
+use crate::{Action, InvalidKeystrokeError, KeyBindingContextPredicate, Keystroke, SharedString};
use smallvec::SmallVec;
/// A keybinding and its associated metadata, from the keymap.
pub struct KeyBinding {
pub(crate) action: Box,
- pub(crate) keystrokes: SmallVec<[KeybindingKeystroke; 2]>,
+ pub(crate) keystrokes: SmallVec<[Keystroke; 2]>,
pub(crate) context_predicate: Option>,
pub(crate) meta: Option,
/// The json input string used when building the keybinding, if any
@@ -33,15 +32,7 @@ impl KeyBinding {
pub fn new(keystrokes: &str, action: A, context: Option<&str>) -> Self {
let context_predicate =
context.map(|context| KeyBindingContextPredicate::parse(context).unwrap().into());
- Self::load(
- keystrokes,
- Box::new(action),
- context_predicate,
- false,
- None,
- &DummyKeyboardMapper,
- )
- .unwrap()
+ Self::load(keystrokes, Box::new(action), context_predicate, None, None).unwrap()
}
/// Load a keybinding from the given raw data.
@@ -49,22 +40,24 @@ impl KeyBinding {
keystrokes: &str,
action: Box,
context_predicate: Option>,
- use_key_equivalents: bool,
+ key_equivalents: Option<&HashMap>,
action_input: Option,
- keyboard_mapper: &dyn PlatformKeyboardMapper,
) -> std::result::Result {
- let keystrokes: SmallVec<[KeybindingKeystroke; 2]> = keystrokes
+ let mut keystrokes: SmallVec<[Keystroke; 2]> = keystrokes
.split_whitespace()
- .map(|source| {
- let keystroke = Keystroke::parse(source)?;
- Ok(KeybindingKeystroke::new(
- keystroke,
- use_key_equivalents,
- keyboard_mapper,
- ))
- })
+ .map(Keystroke::parse)
.collect::>()?;
+ if let Some(equivalents) = key_equivalents {
+ for keystroke in keystrokes.iter_mut() {
+ if keystroke.key.chars().count() == 1
+ && let Some(key) = equivalents.get(&keystroke.key.chars().next().unwrap())
+ {
+ keystroke.key = key.to_string();
+ }
+ }
+ }
+
Ok(Self {
keystrokes,
action,
@@ -86,13 +79,13 @@ impl KeyBinding {
}
/// Check if the given keystrokes match this binding.
- pub fn match_keystrokes(&self, typed: &[impl AsKeystroke]) -> Option {
+ pub fn match_keystrokes(&self, typed: &[Keystroke]) -> Option